Skip to content

Commit 385af84

Browse files
authored
Merge pull request #216 from mk3008/ddl-support
feat: Add DDL parsers for DROP TABLE, DROP INDEX, and DROP CONSTRAINT statements
2 parents 4a94b22 + 7eb2116 commit 385af84

18 files changed

+2872
-68
lines changed

docs/guide/formatting-recipes.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,38 @@ Anonymous style prints bare symbols such as `?` or `%s`. `SqlFormatter` still re
176176
## Learn More
177177

178178
Check the full [`SqlFormatterOptions` API](../api/interfaces/SqlFormatterOptions.md) documentation for every toggle, including advanced preset configuration and default values.
179+
180+
## Formatting DDL statements
181+
182+
`SqlFormatter` now understands schema-definition statements. You can parse `CREATE TABLE`, `DROP TABLE`, `ALTER TABLE` constraint changes, and index management statements and feed the resulting ASTs through the formatter to keep them consistent with query output.
183+
184+
```ts
185+
import {
186+
CreateTableParser,
187+
DropTableParser,
188+
CreateIndexParser,
189+
DropIndexParser,
190+
DropConstraintParser,
191+
AlterTableParser,
192+
SqlFormatter
193+
} from 'rawsql-ts';
194+
195+
const ddl = `CREATE TABLE IF NOT EXISTS public.users (
196+
id BIGINT PRIMARY KEY,
197+
email TEXT NOT NULL UNIQUE,
198+
role_id INT REFERENCES auth.roles(id)
199+
) WITH (fillfactor = 80)`;
200+
201+
const ast = CreateTableParser.parse(ddl);
202+
const { formattedSql } = new SqlFormatter({ keywordCase: 'lower' }).format(ast);
203+
// formattedSql => drop-in-ready canonical SQL
204+
```
205+
206+
Use the dedicated parsers when working with other DDL statements:
207+
208+
- `DropTableParser` for `DROP TABLE` with multi-table targets and cascading options.
209+
- `AlterTableParser` to capture `ADD CONSTRAINT`/`DROP CONSTRAINT` actions on existing tables.
210+
- `CreateIndexParser` and `DropIndexParser` to normalize index definitions, including INCLUDE lists, storage parameters, and partial index predicates.
211+
- `DropConstraintParser` when databases support standalone constraint removal.
212+
213+
These parsers emit strongly typed models (`CreateTableQuery`, `CreateIndexStatement`, `AlterTableStatement`, and more) so the formatter and other visitors can treat DDL alongside queries.

packages/core/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ export * from './parsers/DeleteQueryParser';
88
export * from './parsers/WithClauseParser';
99
export * from './parsers/CreateTableParser';
1010
export * from './parsers/MergeQueryParser';
11+
export * from './parsers/CreateIndexParser';
12+
export * from './parsers/DropTableParser';
13+
export * from './parsers/DropIndexParser';
14+
export * from './parsers/AlterTableParser';
15+
export * from './parsers/DropConstraintParser';
1116

1217
export * from './models/BinarySelectQuery';
1318
export * from './models/SelectQuery';
@@ -22,6 +27,7 @@ export * from './models/UpdateQuery';
2227
export * from './models/DeleteQuery';
2328
export * from './models/CreateTableQuery';
2429
export * from './models/MergeQuery';
30+
export * from './models/DDLStatements';
2531

2632
export * from './transformers/CTECollector';
2733
export * from './transformers/CTENormalizer';

packages/core/src/models/CreateTableQuery.ts

Lines changed: 184 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,187 @@
11
import { SqlComponent } from "./SqlComponent";
22
import type { SelectQuery } from "./SelectQuery";
3-
import { ColumnReference, FunctionCall, IdentifierString, RawString } from "./ValueComponent";
3+
import {
4+
ColumnReference,
5+
FunctionCall,
6+
IdentifierString,
7+
RawString,
8+
ValueComponent,
9+
TypeValue,
10+
QualifiedName
11+
} from "./ValueComponent";
412
import { SimpleSelectQuery } from "./SimpleSelectQuery";
513
import { SelectClause, SelectItem, FromClause, TableSource, SourceExpression } from "./Clause";
614
import { SelectValueCollector } from "../transformers/SelectValueCollector";
715

