@crvouga/sqlite-mem 1.1.0 → 1.1.2

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/README.md CHANGED
@@ -216,7 +216,9 @@ Goal: drop-in SQL behavior vs SQLite **3.51.0**. Full matrix: [COMPATIBILITY.md]
216
216
  - FTS3/4/5 — largely implemented; shadow-table change counters intentionally diverge; some edges partial
217
217
  - `EXPLAIN` / `EXPLAIN QUERY PLAN` — stub shapes, not real bytecode
218
218
  - `INDEXED BY` / `NOT INDEXED` — parsed and discarded
219
- - Unknown statement `PRAGMA` succeeds with an empty result (SQLite-like). All oracle-exposed `pragma_*` eponymous TVFs are supported (`SELECT * FROM pragma_table_info('t')`, bare `FROM pragma_database_list`, …). Storage/journal getters return bun `:memory:`-compatible defaults.
219
+ - Unknown statement `PRAGMA` succeeds with an empty result (SQLite-like). All oracle-exposed `pragma_*` eponymous TVFs are supported (`SELECT * FROM pragma_table_info('t')`, bare `FROM pragma_database_list`, …), including **correlated** args such as `FROM table_list AS tl, pragma_table_info(tl.name) AS p` (Kysely SQLite introspector). Storage/journal getters return bun `:memory:`-compatible defaults.
220
+
221
+ **Also supported (oracle-parity):** boolean literals **`TRUE` / `FALSE`** (any case → integers `1` / `0`) and **`IS [NOT] TRUE` / `IS [NOT] FALSE`** (SQLite truthiness, including NULL). A column named `true`/`false` shadows the literal.
220
222
 
221
223
  ## Common pitfalls
222
224
 
@@ -2,7 +2,7 @@ import type { SqlValue } from "../types/value.js";
2
2
  export type BinaryOp = "+" | "-" | "*" | "/" | "%" | "||" | "=" | "==" | "!=" | "<>" | "<" | "<=" | ">" | ">=" | "AND" | "OR" | "IS" | "IS NOT" | "IS DISTINCT FROM" | "IS NOT DISTINCT FROM" | "LIKE" | "NOT LIKE" | "GLOB" | "NOT GLOB" | "IN" | "NOT IN" | "MATCH" | "->" | "->>" | "&" | "|" | "<<" | ">>";
3
3
  export type UnaryOp = "+" | "-" | "~" | "NOT";
4
4
  /** Parsed SQL expression AST (literals, operators, functions, subqueries, …). */
