Skip to content

Commit c92efa9

Browse files
committed
add update and insert, disabled for now
1 parent 0f17baa commit c92efa9

7 files changed

Lines changed: 1494 additions & 1014 deletions

File tree

SQL_COVERAGE.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,40 @@ Tracking SQL feature coverage in `src/sql.ohm` / `src/ast.ts`.
209209
- [x] `<+>` L1 distance
210210
- [x] `<~>` Hamming distance (binary vectors)
211211
- [x] `<%>` Jaccard distance (binary vectors)
212+
213+
---
214+
215+
## INSERT
216+
217+
- [x] `INSERT INTO <table> VALUES (…)`
218+
- [x] `INSERT INTO <table> (col, …) VALUES (…)`
219+
- [x] Multi-row `VALUES (…), (…), …`
220+
- [x] Schema-qualified target table (`schema.table`)
221+
- [ ] Target table alias (`INSERT INTO t AS alias …`)
222+
- [ ] `INSERT INTO <table> [(cols)] SELECT …`
223+
- [ ] `DEFAULT` / `DEFAULT VALUES`
224+
- [ ] Expressions in `VALUES` (functions, arithmetic, casts, `NULL`)
225+
- [ ] `RETURNING <columns>` / `RETURNING *`
226+
- [ ] `ON CONFLICT DO NOTHING`
227+
- [ ] `ON CONFLICT (col, …) DO UPDATE SET … [WHERE …]`
228+
- [ ] `ON CONFLICT ON CONSTRAINT <name> …`
229+
- [ ] `EXCLUDED.col` reference in upsert `SET`
230+
- [ ] `WITH` / CTE before `INSERT`
231+
- [ ] `OVERRIDING SYSTEM VALUE` / `OVERRIDING USER VALUE`
232+
233+
---
234+
235+
## UPDATE
236+
237+
- [x] `UPDATE <table> SET col = expr`
238+
- [x] Multiple assignments: `SET a = …, b = …`
239+
- [x] Schema-qualified target table (`schema.table`)
240+
- [ ] Target table alias (`UPDATE t AS alias …`)
241+
- [x] `WHERE <condition>` (full WHERE expression grammar)
242+
- [ ] RHS expressions: literals, column refs, arithmetic, functions, `CASE`, `CAST`
243+
- [ ] `SET col = DEFAULT`
244+
- [ ] Row/tuple assignment: `SET (a, b) = (expr, expr)`
245+
- [ ] Row/tuple assignment from subquery: `SET (a, b) = (SELECT …)`
246+
- [ ] `FROM <table(s)>` clause (update-with-join)
247+
- [ ] `RETURNING <columns>` / `RETURNING *`
248+
- [ ] `WITH` / CTE before `UPDATE`

src/ast.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
export type Statement = SelectStatement | InsertStatement | UpdateStatement;
2+
13
export type ASTNode =
24
| SelectStatement
5+
| InsertStatement
6+
| UpdateStatement
7+
| ValuesRow
8+
| Assignment
39
| Column
410
| Alias
511
| SelectFrom
@@ -316,3 +322,28 @@ export interface TableName {
316322
schema?: string;
317323
name: string;
318324
}
325+
326+
export interface InsertStatement {
327+
readonly type: "insert";
328+
table: TableRef;
329+
columns: string[] | null;
330+
rows: ValuesRow[];
331+
}
332+
333+
export interface ValuesRow {
334+
readonly type: "values_row";
335+
values: WhereValue[];
336+
}
337+
338+
export interface UpdateStatement {
339+
readonly type: "update";
340+
table: TableRef;
341+
assignments: Assignment[];
342+
where: WhereRoot | null;
343+
}
344+
345+
export interface Assignment {
346+
readonly type: "assignment";
347+
column: string;
348+
value: WhereValue;
349+
}

