@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.
@@ -90,7 +90,8 @@ export declare class Binder {
90
90
  functionRegistry: object;
91
91
  cteScopes: Map<string, CteInfo>;
92
92
  aggregatesFound: BE.BoundExpr[];
93
- constructor(catalog: CatalogLike, functionRegistry: object);
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): DataFrame;
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>;
@@ -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
- const name = this.expectIdent();
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 Literal(`:${paramToken.value}`, "PARAM");
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
- constructor(catalog, functionRegistry) {
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
  }
@@ -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
- const name = this.expectIdent();
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 Literal(`:${paramToken.value}`, "PARAM");
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
- constructor(catalog, functionRegistry) {
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
  }
@@ -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;
@@ -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.0",
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
- "prepack": "npm run build",
43
- "test": "vitest run",
44
- "start": "node dist/index.cli.js"
45
- },
46
- "devDependencies": {
47
- "@types/node": "^22.19.21",
48
- "@vitest/coverage-v8": "^3.2.6",
49
- "assemblyscript": "^0.27.0",
50
- "buffer": "^6.0.3",
51
- "esbuild": "^0.25.0",
52
- "typescript": "^6.0.3",
53
- "vitest": "^3.2.1"
54
- },
55
- "dependencies": {
56
- "@assemblyscript/loader": "^0.27.0",
57
- "cli-highlight": "^2.1.11",
58
- "csv-parser": "^3.2.1"
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
+ }