5
- export type Expr = LiteralExpr | NullExpr | ColumnRefExpr | UnaryExpr | BinaryExpr | BetweenExpr | InExpr | LikeExpr | FunctionExpr | AggregateExpr | WindowExpr | CaseExpr | CastExpr | ExistsExpr | SubqueryExpr | ParameterExpr | RowExpr | CollateExpr;
5
+ export type Expr = LiteralExpr | NullExpr | ColumnRefExpr | UnaryExpr | BinaryExpr | BetweenExpr | InExpr | LikeExpr | IsBoolExpr | FunctionExpr | AggregateExpr | WindowExpr | CaseExpr | CastExpr | ExistsExpr | SubqueryExpr | ParameterExpr | RowExpr | CollateExpr;
6
6
  export interface LiteralExpr {
7
7
  type: "literal";
8
8
  value: SqlValue;
@@ -28,6 +28,14 @@ export interface BinaryExpr {
28
28
  left: Expr;
29
29
  right: Expr;
30
30
  }
31
+ /** `expr IS [NOT] TRUE` / `expr IS [NOT] FALSE` (truthiness, not equality to 1/0). */
32
+ export interface IsBoolExpr {
33
+ type: "is_bool";
34
+ expr: Expr;
35
+ not: boolean;
36
+ /** `true` for TRUE, `false` for FALSE. */
37
+ sense: boolean;
38
+ }
31
39
  export interface BetweenExpr {
32
40
  type: "between";
33
41
  not: boolean;
@@ -13,6 +13,12 @@ export declare const PRAGMA_TVF_NAMES: readonly ["analysis_limit", "application_
13
13
  /** Full pragma name list for `pragma_pragma_list` (includes names without TVFs). */
14
14
  export declare const PRAGMA_LIST_NAMES: string[];
15
15
  export declare function isPragmaTvfName(name: string): boolean;
16
+ /**
17
+ * Column names for a pragma_* TVF without evaluating arguments.
18
+ * Used by SELECT shape analysis so correlated args (e.g. `tl.name`) are not
19
+ * resolved before the outer row is bound.
20
+ */
21
+ export declare function pragmaTvfColumns(name: string): string[] | null;
16
22
  /**
17
23
  * Query a pragma by name with optional SQL values (TVF args or evaluated statement args).
18
24
  * Read-only — writers stay in {@link executePragma}.
@@ -3,6 +3,11 @@ import type { ExecutionEnv, ScopeRow } from "../executor/env.js";
3
3
  import { registerTableValuedFunction, type TableValuedResult } from "./table-valued-registry.js";
4
4
  export type { TableValuedResult };
5
5
  export { registerTableValuedFunction };
6
+ /**
7
+ * Known output columns for a TVF without evaluating arguments.
8
+ * Prefer this for FROM-item shape analysis when args may be correlated.
9
+ */
10
+ export declare function tableValuedColumns(name: string): string[] | null;
6
11
  export declare function evaluateTableFunction(name: string, args: Expr[], alias: string | null, env: ExecutionEnv, scope?: ScopeRow | null, parent?: import("../expressions/context.js").EvalContext): TableValuedResult;
7
12
  export declare function listTableValuedFunctions(): string[];
8
13
  export declare function hasTableValuedFunction(name: string): boolean;
package/dist/index.js CHANGED
@@ -91,6 +91,7 @@ var KEYWORDS = {
91
91
  FOREIGN: "FOREIGN",
92
92
  FROM: "FROM",
93
93
  FULL: "FULL",
94
+ FALSE: "FALSE",
94
95
  GENERATED: "GENERATED",
95
96
  GLOB: "GLOB",
96
97
  GROUP: "GROUP",
@@ -165,6 +166,7 @@ var KEYWORDS = {
165
166
  TO: "TO",
166
167
  TRANSACTION: "TRANSACTION",
167
168
  TRIGGER: "TRIGGER",
169
+ TRUE: "TRUE",
168
170
  UNBOUNDED: "UNBOUNDED",
169
171
  UNION: "UNION",
170
172
  UNIQUE: "UNIQUE",
@@ -519,6 +521,7 @@ var IDENT_KEYWORDS = /* @__PURE__ */ new Set([
519
521
  "FOREIGN",
520
522
  "FROM",
521
523
  "FULL",
524
+ "FALSE",
522
525
  "GENERATED",
523
526
  "GLOB",
524
527
  "GROUP",
@@ -593,6 +596,7 @@ var IDENT_KEYWORDS = /* @__PURE__ */ new Set([
593
596
  "TO",
594
597
  "TRANSACTION",
595
598
  "TRIGGER",
599
+ "TRUE",
596
600
  "UNBOUNDED",
597
601
  "UNION",
598
602
  "UNIQUE",
@@ -1939,6 +1943,14 @@ var Parser = class {
1939
1943
  continue;
1940
1944
  }
1941
1945
  const not = this.match("NOT");
1946
+ if (this.match("TRUE")) {
1947
+ left = { type: "is_bool", expr: left, not, sense: true };
1948
+ continue;
1949
+ }
1950
+ if (this.match("FALSE")) {
1951
+ left = { type: "is_bool", expr: left, not, sense: false };
1952
+ continue;
1953
+ }
1942
1954
  const right2 = this.parseIsRhs();
1943
1955
  left = { type: "binary", op: not ? "IS NOT" : "IS", left, right: right2 };
1944
1956
  continue;
@@ -5527,6 +5539,8 @@ function explicitCollation(expr) {
5527
5539
  case "unary":
5528
5540
  case "cast":
5529
5541
  return explicitCollation(expr.expr);
5542
+ case "is_bool":
5543
+ return explicitCollation(expr.expr);
5530
5544
  case "binary":
5531
5545
  return explicitCollation(expr.left) ?? explicitCollation(expr.right);
5532
5546
  case "between":
@@ -5557,6 +5571,8 @@ function inheritedCollation(expr, ctx) {
5557
5571
  case "unary":
5558
5572
  case "cast":
5559
5573
  return inheritedCollation(expr.expr, ctx);
5574
+ case "is_bool":
5575
+ return inheritedCollation(expr.expr, ctx);
5560
5576
  default:
5561
5577
  return null;
5562
5578
  }
@@ -5602,6 +5618,13 @@ function evalExpr(expr, ctx) {
5602
5618
  if (expr.op === "-") return asNumber(-numberValue(value));
5603
5619
  return ~integerValue(value);
5604
5620
  }
5621
+ case "is_bool": {
5622
+ const truth = isTruthySql(evalExpr(expr.expr, ctx));
5623
+ if (!expr.not && expr.sense) return booleanValue(truth === true);
5624
+ if (!expr.not && !expr.sense) return booleanValue(truth === false);
5625
+ if (expr.not && expr.sense) return booleanValue(truth !== true);
5626
+ return booleanValue(truth !== false);
5627
+ }
5605
5628
  case "binary":
5606
5629
  return evalBinary(expr.op, expr.left, expr.right, ctx);
5607
5630
  case "between": {
@@ -9038,8 +9061,13 @@ var ExecutionEnv = class {
9038
9061
  if (table === null) return !cell.hiddenByUsing;
9039
9062
  return (cell.tableLower ?? cell.table?.toLowerCase()) === tableKey;
9040
9063
  });
9041
- if (matches.length === 0)
9064
+ if (matches.length === 0) {
9065
+ if (table === null) {
9066
+ if (key === "true") return 1;
9067
+ if (key === "false") return 0;
9068
+ }
9042
9069
  throw new SqliteError(`no such column: ${table ? `${table}.` : ""}${name}`, "no_such_column");
9070
+ }
9043
9071
  if (matches.length > 1 && table === null) throw new SqliteError(`ambiguous column name: ${name}`, "other");
9044
9072
  return matches[0].value;
9045
9073
  },
@@ -9052,8 +9080,10 @@ var ExecutionEnv = class {
9052
9080
  if (table === null) return !cell2.hiddenByUsing;
9053
9081
  return (cell2.tableLower ?? cell2.table?.toLowerCase()) === tableKey;
9054
9082
  });
9055
- if (matches.length === 0)
9083
+ if (matches.length === 0) {
9084
+ if (table === null && (key === "true" || key === "false")) return "integer";
9056
9085
  throw new SqliteError(`no such column: ${table ? `${table}.` : ""}${name}`, "no_such_column");
9086
+ }
9057
9087
  if (matches.length > 1 && table === null) throw new SqliteError(`ambiguous column name: ${name}`, "other");
9058
9088
  const cell = matches[0];
9059
9089
  return cell.affinity === "REAL" && typeof cell.value === "number" ? "real" : storageClassOf(cell.value);
@@ -9400,6 +9430,56 @@ function isPragmaTvfName(name) {
9400
9430
  }
9401
9431
  return false;
9402
9432
  }
9433
+ function pragmaTvfColumns(name) {
9434
+ const key = normalizePragmaKey(name);
9435
+ switch (key) {
9436
+ case "foreign_keys":
9437
+ return ["foreign_keys"];
9438
+ case "user_version":
9439
+ return ["user_version"];
9440
+ case "schema_version":
9441
+ return ["schema_version"];
9442
+ case "table_info":
9443
+ return ["cid", "name", "type", "notnull", "dflt_value", "pk"];
9444
+ case "table_xinfo":
9445
+ return ["cid", "name", "type", "notnull", "dflt_value", "pk", "hidden"];
9446
+ case "index_list":
9447
+ return ["seq", "name", "unique", "origin", "partial"];
9448
+ case "index_info":
9449
+ return ["seqno", "cid", "name"];
9450
+ case "index_xinfo":
9451
+ return ["seqno", "cid", "name", "desc", "coll", "key"];
9452
+ case "foreign_key_list":
9453
+ return ["id", "seq", "table", "from", "to", "on_update", "on_delete", "match"];
9454
+ case "foreign_key_check":
9455
+ return ["table", "rowid", "parent", "fkid"];
9456
+ case "database_list":
9457
+ return ["seq", "name", "file"];
9458
+ case "table_list":
9459
+ return ["schema", "name", "type", "ncol", "wr", "strict"];
9460
+ case "collation_list":
9461
+ return ["seq", "name"];
9462
+ case "compile_options":
9463
+ return ["compile_options"];
9464
+ case "function_list":
9465
+ return ["name", "builtin", "type", "enc", "narg", "flags"];
9466
+ case "module_list":
9467
+ case "pragma_list":
9468
+ return ["name"];
9469
+ case "integrity_check":
9470
+ case "quick_check":
9471
+ return [key];
9472
+ case "optimize":
9473
+ return ["optimize"];
9474
+ case "page_count":
9475
+ return ["page_count"];
9476
+ default: {
9477
+ const storage = STORAGE_DEFAULTS[key];
9478
+ if (storage) return [storage.column];
9479
+ return null;
9480
+ }
9481
+ }
9482
+ }
9403
9483
  function normalizePragmaKey(name) {
9404
9484
  const lower = name.toLowerCase();
9405
9485
  if (PRAGMA_TVF_NAMES.includes(lower)) return lower;
@@ -9823,6 +9903,13 @@ registerTableValuedFunction("json_tree", (args, alias) => {
9823
9903
  function safeInt(value) {
9824
9904
  return value <= BigInt(Number.MAX_SAFE_INTEGER) && value >= BigInt(Number.MIN_SAFE_INTEGER) ? Number(value) : value;
9825
9905
  }
9906
+ function tableValuedColumns(name) {
9907
+ ensurePragmaTvfsRegistered();
9908
+ const lower = name.toLowerCase();
9909
+ if (lower === "generate_series") return ["value"];
9910
+ if (lower === "json_each" || lower === "json_tree") return [...JSON_TVF_COLUMNS];
9911
+ return pragmaTvfColumns(name);
9912
+ }
9826
9913
  function evaluateTableFunction(name, args, alias, env, scope, parent) {
9827
9914
  ensurePragmaTvfsRegistered();
9828
9915
  const fn = getTableValuedFunction(name);
@@ -9924,6 +10011,8 @@ function exprEquals(left, right) {
9924
10011
  return right.type === "column" && (left.table ?? "").toLowerCase() === (right.table ?? "").toLowerCase() && left.name.toLowerCase() === right.name.toLowerCase();
9925
10012
  case "unary":
9926
10013
  return right.type === "unary" && left.op === right.op && exprEquals(left.expr, right.expr);
10014
+ case "is_bool":
10015
+ return right.type === "is_bool" && left.not === right.not && left.sense === right.sense && exprEquals(left.expr, right.expr);
9927
10016
  case "binary":
9928
10017
  return right.type === "binary" && left.op === right.op && exprEquals(left.left, right.left) && exprEquals(left.right, right.right);
9929
10018
  case "function":
@@ -11071,7 +11160,8 @@ function shapeOf(item, env) {
11071
11160
  if (item.type === "subquery")
11072
11161
  return resultColumnNames(item.select.columns).map((name) => ({ table: item.alias, name, value: null }));
11073
11162
  if (item.type === "table_func") {
11074
- const columns = item.name.toLowerCase() === "generate_series" ? ["value"] : item.name.toLowerCase() === "json_each" || item.name.toLowerCase() === "json_tree" ? ["key", "value", "type", "atom", "id", "parent", "fullkey", "path"] : evaluateTableFunction(item.name, item.args, item.alias, env).columns;
11163
+ const known = tableValuedColumns(item.name);
11164
+ const columns = known ?? evaluateTableFunction(item.name, item.args, item.alias, env).columns;
11075
11165
  return columns.map((name) => ({
11076
11166
  table: item.alias ?? item.name,
11077
11167
  name,
@@ -11079,7 +11169,7 @@ function shapeOf(item, env) {
11079
11169
  }));
11080
11170
  }
11081
11171
  if (item.type === "table" && isPragmaTvfName(item.name) && hasTableValuedFunction(item.name.toLowerCase())) {
11082
- const columns = evaluateTableFunction(item.name, [], item.alias, env).columns;
11172
+ const columns = tableValuedColumns(item.name) ?? evaluateTableFunction(item.name, [], item.alias, env).columns;
11083
11173
  return columns.map((name) => ({
11084
11174
  table: item.alias ?? item.name,
11085
11175
  name,
@@ -11387,6 +11477,9 @@ function validateProjectedColumns(stmt, sample, env, parent) {
11387
11477
  case "unary":
11388
11478
  recurse(expr.expr);
11389
11479
  break;
11480
+ case "is_bool":
11481
+ recurse(expr.expr);
11482
+ break;
11390
11483
  case "binary":
11391
11484
  recurse(expr.left);
11392
11485
  recurse(expr.right);
@@ -11449,6 +11542,10 @@ function expressionName(expr) {
11449
11542
  if (expr.type === "binary") {
11450
11543
  return `${expressionName(expr.left)} ${expr.op} ${expressionName(expr.right)}`;
11451
11544
  }
11545
+ if (expr.type === "is_bool") {
11546
+ const sense = expr.sense ? "TRUE" : "FALSE";
11547
+ return `${expressionName(expr.expr)} IS${expr.not ? " NOT" : ""} ${sense}`;
11548
+ }
11452
11549
  return expr.type;
11453
11550
  }
11454
11551
  function replaceSpecial(expr, evaluate) {
@@ -11457,6 +11554,8 @@ function replaceSpecial(expr, evaluate) {
11457
11554
  switch (expr.type) {
11458
11555
  case "unary":
11459
11556
  return { ...expr, expr: recurse(expr.expr) };
11557
+ case "is_bool":
11558
+ return { ...expr, expr: recurse(expr.expr) };
11460
11559
  case "binary":
11461
11560
  return { ...expr, left: recurse(expr.left), right: recurse(expr.right) };
11462
11561
  case "between":