@slexisvn/query-engine 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/binder/binder.d.ts +3 -1
- package/dist/dataframe/dataframe.d.ts +4 -1
- package/dist/engine/query-engine.d.ts +6 -3
- package/dist/index.browser.js +65 -12
- package/dist/index.node.js +65 -12
- package/dist/parser/ast.d.ts +7 -1
- package/dist/parser/lexer.d.ts +2 -0
- package/package.json +59 -60
package/dist/binder/binder.d.ts
CHANGED
|
@@ -90,7 +90,8 @@ export declare class Binder {
|
|
|
90
90
|
functionRegistry: object;
|
|
91
91
|
cteScopes: Map<string, CteInfo>;
|
|
92
92
|
aggregatesFound: BE.BoundExpr[];
|
|
93
|
-
|
|
93
|
+
params: readonly BE.LiteralValue[];
|
|
94
|
+
constructor(catalog: CatalogLike, functionRegistry: object, params?: readonly BE.LiteralValue[]);
|
|
94
95
|
bind(ast: AST.QueryStmt): BoundQuery;
|
|
95
96
|
bindQuery(node: AST.QueryStmt, scope: BinderScope): BoundQuery;
|
|
96
97
|
bindSetOp(node: AST.SetOpNode, scope: BinderScope): BoundSetOp;
|
|
@@ -111,6 +112,7 @@ export declare class Binder {
|
|
|
111
112
|
bindWindowCall(node: AST.WindowCallNode, scope: BinderScope): BE.BoundWindowNode;
|
|
112
113
|
bindColumnRef(node: AST.ColumnRefNode, scope: BinderScope): BE.BoundColumnRefNode;
|
|
113
114
|
bindLiteral(node: AST.LiteralNode): BE.BoundLiteralNode;
|
|
115
|
+
bindParameter(node: AST.ParameterNode): BE.BoundLiteralNode;
|
|
114
116
|
bindBinaryExpr(node: AST.BinaryExprNode, scope: BinderScope): BE.BoundBinaryNode;
|
|
115
117
|
bindUnaryExpr(node: AST.UnaryExprNode, scope: BinderScope): BE.BoundUnaryNode;
|
|
116
118
|
bindAggregateCall(node: AST.AggregateCallNode, scope: BinderScope): BE.BoundAggregateNode;
|
|
@@ -42,6 +42,7 @@ interface EngineLike {
|
|
|
42
42
|
functionRegistry: FunctionRegistry;
|
|
43
43
|
sql(sqlString: string, options: {
|
|
44
44
|
frames: SqlFrameLike[];
|
|
45
|
+
params?: readonly ColumnValue[];
|
|
45
46
|
}): DataFrame;
|
|
46
47
|
_nextDfId(): number;
|
|
47
48
|
_runPlan(plan: LogicalPlanNode, outputColumns: OutputColumnLike[], streaming: boolean, cteMap: CteMap | null): Promise<ResultLike>;
|
|
@@ -62,7 +63,9 @@ export declare class DataFrame {
|
|
|
62
63
|
columns(): string[];
|
|
63
64
|
schema(): DFSchema;
|
|
64
65
|
explain(): string;
|
|
65
|
-
sql(sqlString: string
|
|
66
|
+
sql(sqlString: string, options?: {
|
|
67
|
+
params?: readonly ColumnValue[];
|
|
68
|
+
}): DataFrame;
|
|
66
69
|
select(...items: (Col | string)[]): DataFrame;
|
|
67
70
|
filter(condition: Col | string): DataFrame;
|
|
68
71
|
where(condition: Col | string): DataFrame;
|
|
@@ -8,11 +8,13 @@ import type { LogicalPlanNode } from '../planner/logical-plan.js';
|
|
|
8
8
|
import { DataFrame } from '../dataframe/dataframe.js';
|
|
9
9
|
import type { Statement, QueryStmt, CreateTableStmtNode, DropTableStmtNode } from '../parser/ast.js';
|
|
10
10
|
import type { BoundQuery, OutputColumn } from '../binder/binder.js';
|
|
11
|
+
import type { LiteralValue } from '../binder/expression-binder.js';
|
|
11
12
|
import type { Catalog } from '../catalog/catalog.js';
|
|
12
13
|
import type { ColumnSchema, ColumnValue } from '../storage/data-type.js';
|
|
13
14
|
import type { TableStatistics } from '../catalog/statistics.js';
|
|
14
15
|
import type { OptimizationPass } from '../optimizer/pass.js';
|
|
15
16
|
import type { Transport } from '../distributed/transport/transport.js';
|
|
17
|
+
export type QueryParam = LiteralValue;
|
|
16
18
|
type StatisticsMap = Map<string, TableStatistics>;
|
|
17
19
|
type DDLStmt = CreateTableStmtNode | DropTableStmtNode;
|
|
18
20
|
interface StorageBackendLike {
|
|
@@ -37,6 +39,7 @@ interface SqlFrame {
|
|
|
37
39
|
}
|
|
38
40
|
interface SqlOptions {
|
|
39
41
|
frames?: SqlFrame[];
|
|
42
|
+
params?: readonly QueryParam[];
|
|
40
43
|
}
|
|
41
44
|
interface DistributedClusterConfig {
|
|
42
45
|
nodeId?: string;
|
|
@@ -113,10 +116,10 @@ export declare class QueryEngine {
|
|
|
113
116
|
collectStatistics(): Promise<StatisticsMap | null>;
|
|
114
117
|
createOptimizer(statistics: StatisticsMap | null): Optimizer;
|
|
115
118
|
parseSQL(sql: string): Statement;
|
|
116
|
-
bind(ast: QueryStmt): BoundQuery;
|
|
119
|
+
bind(ast: QueryStmt, params?: readonly QueryParam[]): BoundQuery;
|
|
117
120
|
plan(boundQuery: BoundQuery): LogicalPlanNode;
|
|
118
121
|
optimize(logicalPlan: LogicalPlanNode): LogicalPlanNode;
|
|
119
|
-
compile(sql: string): Promise<CompileResult>;
|
|
122
|
+
compile(sql: string, params?: readonly QueryParam[]): Promise<CompileResult>;
|
|
120
123
|
optimizeCTEMap(cteMap: Map<string, LogicalPlanNode>): Map<string, LogicalPlanNode>;
|
|
121
124
|
_applyDistributedPasses(optimizer: Optimizer, passes: DistributedPassEntry[]): void;
|
|
122
125
|
_ensureStatistics(): Promise<void>;
|
|
@@ -126,7 +129,7 @@ export declare class QueryEngine {
|
|
|
126
129
|
_formatPlan(plan: LogicalPlanNode): Promise<string>;
|
|
127
130
|
_explainPlanResult(plan: LogicalPlanNode): Promise<RunRowsResult>;
|
|
128
131
|
_runPlan(plan: LogicalPlanNode, outputColumns: OutputColumn[], streaming?: boolean, cteMap?: Map<string, LogicalPlanNode> | null): Promise<QueryResult>;
|
|
129
|
-
run(sql: string): Promise<DDLResult | RunRowsResult>;
|
|
132
|
+
run(sql: string, params?: readonly QueryParam[]): Promise<DDLResult | RunRowsResult>;
|
|
130
133
|
cancel(): void;
|
|
131
134
|
executeDDL(ddl: DDLStmt): Promise<DDLResult>;
|
|
132
135
|
executeCreateTable(stmt: CreateTableStmtNode): Promise<DDLResult>;
|
package/dist/index.browser.js
CHANGED
|
@@ -2985,6 +2985,7 @@ var TokenType = /* @__PURE__ */ ((TokenType2) => {
|
|
|
2985
2985
|
TokenType2["SEMICOLON"] = "SEMICOLON";
|
|
2986
2986
|
TokenType2["CONCAT"] = "CONCAT";
|
|
2987
2987
|
TokenType2["COLON"] = "COLON";
|
|
2988
|
+
TokenType2["PLACEHOLDER"] = "PLACEHOLDER";
|
|
2988
2989
|
TokenType2["EOF"] = "EOF";
|
|
2989
2990
|
TokenType2["EXPLAIN"] = "EXPLAIN";
|
|
2990
2991
|
TokenType2["SELECT"] = "SELECT";
|
|
@@ -3100,8 +3101,10 @@ var NON_KEYWORD_TOKENS = /* @__PURE__ */ new Set([
|
|
|
3100
3101
|
"SEMICOLON",
|
|
3101
3102
|
"CONCAT",
|
|
3102
3103
|
"COLON",
|
|
3104
|
+
"PLACEHOLDER",
|
|
3103
3105
|
"EOF"
|
|
3104
3106
|
]);
|
|
3107
|
+
var PLACEHOLDER_PREFIX = "$";
|
|
3105
3108
|
var KEYWORDS = /* @__PURE__ */ new Map();
|
|
3106
3109
|
for (const key of Object.keys(TokenType)) {
|
|
3107
3110
|
if (!NON_KEYWORD_TOKENS.has(key)) {
|
|
@@ -3136,6 +3139,8 @@ var Lexer = class {
|
|
|
3136
3139
|
const ch = this.input[this.pos];
|
|
3137
3140
|
if (ch === "'") {
|
|
3138
3141
|
this.tokens.push(this._readString(start));
|
|
3142
|
+
} else if (ch === PLACEHOLDER_PREFIX) {
|
|
3143
|
+
this.tokens.push(this._readPlaceholder(start));
|
|
3139
3144
|
} else if (this._isDigit(ch)) {
|
|
3140
3145
|
this.tokens.push(this._readNumber(start));
|
|
3141
3146
|
} else if (this._isIdentStart(ch)) {
|
|
@@ -3181,6 +3186,17 @@ var Lexer = class {
|
|
|
3181
3186
|
}
|
|
3182
3187
|
throw new Error(`Unterminated string at position ${start}`);
|
|
3183
3188
|
}
|
|
3189
|
+
_readPlaceholder(start) {
|
|
3190
|
+
this.pos++;
|
|
3191
|
+
const digitsStart = this.pos;
|
|
3192
|
+
while (this.pos < this.input.length && this._isDigit(this.input[this.pos])) {
|
|
3193
|
+
this.pos++;
|
|
3194
|
+
}
|
|
3195
|
+
if (this.pos === digitsStart) {
|
|
3196
|
+
throw new Error(`Expected parameter number after '${PLACEHOLDER_PREFIX}' at position ${start}`);
|
|
3197
|
+
}
|
|
3198
|
+
return new Token("PLACEHOLDER" /* PLACEHOLDER */, this.input.slice(digitsStart, this.pos), start);
|
|
3199
|
+
}
|
|
3184
3200
|
_readNumber(start) {
|
|
3185
3201
|
while (this.pos < this.input.length && this._isDigit(this.input[this.pos])) {
|
|
3186
3202
|
this.pos++;
|
|
@@ -3327,6 +3343,9 @@ function ColumnRef(name, table = null) {
|
|
|
3327
3343
|
function Literal(value, dataType = null) {
|
|
3328
3344
|
return { kind: "Literal" /* LITERAL */, value, dataType };
|
|
3329
3345
|
}
|
|
3346
|
+
function Parameter(index) {
|
|
3347
|
+
return { kind: "Parameter" /* PARAMETER */, index };
|
|
3348
|
+
}
|
|
3330
3349
|
function BinaryExpr(op, left, right) {
|
|
3331
3350
|
return { kind: "BinaryExpr" /* BINARY_EXPR */, op, left, right };
|
|
3332
3351
|
}
|
|
@@ -3631,7 +3650,11 @@ var Parser = class {
|
|
|
3631
3650
|
this.expect("RPAREN" /* RPAREN */);
|
|
3632
3651
|
return inner;
|
|
3633
3652
|
}
|
|
3634
|
-
|
|
3653
|
+
let name = this.expectIdent();
|
|
3654
|
+
while (this.isAt("DOT" /* DOT */)) {
|
|
3655
|
+
this.advance();
|
|
3656
|
+
name = this.expectIdent();
|
|
3657
|
+
}
|
|
3635
3658
|
const alias = this.parseOptionalAlias(() => !this.isJoinKeyword() && !this.isClauseKeyword()) ?? name;
|
|
3636
3659
|
return TableRef(name, alias);
|
|
3637
3660
|
}
|
|
@@ -3909,10 +3932,14 @@ var Parser = class {
|
|
|
3909
3932
|
if (this.isAggregateKeyword(token.type)) {
|
|
3910
3933
|
return this.parseAggregateCall();
|
|
3911
3934
|
}
|
|
3935
|
+
if (token.type === "PLACEHOLDER" /* PLACEHOLDER */) {
|
|
3936
|
+
this.advance();
|
|
3937
|
+
return Parameter(Number(token.value));
|
|
3938
|
+
}
|
|
3912
3939
|
if (token.type === "COLON" /* COLON */) {
|
|
3913
3940
|
this.advance();
|
|
3914
3941
|
const paramToken = this.expect("NUMBER" /* NUMBER */);
|
|
3915
|
-
return
|
|
3942
|
+
return Parameter(Number(paramToken.value));
|
|
3916
3943
|
}
|
|
3917
3944
|
if (token.type === "IDENT" /* IDENT */) {
|
|
3918
3945
|
const name = this.advance().value;
|
|
@@ -4373,16 +4400,32 @@ var BinderScope = class _BinderScope {
|
|
|
4373
4400
|
|
|
4374
4401
|
// src/binder/binder.ts
|
|
4375
4402
|
init_expression_binder();
|
|
4403
|
+
function valueDataType(value) {
|
|
4404
|
+
switch (typeof value) {
|
|
4405
|
+
case "boolean":
|
|
4406
|
+
return "BOOLEAN" /* BOOLEAN */;
|
|
4407
|
+
case "bigint":
|
|
4408
|
+
return "INT64" /* INT64 */;
|
|
4409
|
+
case "string":
|
|
4410
|
+
return "VARCHAR" /* VARCHAR */;
|
|
4411
|
+
case "number":
|
|
4412
|
+
return Number.isInteger(value) ? "INT32" /* INT32 */ : "FLOAT64" /* FLOAT64 */;
|
|
4413
|
+
default:
|
|
4414
|
+
return null;
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4376
4417
|
var Binder = class {
|
|
4377
4418
|
catalog;
|
|
4378
4419
|
functionRegistry;
|
|
4379
4420
|
cteScopes;
|
|
4380
4421
|
aggregatesFound;
|
|
4381
|
-
|
|
4422
|
+
params;
|
|
4423
|
+
constructor(catalog, functionRegistry, params = []) {
|
|
4382
4424
|
this.catalog = catalog;
|
|
4383
4425
|
this.functionRegistry = functionRegistry;
|
|
4384
4426
|
this.cteScopes = /* @__PURE__ */ new Map();
|
|
4385
4427
|
this.aggregatesFound = [];
|
|
4428
|
+
this.params = params;
|
|
4386
4429
|
}
|
|
4387
4430
|
bind(ast) {
|
|
4388
4431
|
const scope = new BinderScope();
|
|
@@ -4647,6 +4690,8 @@ var Binder = class {
|
|
|
4647
4690
|
return this.bindColumnRef(node, scope);
|
|
4648
4691
|
case "Literal" /* LITERAL */:
|
|
4649
4692
|
return this.bindLiteral(node);
|
|
4693
|
+
case "Parameter" /* PARAMETER */:
|
|
4694
|
+
return this.bindParameter(node);
|
|
4650
4695
|
case "BinaryExpr" /* BINARY_EXPR */:
|
|
4651
4696
|
return this.bindBinaryExpr(node, scope);
|
|
4652
4697
|
case "UnaryExpr" /* UNARY_EXPR */:
|
|
@@ -4760,6 +4805,13 @@ var Binder = class {
|
|
|
4760
4805
|
}
|
|
4761
4806
|
return BoundLiteral(node.value, "VARCHAR" /* VARCHAR */);
|
|
4762
4807
|
}
|
|
4808
|
+
bindParameter(node) {
|
|
4809
|
+
if (node.index < 1 || node.index > this.params.length) {
|
|
4810
|
+
throw new Error(`Missing value for parameter $${node.index}`);
|
|
4811
|
+
}
|
|
4812
|
+
const value = this.params[node.index - 1];
|
|
4813
|
+
return BoundLiteral(value, valueDataType(value));
|
|
4814
|
+
}
|
|
4763
4815
|
bindBinaryExpr(node, scope) {
|
|
4764
4816
|
const left = this.bindExpression(node.left, scope);
|
|
4765
4817
|
const right = this.bindExpression(node.right, scope);
|
|
@@ -16938,7 +16990,7 @@ var DataFrame = class _DataFrame {
|
|
|
16938
16990
|
explain() {
|
|
16939
16991
|
return planToString(this._plan);
|
|
16940
16992
|
}
|
|
16941
|
-
sql(sqlString) {
|
|
16993
|
+
sql(sqlString, options = {}) {
|
|
16942
16994
|
const columns = this._schema.fields.map((f) => ({ name: f.name, dataType: f.dataType }));
|
|
16943
16995
|
return this._engine.sql(sqlString, {
|
|
16944
16996
|
frames: [{
|
|
@@ -16946,7 +16998,8 @@ var DataFrame = class _DataFrame {
|
|
|
16946
16998
|
columns,
|
|
16947
16999
|
plan: this._plan,
|
|
16948
17000
|
cteMap: this._cteMap
|
|
16949
|
-
}]
|
|
17001
|
+
}],
|
|
17002
|
+
params: options.params
|
|
16950
17003
|
});
|
|
16951
17004
|
}
|
|
16952
17005
|
select(...items) {
|
|
@@ -17418,8 +17471,8 @@ var QueryEngine = class {
|
|
|
17418
17471
|
parseSQL(sql) {
|
|
17419
17472
|
return parse(sql);
|
|
17420
17473
|
}
|
|
17421
|
-
bind(ast) {
|
|
17422
|
-
const binder = new Binder(this.catalog, this.functionRegistry);
|
|
17474
|
+
bind(ast, params = []) {
|
|
17475
|
+
const binder = new Binder(this.catalog, this.functionRegistry, params);
|
|
17423
17476
|
return binder.bind(ast);
|
|
17424
17477
|
}
|
|
17425
17478
|
plan(boundQuery) {
|
|
@@ -17428,7 +17481,7 @@ var QueryEngine = class {
|
|
|
17428
17481
|
optimize(logicalPlan) {
|
|
17429
17482
|
return this.optimizer.optimize(logicalPlan);
|
|
17430
17483
|
}
|
|
17431
|
-
async compile(sql) {
|
|
17484
|
+
async compile(sql, params = []) {
|
|
17432
17485
|
const ast = this.parseSQL(sql);
|
|
17433
17486
|
let isExplain = false;
|
|
17434
17487
|
let isAnalyze = false;
|
|
@@ -17443,7 +17496,7 @@ var QueryEngine = class {
|
|
|
17443
17496
|
} else if (ast.kind === "CreateTableStmt" || ast.kind === "DropTableStmt") {
|
|
17444
17497
|
return { ddl: ast };
|
|
17445
17498
|
}
|
|
17446
|
-
const bound = this.bind(targetAst);
|
|
17499
|
+
const bound = this.bind(targetAst, params);
|
|
17447
17500
|
const logicalPlan = this.plan(bound);
|
|
17448
17501
|
let cteMap = logicalPlan._cteMap || /* @__PURE__ */ new Map();
|
|
17449
17502
|
await this._ensureStatistics();
|
|
@@ -17498,7 +17551,7 @@ var QueryEngine = class {
|
|
|
17498
17551
|
if (ast.kind === "CreateTableStmt" || ast.kind === "DropTableStmt" || ast.kind === "ExplainStmt" || ast.kind === "ExplainAnalyzeStmt") {
|
|
17499
17552
|
throw new Error("sql() supports query statements only");
|
|
17500
17553
|
}
|
|
17501
|
-
const binder = new Binder(this.catalog, this.functionRegistry);
|
|
17554
|
+
const binder = new Binder(this.catalog, this.functionRegistry, options.params);
|
|
17502
17555
|
for (const frame of options.frames || []) {
|
|
17503
17556
|
binder.cteScopes.set(frame.name.toUpperCase(), {
|
|
17504
17557
|
name: frame.name,
|
|
@@ -17537,8 +17590,8 @@ var QueryEngine = class {
|
|
|
17537
17590
|
const { sink, columnNames } = await this.executor.execute(optimized, outputColumns, streaming);
|
|
17538
17591
|
return new QueryResult(columnNames, sink);
|
|
17539
17592
|
}
|
|
17540
|
-
async run(sql) {
|
|
17541
|
-
const compiled = await this.compile(sql);
|
|
17593
|
+
async run(sql, params = []) {
|
|
17594
|
+
const compiled = await this.compile(sql, params);
|
|
17542
17595
|
if (compiled.ddl) {
|
|
17543
17596
|
return this.executeDDL(compiled.ddl);
|
|
17544
17597
|
}
|
package/dist/index.node.js
CHANGED
|
@@ -12524,6 +12524,7 @@ var TokenType = /* @__PURE__ */ ((TokenType2) => {
|
|
|
12524
12524
|
TokenType2["SEMICOLON"] = "SEMICOLON";
|
|
12525
12525
|
TokenType2["CONCAT"] = "CONCAT";
|
|
12526
12526
|
TokenType2["COLON"] = "COLON";
|
|
12527
|
+
TokenType2["PLACEHOLDER"] = "PLACEHOLDER";
|
|
12527
12528
|
TokenType2["EOF"] = "EOF";
|
|
12528
12529
|
TokenType2["EXPLAIN"] = "EXPLAIN";
|
|
12529
12530
|
TokenType2["SELECT"] = "SELECT";
|
|
@@ -12639,8 +12640,10 @@ var NON_KEYWORD_TOKENS = /* @__PURE__ */ new Set([
|
|
|
12639
12640
|
"SEMICOLON",
|
|
12640
12641
|
"CONCAT",
|
|
12641
12642
|
"COLON",
|
|
12643
|
+
"PLACEHOLDER",
|
|
12642
12644
|
"EOF"
|
|
12643
12645
|
]);
|
|
12646
|
+
var PLACEHOLDER_PREFIX = "$";
|
|
12644
12647
|
var KEYWORDS = /* @__PURE__ */ new Map();
|
|
12645
12648
|
for (const key of Object.keys(TokenType)) {
|
|
12646
12649
|
if (!NON_KEYWORD_TOKENS.has(key)) {
|
|
@@ -12675,6 +12678,8 @@ var Lexer = class {
|
|
|
12675
12678
|
const ch = this.input[this.pos];
|
|
12676
12679
|
if (ch === "'") {
|
|
12677
12680
|
this.tokens.push(this._readString(start));
|
|
12681
|
+
} else if (ch === PLACEHOLDER_PREFIX) {
|
|
12682
|
+
this.tokens.push(this._readPlaceholder(start));
|
|
12678
12683
|
} else if (this._isDigit(ch)) {
|
|
12679
12684
|
this.tokens.push(this._readNumber(start));
|
|
12680
12685
|
} else if (this._isIdentStart(ch)) {
|
|
@@ -12720,6 +12725,17 @@ var Lexer = class {
|
|
|
12720
12725
|
}
|
|
12721
12726
|
throw new Error(`Unterminated string at position ${start}`);
|
|
12722
12727
|
}
|
|
12728
|
+
_readPlaceholder(start) {
|
|
12729
|
+
this.pos++;
|
|
12730
|
+
const digitsStart = this.pos;
|
|
12731
|
+
while (this.pos < this.input.length && this._isDigit(this.input[this.pos])) {
|
|
12732
|
+
this.pos++;
|
|
12733
|
+
}
|
|
12734
|
+
if (this.pos === digitsStart) {
|
|
12735
|
+
throw new Error(`Expected parameter number after '${PLACEHOLDER_PREFIX}' at position ${start}`);
|
|
12736
|
+
}
|
|
12737
|
+
return new Token("PLACEHOLDER" /* PLACEHOLDER */, this.input.slice(digitsStart, this.pos), start);
|
|
12738
|
+
}
|
|
12723
12739
|
_readNumber(start) {
|
|
12724
12740
|
while (this.pos < this.input.length && this._isDigit(this.input[this.pos])) {
|
|
12725
12741
|
this.pos++;
|
|
@@ -12865,6 +12881,9 @@ function ColumnRef(name, table = null) {
|
|
|
12865
12881
|
function Literal(value, dataType = null) {
|
|
12866
12882
|
return { kind: "Literal" /* LITERAL */, value, dataType };
|
|
12867
12883
|
}
|
|
12884
|
+
function Parameter(index) {
|
|
12885
|
+
return { kind: "Parameter" /* PARAMETER */, index };
|
|
12886
|
+
}
|
|
12868
12887
|
function BinaryExpr(op, left, right) {
|
|
12869
12888
|
return { kind: "BinaryExpr" /* BINARY_EXPR */, op, left, right };
|
|
12870
12889
|
}
|
|
@@ -13169,7 +13188,11 @@ var Parser = class {
|
|
|
13169
13188
|
this.expect("RPAREN" /* RPAREN */);
|
|
13170
13189
|
return inner;
|
|
13171
13190
|
}
|
|
13172
|
-
|
|
13191
|
+
let name = this.expectIdent();
|
|
13192
|
+
while (this.isAt("DOT" /* DOT */)) {
|
|
13193
|
+
this.advance();
|
|
13194
|
+
name = this.expectIdent();
|
|
13195
|
+
}
|
|
13173
13196
|
const alias = this.parseOptionalAlias(() => !this.isJoinKeyword() && !this.isClauseKeyword()) ?? name;
|
|
13174
13197
|
return TableRef(name, alias);
|
|
13175
13198
|
}
|
|
@@ -13447,10 +13470,14 @@ var Parser = class {
|
|
|
13447
13470
|
if (this.isAggregateKeyword(token.type)) {
|
|
13448
13471
|
return this.parseAggregateCall();
|
|
13449
13472
|
}
|
|
13473
|
+
if (token.type === "PLACEHOLDER" /* PLACEHOLDER */) {
|
|
13474
|
+
this.advance();
|
|
13475
|
+
return Parameter(Number(token.value));
|
|
13476
|
+
}
|
|
13450
13477
|
if (token.type === "COLON" /* COLON */) {
|
|
13451
13478
|
this.advance();
|
|
13452
13479
|
const paramToken = this.expect("NUMBER" /* NUMBER */);
|
|
13453
|
-
return
|
|
13480
|
+
return Parameter(Number(paramToken.value));
|
|
13454
13481
|
}
|
|
13455
13482
|
if (token.type === "IDENT" /* IDENT */) {
|
|
13456
13483
|
const name = this.advance().value;
|
|
@@ -13909,16 +13936,32 @@ var BinderScope = class _BinderScope {
|
|
|
13909
13936
|
|
|
13910
13937
|
// src/binder/binder.ts
|
|
13911
13938
|
init_expression_binder();
|
|
13939
|
+
function valueDataType(value) {
|
|
13940
|
+
switch (typeof value) {
|
|
13941
|
+
case "boolean":
|
|
13942
|
+
return "BOOLEAN" /* BOOLEAN */;
|
|
13943
|
+
case "bigint":
|
|
13944
|
+
return "INT64" /* INT64 */;
|
|
13945
|
+
case "string":
|
|
13946
|
+
return "VARCHAR" /* VARCHAR */;
|
|
13947
|
+
case "number":
|
|
13948
|
+
return Number.isInteger(value) ? "INT32" /* INT32 */ : "FLOAT64" /* FLOAT64 */;
|
|
13949
|
+
default:
|
|
13950
|
+
return null;
|
|
13951
|
+
}
|
|
13952
|
+
}
|
|
13912
13953
|
var Binder = class {
|
|
13913
13954
|
catalog;
|
|
13914
13955
|
functionRegistry;
|
|
13915
13956
|
cteScopes;
|
|
13916
13957
|
aggregatesFound;
|
|
13917
|
-
|
|
13958
|
+
params;
|
|
13959
|
+
constructor(catalog, functionRegistry, params = []) {
|
|
13918
13960
|
this.catalog = catalog;
|
|
13919
13961
|
this.functionRegistry = functionRegistry;
|
|
13920
13962
|
this.cteScopes = /* @__PURE__ */ new Map();
|
|
13921
13963
|
this.aggregatesFound = [];
|
|
13964
|
+
this.params = params;
|
|
13922
13965
|
}
|
|
13923
13966
|
bind(ast) {
|
|
13924
13967
|
const scope = new BinderScope();
|
|
@@ -14183,6 +14226,8 @@ var Binder = class {
|
|
|
14183
14226
|
return this.bindColumnRef(node, scope);
|
|
14184
14227
|
case "Literal" /* LITERAL */:
|
|
14185
14228
|
return this.bindLiteral(node);
|
|
14229
|
+
case "Parameter" /* PARAMETER */:
|
|
14230
|
+
return this.bindParameter(node);
|
|
14186
14231
|
case "BinaryExpr" /* BINARY_EXPR */:
|
|
14187
14232
|
return this.bindBinaryExpr(node, scope);
|
|
14188
14233
|
case "UnaryExpr" /* UNARY_EXPR */:
|
|
@@ -14296,6 +14341,13 @@ var Binder = class {
|
|
|
14296
14341
|
}
|
|
14297
14342
|
return BoundLiteral(node.value, "VARCHAR" /* VARCHAR */);
|
|
14298
14343
|
}
|
|
14344
|
+
bindParameter(node) {
|
|
14345
|
+
if (node.index < 1 || node.index > this.params.length) {
|
|
14346
|
+
throw new Error(`Missing value for parameter $${node.index}`);
|
|
14347
|
+
}
|
|
14348
|
+
const value = this.params[node.index - 1];
|
|
14349
|
+
return BoundLiteral(value, valueDataType(value));
|
|
14350
|
+
}
|
|
14299
14351
|
bindBinaryExpr(node, scope) {
|
|
14300
14352
|
const left = this.bindExpression(node.left, scope);
|
|
14301
14353
|
const right = this.bindExpression(node.right, scope);
|
|
@@ -20174,7 +20226,7 @@ var DataFrame = class _DataFrame {
|
|
|
20174
20226
|
explain() {
|
|
20175
20227
|
return planToString(this._plan);
|
|
20176
20228
|
}
|
|
20177
|
-
sql(sqlString) {
|
|
20229
|
+
sql(sqlString, options = {}) {
|
|
20178
20230
|
const columns = this._schema.fields.map((f) => ({ name: f.name, dataType: f.dataType }));
|
|
20179
20231
|
return this._engine.sql(sqlString, {
|
|
20180
20232
|
frames: [{
|
|
@@ -20182,7 +20234,8 @@ var DataFrame = class _DataFrame {
|
|
|
20182
20234
|
columns,
|
|
20183
20235
|
plan: this._plan,
|
|
20184
20236
|
cteMap: this._cteMap
|
|
20185
|
-
}]
|
|
20237
|
+
}],
|
|
20238
|
+
params: options.params
|
|
20186
20239
|
});
|
|
20187
20240
|
}
|
|
20188
20241
|
select(...items) {
|
|
@@ -20654,8 +20707,8 @@ var QueryEngine = class {
|
|
|
20654
20707
|
parseSQL(sql) {
|
|
20655
20708
|
return parse(sql);
|
|
20656
20709
|
}
|
|
20657
|
-
bind(ast) {
|
|
20658
|
-
const binder = new Binder(this.catalog, this.functionRegistry);
|
|
20710
|
+
bind(ast, params = []) {
|
|
20711
|
+
const binder = new Binder(this.catalog, this.functionRegistry, params);
|
|
20659
20712
|
return binder.bind(ast);
|
|
20660
20713
|
}
|
|
20661
20714
|
plan(boundQuery) {
|
|
@@ -20664,7 +20717,7 @@ var QueryEngine = class {
|
|
|
20664
20717
|
optimize(logicalPlan) {
|
|
20665
20718
|
return this.optimizer.optimize(logicalPlan);
|
|
20666
20719
|
}
|
|
20667
|
-
async compile(sql) {
|
|
20720
|
+
async compile(sql, params = []) {
|
|
20668
20721
|
const ast = this.parseSQL(sql);
|
|
20669
20722
|
let isExplain = false;
|
|
20670
20723
|
let isAnalyze = false;
|
|
@@ -20679,7 +20732,7 @@ var QueryEngine = class {
|
|
|
20679
20732
|
} else if (ast.kind === "CreateTableStmt" || ast.kind === "DropTableStmt") {
|
|
20680
20733
|
return { ddl: ast };
|
|
20681
20734
|
}
|
|
20682
|
-
const bound = this.bind(targetAst);
|
|
20735
|
+
const bound = this.bind(targetAst, params);
|
|
20683
20736
|
const logicalPlan = this.plan(bound);
|
|
20684
20737
|
let cteMap = logicalPlan._cteMap || /* @__PURE__ */ new Map();
|
|
20685
20738
|
await this._ensureStatistics();
|
|
@@ -20734,7 +20787,7 @@ var QueryEngine = class {
|
|
|
20734
20787
|
if (ast.kind === "CreateTableStmt" || ast.kind === "DropTableStmt" || ast.kind === "ExplainStmt" || ast.kind === "ExplainAnalyzeStmt") {
|
|
20735
20788
|
throw new Error("sql() supports query statements only");
|
|
20736
20789
|
}
|
|
20737
|
-
const binder = new Binder(this.catalog, this.functionRegistry);
|
|
20790
|
+
const binder = new Binder(this.catalog, this.functionRegistry, options.params);
|
|
20738
20791
|
for (const frame of options.frames || []) {
|
|
20739
20792
|
binder.cteScopes.set(frame.name.toUpperCase(), {
|
|
20740
20793
|
name: frame.name,
|
|
@@ -20773,8 +20826,8 @@ var QueryEngine = class {
|
|
|
20773
20826
|
const { sink, columnNames } = await this.executor.execute(optimized, outputColumns, streaming);
|
|
20774
20827
|
return new QueryResult(columnNames, sink);
|
|
20775
20828
|
}
|
|
20776
|
-
async run(sql) {
|
|
20777
|
-
const compiled = await this.compile(sql);
|
|
20829
|
+
async run(sql, params = []) {
|
|
20830
|
+
const compiled = await this.compile(sql, params);
|
|
20778
20831
|
if (compiled.ddl) {
|
|
20779
20832
|
return this.executeDDL(compiled.ddl);
|
|
20780
20833
|
}
|
package/dist/parser/ast.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare enum NodeKind {
|
|
|
11
11
|
SUBQUERY_REF = "SubqueryRef",
|
|
12
12
|
COLUMN_REF = "ColumnRef",
|
|
13
13
|
LITERAL = "Literal",
|
|
14
|
+
PARAMETER = "Parameter",
|
|
14
15
|
BINARY_EXPR = "BinaryExpr",
|
|
15
16
|
UNARY_EXPR = "UnaryExpr",
|
|
16
17
|
BETWEEN_EXPR = "BetweenExpr",
|
|
@@ -36,7 +37,7 @@ export declare enum NodeKind {
|
|
|
36
37
|
COLUMN_DEF = "ColumnDef",
|
|
37
38
|
EXPLAIN_ANALYZE_STMT = "ExplainAnalyzeStmt"
|
|
38
39
|
}
|
|
39
|
-
export type Expr = ColumnRefNode | LiteralNode | BinaryExprNode | UnaryExprNode | BetweenExprNode | InExprNode | LikeExprNode | IsNullExprNode | ExistsExprNode | SubqueryExprNode | FunctionCallNode | AggregateCallNode | CaseExprNode | CastExprNode | ExtractExprNode | SubstringExprNode | IntervalExprNode | WindowCallNode | AllColumnsNode;
|
|
40
|
+
export type Expr = ColumnRefNode | LiteralNode | ParameterNode | BinaryExprNode | UnaryExprNode | BetweenExprNode | InExprNode | LikeExprNode | IsNullExprNode | ExistsExprNode | SubqueryExprNode | FunctionCallNode | AggregateCallNode | CaseExprNode | CastExprNode | ExtractExprNode | SubstringExprNode | IntervalExprNode | WindowCallNode | AllColumnsNode;
|
|
40
41
|
export type FromItem = TableRefNode | JoinRefNode | SubqueryRefNode;
|
|
41
42
|
export type QueryStmt = SelectStmtNode | SetOpNode;
|
|
42
43
|
export type Statement = QueryStmt | ExplainStmtNode | ExplainAnalyzeStmtNode | CreateTableStmtNode | DropTableStmtNode;
|
|
@@ -113,6 +114,10 @@ export interface LiteralNode {
|
|
|
113
114
|
value: string | number | boolean | null;
|
|
114
115
|
dataType: string | null;
|
|
115
116
|
}
|
|
117
|
+
export interface ParameterNode {
|
|
118
|
+
kind: NodeKind.PARAMETER;
|
|
119
|
+
index: number;
|
|
120
|
+
}
|
|
116
121
|
export interface BinaryExprNode {
|
|
117
122
|
kind: NodeKind.BINARY_EXPR;
|
|
118
123
|
op: string;
|
|
@@ -272,6 +277,7 @@ export declare function JoinRef(left: FromItem, right: FromItem, joinType: strin
|
|
|
272
277
|
export declare function SubqueryRef(query: QueryStmt, alias: string | null): SubqueryRefNode;
|
|
273
278
|
export declare function ColumnRef(name: string, table?: string | null): ColumnRefNode;
|
|
274
279
|
export declare function Literal(value: string | number | boolean | null, dataType?: string | null): LiteralNode;
|
|
280
|
+
export declare function Parameter(index: number): ParameterNode;
|
|
275
281
|
export declare function BinaryExpr(op: string, left: Expr, right: Expr | QuantifiedSubqueryNode): BinaryExprNode;
|
|
276
282
|
export declare function UnaryExpr(op: string, operand: Expr): UnaryExprNode;
|
|
277
283
|
export declare function BetweenExpr(expr: Expr, low: Expr, high: Expr, negated?: boolean): BetweenExprNode;
|
package/dist/parser/lexer.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export declare enum TokenType {
|
|
|
20
20
|
SEMICOLON = "SEMICOLON",
|
|
21
21
|
CONCAT = "CONCAT",
|
|
22
22
|
COLON = "COLON",
|
|
23
|
+
PLACEHOLDER = "PLACEHOLDER",
|
|
23
24
|
EOF = "EOF",
|
|
24
25
|
EXPLAIN = "EXPLAIN",
|
|
25
26
|
SELECT = "SELECT",
|
|
@@ -126,6 +127,7 @@ export declare class Lexer {
|
|
|
126
127
|
_tokenize(): void;
|
|
127
128
|
_skipWhitespaceAndComments(): void;
|
|
128
129
|
_readString(start: number): Token;
|
|
130
|
+
_readPlaceholder(start: number): Token;
|
|
129
131
|
_readNumber(start: number): Token;
|
|
130
132
|
_readIdentOrKeyword(start: number): Token;
|
|
131
133
|
_readSymbol(start: number): Token;
|
package/package.json
CHANGED
|
@@ -1,60 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@slexisvn/query-engine",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"main": "./dist/index.node.js",
|
|
6
|
-
"module": "./dist/index.node.js",
|
|
7
|
-
"browser": "./dist/index.browser.js",
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
9
|
-
"bin": {
|
|
10
|
-
"qe": "./dist/index.cli.js"
|
|
11
|
-
},
|
|
12
|
-
"exports": {
|
|
13
|
-
".": {
|
|
14
|
-
"types": "./dist/index.d.ts",
|
|
15
|
-
"browser": "./dist/index.browser.js",
|
|
16
|
-
"import": "./dist/index.node.js",
|
|
17
|
-
"default": "./dist/index.node.js"
|
|
18
|
-
},
|
|
19
|
-
"./browser": {
|
|
20
|
-
"types": "./dist/browser.d.ts",
|
|
21
|
-
"import": "./dist/index.browser.js",
|
|
22
|
-
"default": "./dist/index.browser.js"
|
|
23
|
-
},
|
|
24
|
-
"./cli": "./dist/index.cli.js",
|
|
25
|
-
"./core.wasm": "./dist/core.wasm",
|
|
26
|
-
"./package.json": "./package.json"
|
|
27
|
-
},
|
|
28
|
-
"files": [
|
|
29
|
-
"dist/index.node.js",
|
|
30
|
-
"dist/index.browser.js",
|
|
31
|
-
"dist/index.cli.js",
|
|
32
|
-
"dist/core.wasm",
|
|
33
|
-
"dist/**/*.d.ts"
|
|
34
|
-
],
|
|
35
|
-
"publishConfig": {
|
|
36
|
-
"access": "public"
|
|
37
|
-
},
|
|
38
|
-
"scripts": {
|
|
39
|
-
"typecheck": "tsc --noEmit",
|
|
40
|
-
"build": "node scripts/build.js && npm run build:wasm && tsc -p tsconfig.build.json",
|
|
41
|
-
"build:wasm": "asc --config src/wasm/assembly/asconfig.json --target release",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"@
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@slexisvn/query-engine",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.node.js",
|
|
6
|
+
"module": "./dist/index.node.js",
|
|
7
|
+
"browser": "./dist/index.browser.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"qe": "./dist/index.cli.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"browser": "./dist/index.browser.js",
|
|
16
|
+
"import": "./dist/index.node.js",
|
|
17
|
+
"default": "./dist/index.node.js"
|
|
18
|
+
},
|
|
19
|
+
"./browser": {
|
|
20
|
+
"types": "./dist/browser.d.ts",
|
|
21
|
+
"import": "./dist/index.browser.js",
|
|
22
|
+
"default": "./dist/index.browser.js"
|
|
23
|
+
},
|
|
24
|
+
"./cli": "./dist/index.cli.js",
|
|
25
|
+
"./core.wasm": "./dist/core.wasm",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist/index.node.js",
|
|
30
|
+
"dist/index.browser.js",
|
|
31
|
+
"dist/index.cli.js",
|
|
32
|
+
"dist/core.wasm",
|
|
33
|
+
"dist/**/*.d.ts"
|
|
34
|
+
],
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"build": "node scripts/build.js && npm run build:wasm && tsc -p tsconfig.build.json",
|
|
41
|
+
"build:wasm": "asc --config src/wasm/assembly/asconfig.json --target release",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"start": "node dist/index.cli.js"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^22.19.21",
|
|
47
|
+
"@vitest/coverage-v8": "^3.2.6",
|
|
48
|
+
"assemblyscript": "^0.27.0",
|
|
49
|
+
"buffer": "^6.0.3",
|
|
50
|
+
"esbuild": "^0.25.0",
|
|
51
|
+
"typescript": "^6.0.3",
|
|
52
|
+
"vitest": "^3.2.1"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@assemblyscript/loader": "^0.27.0",
|
|
56
|
+
"cli-highlight": "^2.1.11",
|
|
57
|
+
"csv-parser": "^3.2.1"
|
|
58
|
+
}
|
|
59
|
+
}
|