8-
// Represents a CREATE TABLE query model
9-
// Supports temporary tables and AS SELECT ...
16+
export type ReferentialAction = 'cascade' | 'restrict' | 'no action' | 'set null' | 'set default';
17+
export type ConstraintDeferrability = 'deferrable' | 'not deferrable' | null;
18+
export type ConstraintInitially = 'immediate' | 'deferred' | null;
19+
export type MatchType = 'full' | 'partial' | 'simple' | null;
20+
21+
/**
22+
* Represents a REFERENCES clause definition that can be shared between column and table constraints.
23+
*/
24+
export class ReferenceDefinition extends SqlComponent {
25+
static kind = Symbol("ReferenceDefinition");
26+
targetTable: QualifiedName;
27+
columns: IdentifierString[] | null;
28+
matchType: MatchType;
29+
onDelete: ReferentialAction | null;
30+
onUpdate: ReferentialAction | null;
31+
deferrable: ConstraintDeferrability;
32+
initially: ConstraintInitially;
33+
34+
constructor(params: {
35+
targetTable: QualifiedName;
36+
columns?: IdentifierString[] | null;
37+
matchType?: MatchType;
38+
onDelete?: ReferentialAction | null;
39+
onUpdate?: ReferentialAction | null;
40+
deferrable?: ConstraintDeferrability;
41+
initially?: ConstraintInitially;
42+
}) {
43+
super();
44+
this.targetTable = params.targetTable;
45+
this.columns = params.columns ? [...params.columns] : null;
46+
this.matchType = params.matchType ?? null;
47+
this.onDelete = params.onDelete ?? null;
48+
this.onUpdate = params.onUpdate ?? null;
49+
this.deferrable = params.deferrable ?? null;
50+
this.initially = params.initially ?? null;
51+
}
52+
}
53+
54+
export type ColumnConstraintKind =
55+
| 'not-null'
56+
| 'null'
57+
| 'default'
58+
| 'primary-key'
59+
| 'unique'
60+
| 'references'
61+
| 'check'
62+
| 'generated-always-identity'
63+
| 'generated-by-default-identity'
64+
| 'raw';
65+
66+
/**
67+
* Column-level constraint definition.
68+
*/
69+
export class ColumnConstraintDefinition extends SqlComponent {
70+
static kind = Symbol("ColumnConstraintDefinition");
71+
kind: ColumnConstraintKind;
72+
constraintName?: IdentifierString;
73+
defaultValue?: ValueComponent;
74+
checkExpression?: ValueComponent;
75+
reference?: ReferenceDefinition;
76+
rawClause?: RawString;
77+
78+
constructor(params: {
79+
kind: ColumnConstraintKind;
80+
constraintName?: IdentifierString;
81+
defaultValue?: ValueComponent;
82+
checkExpression?: ValueComponent;
83+
reference?: ReferenceDefinition;
84+
rawClause?: RawString;
85+
}) {
86+
super();
87+
this.kind = params.kind;
88+
this.constraintName = params.constraintName;
89+
this.defaultValue = params.defaultValue;
90+
this.checkExpression = params.checkExpression;
91+
this.reference = params.reference;
92+
this.rawClause = params.rawClause;
93+
}
94+
}
95+
96+
export type TableConstraintKind = 'primary-key' | 'unique' | 'foreign-key' | 'check' | 'raw';
97+
98+
/**
99+
* Table-level constraint definition.
100+
*/
101+
export class TableConstraintDefinition extends SqlComponent {
102+
static kind = Symbol("TableConstraintDefinition");
103+
kind: TableConstraintKind;
104+
constraintName?: IdentifierString;
105+
columns: IdentifierString[] | null;
106+
reference?: ReferenceDefinition;
107+
checkExpression?: ValueComponent;
108+
rawClause?: RawString;
109+
deferrable: ConstraintDeferrability;
110+
initially: ConstraintInitially;
111+
112+
constructor(params: {
113+
kind: TableConstraintKind;
114+
constraintName?: IdentifierString;
115+
columns?: IdentifierString[] | null;
116+
reference?: ReferenceDefinition;
117+
checkExpression?: ValueComponent;
118+
rawClause?: RawString;
119+
deferrable?: ConstraintDeferrability;
120+
initially?: ConstraintInitially;
121+
}) {
122+
super();
123+
this.kind = params.kind;
124+
this.constraintName = params.constraintName;
125+
this.columns = params.columns ? [...params.columns] : null;
126+
this.reference = params.reference;
127+
this.checkExpression = params.checkExpression;
128+
this.rawClause = params.rawClause;
129+
this.deferrable = params.deferrable ?? null;
130+
this.initially = params.initially ?? null;
131+
}
132+
}
133+
134+
/**
135+
* Represents a single column definition within CREATE TABLE.
136+
*/
137+
export class TableColumnDefinition extends SqlComponent {
138+
static kind = Symbol("TableColumnDefinition");
139+
name: IdentifierString;
140+
dataType?: TypeValue | RawString;
141+
constraints: ColumnConstraintDefinition[];
142+
143+
constructor(params: {
144+
name: IdentifierString;
145+
dataType?: TypeValue | RawString;
146+
constraints?: ColumnConstraintDefinition[];
147+
}) {
148+
super();
149+
this.name = params.name;
150+
this.dataType = params.dataType;
151+
this.constraints = params.constraints ? [...params.constraints] : [];
152+
}
153+
}
154+
155+
// Represents a CREATE TABLE query model that supports column definitions and AS SELECT variants.
10156
export class CreateTableQuery extends SqlComponent {
11-
/** SqlComponent kind symbol for visitor pattern */
12157
static kind = Symbol("CreateTableQuery");
13-
/** Table name (with optional schema) */
14158
tableName: IdentifierString;
15-
/** If true, this is a temporary table */
159+
namespaces: string[] | null;
16160
isTemporary: boolean;
17-
/** If true, the statement includes IF NOT EXISTS */
18161
ifNotExists: boolean;
19-
/** Optional: SELECT query for AS SELECT ... */
162+
columns: TableColumnDefinition[];
163+
tableConstraints: TableConstraintDefinition[];
164+
tableOptions?: RawString | null;
20165
asSelectQuery?: SelectQuery;
21166

22167
constructor(params: {
23168
tableName: string;
169+
namespaces?: string[] | null;
24170
isTemporary?: boolean;
25171
ifNotExists?: boolean;
172+
columns?: TableColumnDefinition[];
173+
tableConstraints?: TableConstraintDefinition[];
174+
tableOptions?: RawString | null;
26175
asSelectQuery?: SelectQuery;
27176
}) {
28177
super();
29178
this.tableName = new IdentifierString(params.tableName);
179+
this.namespaces = params.namespaces ? [...params.namespaces] : null;
30180
this.isTemporary = params.isTemporary ?? false;
31181
this.ifNotExists = params.ifNotExists ?? false;
182+
this.columns = params.columns ? [...params.columns] : [];
183+
this.tableConstraints = params.tableConstraints ? [...params.tableConstraints] : [];
184+
this.tableOptions = params.tableOptions ?? null;
32185
this.asSelectQuery = params.asSelectQuery;
33186
}
34187

@@ -37,23 +190,36 @@ export class CreateTableQuery extends SqlComponent {
37190
*/
38191
getSelectQuery(): SimpleSelectQuery {
39192
let selectItems: SelectItem[];
193+
194+
// Prefer explicit AS SELECT query columns when present.
40195
if (this.asSelectQuery) {
41-
// Use SelectValueCollector to get columns from asSelectQuery
42196
const collector = new SelectValueCollector();
43197
const values = collector.collect(this.asSelectQuery);
44198
selectItems = values.map(val => new SelectItem(val.value, val.name));
199+
} else if (this.columns.length > 0) {
200+
// Use defined column names when the table definition is DDL-based.
201+
selectItems = this.columns.map(column => new SelectItem(
202+
new ColumnReference(null, column.name),
203+
column.name.name
204+
));
45205
} else {
46-
// fallback: wildcard
206+
// Fallback to wild-card selection when no column metadata is available.
47207
selectItems = [new SelectItem(new RawString("*"))];
48208
}
209+
210+
// Build a simple SELECT ... FROM table query.
211+
const qualifiedName = this.namespaces && this.namespaces.length > 0
212+
? [...this.namespaces, this.tableName.name].join(".")
213+
: this.tableName.name;
214+
49215
return new SimpleSelectQuery({
50216
selectClause: new SelectClause(selectItems),
51217
fromClause: new FromClause(
52218
new SourceExpression(
53-
new TableSource(null, this.tableName.name),
219+
new TableSource(null, qualifiedName),
54220
null
55221
),
56-
null // joins
222+
null
57223
),
58224
});
59225
}
@@ -62,16 +228,20 @@ export class CreateTableQuery extends SqlComponent {
62228
* Returns a SelectQuery that counts all rows in this table.
63229
*/
64230
getCountQuery(): SimpleSelectQuery {
231+
const qualifiedName = this.namespaces && this.namespaces.length > 0
232+
? [...this.namespaces, this.tableName.name].join(".")
233+
: this.tableName.name;
234+
65235
return new SimpleSelectQuery({
66236
selectClause: new SelectClause([
67237
new SelectItem(new FunctionCall(null, "count", new ColumnReference(null, "*"), null))
68238
]),
69239
fromClause: new FromClause(
70240
new SourceExpression(
71-
new TableSource(null, this.tableName.name),
241+
new TableSource(null, qualifiedName),
72242
null
73243
),
74-
null // joins
244+
null
75245
),
76246
});
77247
}

0 commit comments

Comments
 (0)