src/output.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
FuncCall,
1212
GroupByClause,
1313
HavingClause,
14+
InsertStatement,
1415
JoinClause,
1516
JoinCondition,
1617
LimitClause,
@@ -19,8 +20,10 @@ import type {
1920
OrderByItem,
2021
SelectFrom,
2122
SelectStatement,
23+
Statement,
2224
TableRef,
2325
Terminal,
26+
UpdateStatement,
2427
WhereAnd,
2528
WhereArith,
2629
WhereBetween,
@@ -40,7 +43,7 @@ import type {
4043
} from "./ast";
4144
import { unreachable } from "./utils";
4245

43-
export function outputSql(ast: SelectStatement, pretty: boolean = false): string {
46+
export function outputSql(ast: Statement, pretty: boolean = false): string {
4447
const res = r(ast, pretty);
4548
if (pretty) {
4649
return res;
@@ -68,6 +71,14 @@ function r(node: ASTNode | Terminal, pretty: boolean = false): string {
6871
switch (node.type) {
6972
case "select":
7073
return handleSelect(node, pretty);
74+
case "insert":
75+
return handleInsert(node, pretty);
76+
case "update":
77+
return handleUpdate(node, pretty);
78+
case "values_row":
79+
return `(${rMap(node.values)})`;
80+
case "assignment":
81+
return `${node.column} = ${r(node.value)}`;
7182
case "select_from":
7283
return handleSelectFrom(node);
7384
case "join":
@@ -167,6 +178,19 @@ function handleSelect(node: SelectStatement, pretty: boolean): string {
167178
].join(joiner);
168179
}
169180

181+
function handleInsert(node: InsertStatement, pretty: boolean): string {
182+
const cols = node.columns ? ` (${node.columns.join(", ")})` : "";
183+
const joiner = pretty ? "\n" : " ";
184+
return [`INSERT INTO ${r(node.table)}${cols}`, `VALUES ${rMap(node.rows, pretty)}`].join(joiner);
185+
}
186+
187+
function handleUpdate(node: UpdateStatement, pretty: boolean): string {
188+
const joiner = pretty ? "\n" : " ";
189+
return [`UPDATE ${r(node.table)}`, `SET ${rMap(node.assignments)}`, r(node.where)]
190+
.filter(Boolean)
191+
.join(joiner);
192+
}
193+
170194
function handleDistinct(_node: Distinct): string {
171195
return "DISTINCT";
172196
}

src/parse.ts

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import type {
22
ASTNode,
33
Alias,
44
ArithOp,
5+
Assignment,
6+
InsertStatement,
7+
Statement,
8+
UpdateStatement,
9+
ValuesRow,
510
CaseExpr,
611
CaseWhen,
712
CastExpr,
@@ -55,8 +60,58 @@ import grammar, { type SQLSemantics } from "./sql.ohm-bundle";
5560
const semantics: SQLSemantics = grammar.createSemantics();
5661

5762
semantics.addOperation<ASTNode>("toAST()", {
58-
Statement(select, _semi) {
59-
return select.toAST();
63+
Statement(stmt, _semi) {
64+
return stmt.toAST();
65+
},
66+
67+
InsertStatement_withCols(_insert, _into, tableName, _open, colList, _close, _values, rows) {
68+
const tn = tableName.toAST() as TableName;
69+
return {
70+
type: "insert",
71+
table: { type: "table_ref", ...tn },
72+
columns: colList.asIteration().children.map((c) => c.toAST() as string),
73+
rows: rows.asIteration().children.map((r) => r.toAST() as ValuesRow),
74+
} satisfies InsertStatement as ASTNode;
75+
},
76+
77+
InsertStatement_noCols(_insert, _into, tableName, _values, rows) {
78+
const tn = tableName.toAST() as TableName;
79+
return {
80+
type: "insert",
81+
table: { type: "table_ref", ...tn },
82+
columns: null,
83+
rows: rows.asIteration().children.map((r) => r.toAST() as ValuesRow),
84+
} satisfies InsertStatement as ASTNode;
85+
},
86+
87+
ValuesRow(_open, list, _close) {
88+
return {
89+
type: "values_row",
90+
values: list.asIteration().children.map((v) => v.toAST() as WhereValue),
91+
} satisfies ValuesRow as ASTNode;
92+
},
93+
94+
UpdateStatement(_update, tableName, _set, assignments, whereClause) {
95+
const tn = tableName.toAST() as TableName;
96+
const whereIter = whereClause.children;
97+
const where: WhereRoot | null =
98+
whereIter.length > 0
99+
? { type: "where_root", inner: whereIter[0]!.toAST() as WhereExpr }
100+
: null;
101+
return {
102+
type: "update",
103+
table: { type: "table_ref", ...tn },
104+
assignments: assignments.asIteration().children.map((a) => a.toAST() as Assignment),
105+
where,
106+
} satisfies UpdateStatement as ASTNode;
107+
},
108+
109+
Assignment(column, _eq, value) {
110+
return {
111+
type: "assignment",
112+
column: column.toAST() as string,
113+
value: value.toAST() as WhereValue,
114+
} satisfies Assignment as ASTNode;
60115
},
61116

62117
SelectStatement(
@@ -825,7 +880,7 @@ semantics.addOperation<ASTNode>("toAST()", {
825880
},
826881
});
827882

828-
export function parseSql(expr: string): Result<SelectStatement> {
883+
export function parseStatement(expr: string): Result<Statement> {
829884
const matchResult = grammar.match(expr);
830885
if (matchResult.failed()) {
831886
const [message] = matchResult.message.split("\nExpected");
@@ -836,11 +891,20 @@ export function parseSql(expr: string): Result<SelectStatement> {
836891
);
837892
}
838893
try {
839-
return Ok(semantics(matchResult).toAST() as SelectStatement);
894+
return Ok(semantics(matchResult).toAST() as Statement);
840895
} catch (e) {
841896
if (e instanceof SanitiseError) {
842897
return Err(e);
843898
}
844899
throw e;
845900
}
846901
}
902+
903+
export function parseSql(expr: string): Result<SelectStatement> {
904+
const res = parseStatement(expr);
905+
if (!res.ok) return res;
906+
if (res.data.type !== "select") {
907+
return Err(new SanitiseError(`Expected SELECT statement, got ${res.data.type.toUpperCase()}`));
908+
}
909+
return Ok(res.data);
910+
}

src/sql.ohm

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
SQL {
2-
Statement = SelectStatement ";"?
2+
Statement
3+
= SelectStatement ";"?
4+
| InsertStatement ";"?
5+
| UpdateStatement ";"?
6+
7+
// --- INSERT ---
8+
9+
InsertStatement
10+
= insert into TableName "(" NonemptyListOf<identifier, ","> ")" values NonemptyListOf<ValuesRow, ","> -- withCols
11+
| insert into TableName values NonemptyListOf<ValuesRow, ","> -- noCols
12+
13+
ValuesRow = "(" NonemptyListOf<WhereValue, ","> ")"
14+
15+
// --- UPDATE ---
16+
17+
UpdateStatement = update TableName set NonemptyListOf<Assignment, ","> WhereClause?
18+
19+
Assignment = identifier "=" WhereValue
320

421
SelectStatement
522
= select DistinctClause? ColumnList from TableRef JoinClause* WhereClause? GroupByClause? HavingClause? OrderByClause? LimitClause? OffsetClause?
@@ -261,12 +278,18 @@ SQL {
261278
else = caseInsensitive<"else"> ~identPart
262279
end_kw = caseInsensitive<"end"> ~identPart
263280
cast = caseInsensitive<"cast"> ~identPart
281+
insert = caseInsensitive<"insert"> ~identPart
282+
into = caseInsensitive<"into"> ~identPart
283+
values = caseInsensitive<"values"> ~identPart
284+
update = caseInsensitive<"update"> ~identPart
285+
set = caseInsensitive<"set"> ~identPart
264286

265287
keyword = select | from | as | where | and | or | limit | not | null | is
266288
| join | inner | left | right | full | outer | cross | natural | on | using
267289
| order | by | asc | desc | nulls | first | last | offset | group | having | distinct
268290
| between | in | like | ilike | unknown
269291
| case | when | then | else | end_kw | cast
292+
| insert | into | values | update | set
270293
| true_kw | false_kw
271294
identPart = alnum | "_"
272295

src/sql.ohm-bundle.d.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,46 @@ import {
1313

1414
export interface SQLActionDict<T> extends BaseActionDict<T> {
1515
Statement?: (this: NonterminalNode, arg0: NonterminalNode, arg1: IterationNode) => T;
16+
InsertStatement_withCols?: (
17+
this: NonterminalNode,
18+
arg0: NonterminalNode,
19+
arg1: NonterminalNode,
20+
arg2: NonterminalNode,
21+
arg3: TerminalNode,
22+
arg4: NonterminalNode,
23+
arg5: TerminalNode,
24+
arg6: NonterminalNode,
25+
arg7: NonterminalNode,
26+
) => T;
27+
InsertStatement_noCols?: (
28+
this: NonterminalNode,
29+
arg0: NonterminalNode,
30+
arg1: NonterminalNode,
31+
arg2: NonterminalNode,
32+
arg3: NonterminalNode,
33+
arg4: NonterminalNode,
34+
) => T;
35+
InsertStatement?: (this: NonterminalNode, arg0: NonterminalNode) => T;
36+
ValuesRow?: (
37+
this: NonterminalNode,
38+
arg0: TerminalNode,
39+
arg1: NonterminalNode,
40+
arg2: TerminalNode,
41+
) => T;
42+
UpdateStatement?: (
43+
this: NonterminalNode,
44+
arg0: NonterminalNode,
45+
arg1: NonterminalNode,
46+
arg2: NonterminalNode,
47+
arg3: NonterminalNode,
48+
arg4: IterationNode,
49+
) => T;
50+
Assignment?: (
51+
this: NonterminalNode,
52+
arg0: NonterminalNode,
53+
arg1: TerminalNode,
54+
arg2: NonterminalNode,
55+
) => T;
1656
SelectStatement?: (
1757
this: NonterminalNode,
1858
arg0: NonterminalNode,
@@ -506,6 +546,11 @@ export interface SQLActionDict<T> extends BaseActionDict<T> {
506546
else?: (this: NonterminalNode, arg0: NonterminalNode) => T;
507547
end_kw?: (this: NonterminalNode, arg0: NonterminalNode) => T;
508548
cast?: (this: NonterminalNode, arg0: NonterminalNode) => T;
549+
insert?: (this: NonterminalNode, arg0: NonterminalNode) => T;
550+
into?: (this: NonterminalNode, arg0: NonterminalNode) => T;
551+
values?: (this: NonterminalNode, arg0: NonterminalNode) => T;
552+
update?: (this: NonterminalNode, arg0: NonterminalNode) => T;
553+
set?: (this: NonterminalNode, arg0: NonterminalNode) => T;
509554
keyword?: (this: NonterminalNode, arg0: NonterminalNode) => T;
510555
identPart?: (this: NonterminalNode, arg0: NonterminalNode | TerminalNode) => T;
511556
space?: (this: NonterminalNode, arg0: NonterminalNode | TerminalNode) => T;

0 commit comments

Comments
 (0)