Skip to content

Commit c19630b

Browse files
author
MPCoreDeveloper
committed
refactor(ddl): close DDL parity gap via ApplySchema + TableSchemaDefinition
1 parent f4e1df6 commit c19630b

5 files changed

Lines changed: 292 additions & 41 deletions

File tree

src/SharpCoreDB/DataStructures/Table.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,43 @@ public StructRowSchema BuildStructRowSchema()
361361
return new StructRowSchema(Columns.ToArray(), ColumnTypes.ToArray(), offsets, currentOffset);
362362
}
363363

364+
/// <summary>
365+
/// <summary>
366+
/// Applies a parsed DDL schema to this table instance.
367+
/// Called by <c>SqlParser.ExecuteCreateTable</c> to bulk-set all schema properties
368+
/// in a single call, removing the need for individual property setters on <see cref="ITable"/>.
369+
/// </summary>
370+
/// <param name="schema">The schema definition from DDL parsing.</param>
371+
public void ApplySchema(TableSchemaDefinition schema)
372+
{
373+
ArgumentNullException.ThrowIfNull(schema);
374+
Columns = schema.Columns;
375+
ColumnTypes = schema.ColumnTypes;
376+
IsAuto = schema.IsAuto;
377+
PrimaryKeyIndex = schema.PrimaryKeyIndex;
378+
HasInternalRowId = schema.HasInternalRowId;
379+
DataFile = schema.DataFilePath;
380+
StorageMode = schema.StorageMode;
381+
IsNotNull = schema.IsNotNull;
382+
DefaultValues = schema.DefaultValues;
383+
UniqueConstraints = schema.UniqueConstraints;
384+
ForeignKeys = schema.ForeignKeys;
385+
DefaultExpressions = schema.DefaultExpressions;
386+
ColumnCheckExpressions = schema.ColumnCheckExpressions;
387+
TableCheckConstraints = schema.TableCheckConstraints;
388+
ColumnCollations = schema.ColumnCollations;
389+
ColumnLocaleNames = schema.ColumnLocaleNames;
390+
391+
// Initialise primary key B-tree with correct collation
392+
if (PrimaryKeyIndex >= 0)
393+
{
394+
var pkCollation = PrimaryKeyIndex < ColumnCollations.Count
395+
? ColumnCollations[PrimaryKeyIndex]
396+
: CollationType.Binary;
397+
Index = new BTree<string, long>(pkCollation);
398+
}
399+
}
400+
364401
/// <summary>
365402
/// Adds a new column to the table schema.
366403
/// Used for ALTER TABLE ADD COLUMN operations.

src/SharpCoreDB/Interfaces/ITable.cs

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
// </copyright>
55
namespace SharpCoreDB.Interfaces;
66

7+
using SharpCoreDB.Storage.Hybrid;
8+
79
/// <summary>
810
/// Interface for a database table.
911
/// </summary>
@@ -87,6 +89,66 @@ public interface ITable
8789
/// </summary>
8890
List<string?> ColumnLocaleNames { get; }
8991

