Skip to content

Commit 02ec4ba

Browse files
committed
Fix incorrect StdStmt integration
1 parent 05797d6 commit 02ec4ba

24 files changed

+225
-57
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ LinkedQL solves **reactivity, relationships, JSON, schemas, embedding, federatio
3535
3636
> [!IMPORTANT]
3737
> 🚀 **LinkedQL is in active development and evolving daily.** Current status = **alpha**.<br>
38-
> You’re welcome to experiment, but it’s not yet suited for production workloads.
38+
> You’re welcome to experiment, but it’s not yet suited for production apps.
3939
4040
---
4141

@@ -79,9 +79,9 @@ import { FlashQL } from '@linked-db/linked-ql/flashql';
7979

8080
const client = new FlashQL();
8181

82+
await client.query(`CREATE TABLE users (id INT PRIMARY KEY, name TEXT)`);
8283
const result = await client.query(`
83-
CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT);
84-
INSERT INTO users (name) VALUES ('Ada'), ('Linus');
84+
INSERT INTO users (id, name) VALUES (1, 'Ada'), (2, 'Linus');
8585
SELECT * FROM users;
8686
`);
8787

site/docs.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ If you're totally new here, you may want to [meet LinkedQL](/overview).
88

99
> [!IMPORTANT]
1010
> 🚀 **LinkedQL is in active development and evolving daily.** Current status = **alpha**.<br>
11-
> You’re welcome to experiment, but it’s not yet suited for production workloads.
11+
> You’re welcome to experiment, but it’s not yet suited for production apps.
1212
1313
## Installation
1414

@@ -34,9 +34,9 @@ import { FlashQL } from '@linked-db/linked-ql/flashql';
3434

3535
const client = new FlashQL();
3636

37+
await client.query(`CREATE TABLE users (id INT PRIMARY KEY, name TEXT)`);
3738
const result = await client.query(`
38-
CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT);
39-
INSERT INTO users (name) VALUES ('Ada'), ('Linus');
39+
INSERT INTO users (id, name) VALUES (1, 'Ada'), (2, 'Linus');
4040
SELECT * FROM users;
4141
`);
4242

src/entry/abstracts/AbstractSQLClient.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export class AbstractSQLClient extends AbstractClient {
213213
};
214214

215215
break;
216-
216+
217217
default: throw new Error(`Invalid query input`);
218218
}
219219