92+
// ── DDL lifecycle members ───────────────────────────────────────────────
93+
// These have default implementations so existing test fakes that implement
94+
// ITable for read-only purposes do not need to be updated.
95+
96+
/// <summary>
97+
/// Gets or sets the storage engine mode for this table.
98+
/// Directory-mode tables use <see cref="StorageMode.Columnar"/> or <see cref="StorageMode.PageBased"/>.
99+
/// Single-file tables default to <see cref="StorageMode.Columnar"/> and the value is schema-only.
100+
/// </summary>
101+
StorageMode StorageMode { get => StorageMode.Columnar; set { } }
102+
103+
/// <summary>
104+
/// Gets or sets the primary key B-tree index.
105+
/// Directory-mode tables use a full BTree; single-file tables use a no-op implementation.
106+
/// </summary>
107+
IIndex<string, long> Index { get => NullIndex.Instance; set { } }
108+
109+
/// <summary>
110+
/// Gets or sets per-column DEFAULT expressions (SQL expression strings).
111+
/// </summary>
112+
List<string?> DefaultExpressions { get => []; set { } }
113+
114+
/// <summary>
115+
/// Gets or sets per-column CHECK constraint expressions.
116+
/// </summary>
117+
List<string?> ColumnCheckExpressions { get => []; set { } }
118+
119+
/// <summary>
120+
/// Gets or sets table-level CHECK constraint expressions.
121+
/// </summary>
122+
List<string> TableCheckConstraints { get => []; set { } }
123+
124+
/// <summary>
125+
/// Initializes (or re-initializes) the underlying storage engine for this table.
126+
/// For directory-mode tables this opens or creates the columnar/page-based file.
127+
/// For single-file tables this is a no-op (storage is managed by the provider).
128+
/// </summary>
129+
void InitializeStorageEngine() { }
130+
131+
/// <summary>
132+
/// Returns true if an index (by name or column name) exists on this table.
133+
/// Used by <c>CREATE INDEX IF NOT EXISTS</c> to avoid duplicate creation.
134+
/// Single-file tables without a real index registry always return false.
135+
/// </summary>
136+
/// <param name="nameOrColumn">Index name or column name to check.</param>
137+
/// <returns>True if the index exists.</returns>
138+
bool HasIndex(string nameOrColumn) => false;
139+
140+
/// <summary>
141+
/// Applies the given <paramref name="schema"/> definition to this table instance.
142+
/// Called by <c>SqlParser.ExecuteCreateTable</c> after the factory creates the table
143+
/// object but before it is registered or its storage is initialised.
144+
/// The default implementation is a no-op; concrete classes override this to populate
145+
/// their internal schema fields without requiring individual property setters on the interface.
146+
/// </summary>
147+
/// <param name="schema">The full schema definition produced by DDL parsing.</param>
148+
void ApplySchema(TableSchemaDefinition schema) { }
149+
150+
// ── Insert ──────────────────────────────────────────────────────────────
151+
90152
/// <summary>
91153
/// Inserts a row into the table.
92154
/// </summary>
@@ -242,7 +304,7 @@ public interface ITable
242304
/// This prevents stale/corrupt index data from being read after DDL operations.
243305
/// </summary>
244306
void ClearAllIndexes();
245-
307+
246308
/// <summary>
247309
/// Gets the cached row count (O(1) operation).
248310
/// Returns -1 if cache is not initialized.
@@ -279,7 +341,7 @@ public interface ITable
279341
/// <param name="columnName">The column name.</param>
280342
/// <returns>True if B-tree index exists.</returns>
281343
bool HasBTreeIndex(string columnName);
282-
344+
283345
/// <summary>
284346
/// Flushes all pending writes to disk.
285347
/// Ensures INSERT/UPDATE/DELETE operations are persisted.
@@ -314,4 +376,19 @@ public interface ITable
314376
/// <param name="key">The metadata key.</param>
315377
/// <returns>True if the entry was removed.</returns>
316378
bool RemoveMetadata(string key);
379+
380+
// ── Private helper singleton ─────────────────────────────────────────────
381+
382+
/// <summary>
383+
/// No-op <see cref="IIndex{TKey,TValue}"/> singleton returned by the default
384+
/// <see cref="Index"/> property implementation.
385+
/// </summary>
386+
private sealed class NullIndex : IIndex<string, long>
387+
{
388+
internal static readonly NullIndex Instance = new();
389+
public void Insert(string key, long value) { }
390+
public (bool Found, long Value) Search(string key) => (false, 0);
391+
public bool Delete(string key) => false;
392+
public void Clear() { }
393+
}
317394
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// <copyright file="TableSchemaDefinition.cs" company="MPCoreDeveloper">
2+
// Copyright (c) 2025-2026 MPCoreDeveloper and GitHub Copilot. All rights reserved.
3+
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
4+
// </copyright>
5+
namespace SharpCoreDB.Interfaces;
6+
7+
using SharpCoreDB.Storage.Hybrid;
8+
9+
/// <summary>
10+
/// Immutable schema definition produced by DDL parsing and passed to
11+
/// <see cref="ITable.ApplySchema"/> during <c>CREATE TABLE</c>.
12+
/// Using a record keeps the DDL path free of individual property setters on <see cref="ITable"/>.
13+
/// </summary>
14+
public sealed record TableSchemaDefinition
15+
{
16+
/// <summary>Gets the column names.</summary>
17+
public required List<string> Columns { get; init; }
18+
19+
/// <summary>Gets the column data types.</summary>
20+
public required List<DataType> ColumnTypes { get; init; }
21+
22+
/// <summary>Gets the auto-generation flags per column.</summary>
23+
public required List<bool> IsAuto { get; init; }
24+
25+
/// <summary>Gets the NOT NULL constraint flags per column.</summary>
26+
public required List<bool> IsNotNull { get; init; }
27+
28+
/// <summary>Gets the default values per column.</summary>
29+
public required List<object?> DefaultValues { get; init; }
30+
31+
/// <summary>Gets the DEFAULT expression strings per column (may be null per entry).</summary>
32+
public required List<string?> DefaultExpressions { get; init; }
33+
34+
/// <summary>Gets the inline CHECK expressions per column (may be null per entry).</summary>
35+
public required List<string?> ColumnCheckExpressions { get; init; }
36+
37+
/// <summary>Gets the table-level CHECK constraint expressions.</summary>
38+
public required List<string> TableCheckConstraints { get; init; }
39+
40+
/// <summary>Gets the foreign key constraints.</summary>
41+
public required List<ForeignKeyConstraint> ForeignKeys { get; init; }
42+
43+
/// <summary>Gets the unique constraints (each inner list is one unique set of columns).</summary>
44+
public required List<List<string>> UniqueConstraints { get; init; }
45+
46+
/// <summary>Gets the collation type per column.</summary>
47+
public required List<CollationType> ColumnCollations { get; init; }
48+
49+
/// <summary>Gets the locale name per column (null for non-Locale collations).</summary>
50+
public required List<string?> ColumnLocaleNames { get; init; }
51+
52+
/// <summary>Gets the index of the primary key column (-1 if none).</summary>
53+
public required int PrimaryKeyIndex { get; init; }
54+
55+
/// <summary>Gets whether an internal ULID <c>_rowid</c> column was injected.</summary>
56+
public required bool HasInternalRowId { get; init; }
57+
58+
/// <summary>Gets the storage engine mode requested for this table.</summary>
59+
public required StorageMode StorageMode { get; init; }
60+
61+
/// <summary>Gets the data file path resolved by the DDL engine.</summary>
62+
public required string DataFilePath { get; init; }
63+
}

src/SharpCoreDB/Services/SqlParser.DDL.cs

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -356,34 +356,32 @@ private void ExecuteCreateTable(string sql, string[] parts, IWAL? wal)
356356

357357
// Create brand new table with clean state — use the factory so the storage coupling
358358
// is contained to the factory implementation rather than spread through DDL logic.
359-
var table = (Table)_tableFactory.CreateTable(tableName, this.isReadOnly, this.config);
360-
table.Name = tableName;
361-
table.Columns = columns;
362-
table.ColumnTypes = columnTypes;
363-
table.IsAuto = isAuto;
364-
table.PrimaryKeyIndex = primaryKeyIndex;
365-
table.HasInternalRowId = hasInternalRowId; // ✅ AUTO-ROWID: Track internal _rowid
366-
table.DataFile = dataFilePath;
367-
table.StorageMode = storageMode;
368-
table.IsNotNull = isNotNull;
369-
table.DefaultValues = defaultValues;
370-
table.UniqueConstraints = uniqueConstraints;
371-
table.ForeignKeys = foreignKeys; // Added for Phase 1.2
372-
table.DefaultExpressions = defaultExpressions;
373-
table.ColumnCheckExpressions = columnCheckExpressions;
374-
table.TableCheckConstraints = tableCheckConstraints;
375-
table.ColumnCollations = columnCollations; // ✅ COLLATE Phase 2
376-
table.ColumnLocaleNames = columnLocaleNames; // ✅ Phase 9
377-
378-
// ✅ COLLATE Phase 4: Initialize Primary Key BTree Index with correct collation
379-
if (primaryKeyIndex >= 0)
359+
var table = _tableFactory.CreateTable(tableName, this.isReadOnly, this.config);
360+
361+
// Populate all schema fields via a single ApplySchema call.
362+
// This works for both directory-mode (Table) and single-file (SingleFileTable) without
363+
// any cast to a concrete type.
364+
table.ApplySchema(new TableSchemaDefinition
380365
{
381-
var pkCollation = primaryKeyIndex < columnCollations.Count
382-
? columnCollations[primaryKeyIndex]
383-
: CollationType.Binary;
384-
table.Index = new BTree<string, long>(pkCollation);
385-
}
366+
Columns = columns,
367+
ColumnTypes = columnTypes,
368+
IsAuto = isAuto,
369+
PrimaryKeyIndex = primaryKeyIndex,
370+
HasInternalRowId = hasInternalRowId,
371+
DataFilePath = dataFilePath,
372+
StorageMode = storageMode,
373+
IsNotNull = isNotNull,
374+
DefaultValues = defaultValues,
375+
UniqueConstraints = uniqueConstraints,
376+
ForeignKeys = foreignKeys,
377+
DefaultExpressions = defaultExpressions,
378+
ColumnCheckExpressions = columnCheckExpressions,
379+
TableCheckConstraints = tableCheckConstraints,
380+
ColumnCollations = columnCollations,
381+
ColumnLocaleNames = columnLocaleNames,
382+
});
386383

384+
table.Name = tableName;
387385
this.tables[tableName] = table;
388386

389387
// ✅ CRITICAL: Initialize storage engine IMMEDIATELY after creating table
@@ -499,7 +497,7 @@ private void ExecuteCreateIndex(string sql, string[] parts, IWAL? wal)
499497
// Check both index name and column name using the new HasIndex method
500498
var table = this.tables[tableName];
501499

502-
if (ifNotExists && (table is Table concreteTable) && concreteTable.HasIndex(indexName))
500+
if (ifNotExists && table.HasIndex(indexName))
503501
{
504502
return; // Silently skip - index already exists
505503
}

0 commit comments

Comments
 (0)