@@ -270,13 +270,22 @@ export class AbstractSQLClient extends AbstractClient {
270270
// Parsing...
271271
if (!(query instanceof AbstractNode)) {
272272
query = await this.parse(query, options);
273-
} else if (!(query instanceof registry.Script) && !(query instanceof AbstractStmt)) {
273+
} else if (!(query instanceof registry.Script)
274+
&& !(query instanceof AbstractStmt)
275+
&& !(query instanceof registry.MYSetStmt)
276+
&& !(query instanceof registry.PGSetStmt)) {
274277
throw new TypeError('query must be a string or an instance of Script | AbstractStmt');
275278
}
276279
if (query instanceof registry.Script && query.length === 1) {
277280
query = query.entries()[0];
278281
}
279282

283+
// Return if query is a set statement or a standard statement
284+
if (query instanceof registry.MYSetStmt
285+
|| query instanceof registry.PGSetStmt
286+
|| query instanceof registry.StdStmt
287+
) return query;
288+
280289
// Determine by heuristics if desugaring needed
281290
if ((query instanceof registry.DDLStmt && !query.returningClause?.()) // Desugaring not applicable
282291
|| query.originSchemas?.()?.length // Desugaring already done
@@ -286,6 +295,10 @@ export class AbstractSQLClient extends AbstractClient {
286295
const relationSelector = {};
287296
let anyFound = false;
288297
query.walkTree((v, k, scope) => {
298+
if (v instanceof registry.MYSetStmt
299+
|| v instanceof registry.PGSetStmt
300+
|| v instanceof registry.StdStmt
301+
) return;
289302
if (v instanceof registry.DDLStmt
290303
&& !v.returningClause?.()) return;
291304
if (v instanceof registry.CTEItem) {

src/flashql/QueryEngine.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ export class QueryEngine extends SimpleEmitter {
8383
let returnValue;
8484
for (let stmtNode of (scriptNode instanceof registry.Script && scriptNode || [scriptNode])) {
8585
switch (stmtNode.NODE_NAME) {
86+
// CONFIG
87+
case 'PG_SET_STMT': returnValue = await this.#evaluatePG_SET_STMT(stmtNode, queryCtx); break;
8688
// DDL
8789
case 'CREATE_SCHEMA_STMT': returnValue = await this.#evaluateCREATE_SCHEMA_STMT(stmtNode, queryCtx); break;
8890
case 'ALTER_SCHEMA_STMT': returnValue = await this.#evaluateALTER_SCHEMA_STMT(stmtNode, queryCtx); break;
@@ -101,12 +103,28 @@ export class QueryEngine extends SimpleEmitter {
101103
case 'BASIC_SELECT_STMT':
102104
case 'COMPLETE_SELECT_STMT': returnValue = await this.#evaluateSELECT_STMT(stmtNode, queryCtx); break;
103105
case 'COMPOSITE_SELECT_STMT': returnValue = await this.#evaluateCOMPOSITE_SELECT_STMT(stmtNode, queryCtx); break;
106+
// STD_STMT
107+
case 'STD_STMT': console.error(`Not supported yet in the in-memory StorageEngine: ${stmtNode.stringify()}`); break;
108+
// Throw
104109
default: throw new Error(`Unknown statement type: ${stmtNode.NODE_NAME}`);
105110
}
106111
}
107112
return returnValue;
108113
}
109114

115+
// -------- CONFIG
116+
117+
async #evaluatePG_SET_STMT(stmtNode, queryCtx) {
118+
const scopeKW = stmtNode.scopeKW();
119+
const left = stmtNode.left()?.toLowerCase();
120+
const right = stmtNode.right();
121+
if (left === 'search_path') {
122+
const searchPath = right.map((r) => r.value());
123+
this.#storageEngine.setSearchPath(...searchPath);
124+
return true;
125+
}
126+
}
127+
110128
// -------- DDL
111129

112130
async #evaluateCREATE_SCHEMA_STMT(stmtNode, queryCtx) {

src/flashql/StorageEngine.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,36 @@ import { StorageNamespace } from './StorageNamespace.js';
44

55
export class StorageEngine extends SimpleEmitter {
66

7-
#defaultNamespace;
7+
#searchPath;
88
#options;
99

1010
#catalog = new Map;
1111
#init;
1212

13-
get defaultNamespace() { return this.#defaultNamespace; }
13+
get defaultNamespace() { return this.#searchPath[0]; }
1414
get options() { return this.#options; }
1515

1616
constructor({
17-
defaultNamespace = 'public',
17+
searchPath = ['public'],
1818
...options
1919
} = {}) {
2020
super();
21-
this.#defaultNamespace = defaultNamespace;
21+
this.#searchPath = [].concat(searchPath || []);
2222
this.#options = options;
2323

24-
if (defaultNamespace) {
25-
this.#init = this.createNamespace(defaultNamespace, { ifNotExists: true });
24+
if (this.defaultNamespace) {
25+
this.#init = this.createNamespace(this.defaultNamespace, { ifNotExists: true });
2626
}
2727
}
2828

29+
async setSearchPath(...searchPath) {
30+
this.#searchPath = searchPath;
31+
}
32+
33+
async getSearchPath() {
34+
return this.#searchPath.slice();
35+
}
36+
2937
async startTransaction(txIdPrefix = '~tx') {
3038
const events = new Map;
3139
const txId = `${txIdPrefix}:${(0 | Math.random() * 9e6).toString(36)}`;

src/flashql/TableStorage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export class TableStorage extends SimpleEmitter {
101101
for (const colSchema of this.#columns) {
102102
const colName = colSchema.name().value();
103103

104-
const autoIncr = colSchema.identityConstraint() || colSchema.autoIncrementConstraint();
104+
const autoIncr = colSchema.identityConstraint() || colSchema.dataType().value() === 'SERIAL' || colSchema.autoIncrementConstraint();
105105
const isPKey = this.#keyColumns.includes(colName);
106106

107107
let v = row[colName];

src/lang/SchemaInference.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { matchRelationSelector, normalizeRelationSelectorArg } from '../entry/ab
44
export class SchemaInference {
55

66
#searchPath = ['public'];
7-
get searchPath() { return this.#searchPath; }
7+
get searchPath() {
8+
return this.#searchPath;
9+
}
810

911
#driver;
1012
#queryHistory = new Map;

src/lang/Script.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ export class Script extends AbstractNodeList {
1414
'UpdateStmt',
1515
'DeleteStmt',
1616
'MYSetStmt',
17+
'PGSetStmt',
1718
'CTE',
1819
'CreateSchemaStmt',
1920
'DropSchemaStmt',
2021
'CreateTableStmt',
2122
'DropTableStmt',
23+
'StdStmt',
2224
];
2325
}
2426

@@ -30,10 +32,6 @@ export class Script extends AbstractNodeList {
3032
/* API */
3133

3234
static async parse(input, options = {}) {
33-
if (options.supportStdStmt) {
34-
const std = await StdStmt.parse(input);
35-
if (std) return new this({ entries: [std] });
36-
}
3735
const tokenStream = await this.toStream(input, options);
3836
const result = await super.parse(tokenStream, options);
3937
if (!tokenStream.done && tokenStream.current()) {
@@ -44,5 +42,13 @@ export class Script extends AbstractNodeList {
4442
return result;
4543
}
4644

45+
static async _parseFromRules(tokenStream, syntaxRules, { left, minPrecedence, trail, ...options }, resultAST = {}) {
46+
let rulesArray;
47+
if (!options.supportStdStmt && (rulesArray = [].concat(syntaxRules)).length === 1 && Array.isArray(rulesArray[0].type) && rulesArray[0].type.includes('StdStmt')) {
48+
syntaxRules = { ...rulesArray[0], type: rulesArray[0].type.filter((r) => r !== 'StdStmt') };
49+
}
50+
return super._parseFromRules(tokenStream, syntaxRules, { left, minPrecedence, trail, ...options }, resultAST);
51+
}
52+
4753
stringify(options = {}) { return `${super.stringify(options)};`; }
4854
}

src/lang/StdStmt.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { AbstractStmt } from './abstracts/AbstractStmt.js';
2+
import { TokenStream } from './TokenStream.js';
23

34
export class StdStmt extends AbstractStmt {
45

@@ -25,8 +26,37 @@ export class StdStmt extends AbstractStmt {
2526
sql() { return this._get('sql'); }
2627

2728
static async parse(sql, options = {}) {
28-
if (typeof sql !== 'string'
29-
|| /^(\((\s+)?)?(WITH|TABLE|SELECT|DELETE|INSERT|UPDATE|UPSERT|CREATE|DROP)\s+/i.test(sql.trimStart())) return;
29+
const test = (sql) => (typeof sql === 'string'
30+
&& !/^(\((\s+)?)?(WITH|TABLE|SELECT|DELETE|INSERT|UPDATE|UPSERT|CREATE|DROP|SET)\s+/i.test(sql.trimStart()));
31+
32+
if (sql instanceof TokenStream) {
33+
const _sql = [];
34+
35+
if (sql.current()) {
36+
if (!test(sql.current().value)) return;
37+
_sql.push(sql.current().value);
38+
}
39+
40+
await (async function render(tokenStream) {
41+
for await (const tok of tokenStream) {
42+
if (tok.spaceBefore) _sql.push(tok.spaceBefore);
43+
if (tok.value instanceof TokenStream) {
44+
const tag = tok.type === 'bracket_block' ? ['{', '}'] : (
45+
tok.type === 'bracket_block' ? ['[', ']'] : ['(', ')']
46+
);
47+
_sql.push(tag[0]);
48+
await render(tok.value);
49+
_sql.push(tag[1]);
50+
} else _sql.push(tok.value);
51+
if (tok.value === ';') break;
52+
}
53+
})(sql);
54+
55+
sql = _sql.join('');
56+
}
57+
58+
if (!sql || !test(sql)) return;
59+
3060
return new this({ sql }, { ...options });
3161
}
3262

src/lang/ddl/DDLStmt.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,13 @@ export class DDLStmt extends AbstractStmt {
66
/* SYNTAX RULES */
77

88
static get syntaxRules() { return { type: ['CreateSchemaStmt', 'DropSchemaStmt', 'CreateTableStmt', 'DropTableStmt'] }; }
9+
10+
/** API */
11+
12+
jsonfy({ deSugar, ...options } = {}, transformer = null, schemaInference = null) {
13+
if (this.returningClause?.()) {
14+
options = { deSugar, ...options };
15+
}
16+
return super.jsonfy(options, transformer, schemaInference);
17+
}
918
}

0 commit comments

Comments
 (0)