@quereus/quereus 0.4.11 → 0.5.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.
Files changed (128) hide show
  1. package/README.md +40 -16
  2. package/dist/src/core/database.d.ts +18 -0
  3. package/dist/src/core/database.d.ts.map +1 -1
  4. package/dist/src/core/database.js +22 -0
  5. package/dist/src/core/database.js.map +1 -1
  6. package/dist/src/func/builtins/conversion.d.ts +51 -0
  7. package/dist/src/func/builtins/conversion.d.ts.map +1 -0
  8. package/dist/src/func/builtins/conversion.js +152 -0
  9. package/dist/src/func/builtins/conversion.js.map +1 -0
  10. package/dist/src/func/builtins/index.d.ts.map +1 -1
  11. package/dist/src/func/builtins/index.js +20 -1
  12. package/dist/src/func/builtins/index.js.map +1 -1
  13. package/dist/src/func/builtins/json.d.ts +1 -0
  14. package/dist/src/func/builtins/json.d.ts.map +1 -1
  15. package/dist/src/func/builtins/json.js +100 -0
  16. package/dist/src/func/builtins/json.js.map +1 -1
  17. package/dist/src/func/builtins/schema.js +1 -1
  18. package/dist/src/func/builtins/schema.js.map +1 -1
  19. package/dist/src/index.d.ts +8 -1
  20. package/dist/src/index.d.ts.map +1 -1
  21. package/dist/src/index.js +6 -0
  22. package/dist/src/index.js.map +1 -1
  23. package/dist/src/runtime/emit/binary.d.ts.map +1 -1
  24. package/dist/src/runtime/emit/binary.js +6 -2
  25. package/dist/src/runtime/emit/binary.js.map +1 -1
  26. package/dist/src/runtime/emit/constraint-check.d.ts.map +1 -1
  27. package/dist/src/runtime/emit/constraint-check.js +8 -1
  28. package/dist/src/runtime/emit/constraint-check.js.map +1 -1
  29. package/dist/src/runtime/emit/insert.d.ts.map +1 -1
  30. package/dist/src/runtime/emit/insert.js +4 -24
  31. package/dist/src/runtime/emit/insert.js.map +1 -1
  32. package/dist/src/runtime/emit/scalar-function.d.ts +9 -0
  33. package/dist/src/runtime/emit/scalar-function.d.ts.map +1 -1
  34. package/dist/src/runtime/emit/scalar-function.js +23 -1
  35. package/dist/src/runtime/emit/scalar-function.js.map +1 -1
  36. package/dist/src/runtime/emit/unary.d.ts.map +1 -1
  37. package/dist/src/runtime/emit/unary.js +3 -1
  38. package/dist/src/runtime/emit/unary.js.map +1 -1
  39. package/dist/src/schema/column.d.ts +4 -1
  40. package/dist/src/schema/column.d.ts.map +1 -1
  41. package/dist/src/schema/column.js +3 -0
  42. package/dist/src/schema/column.js.map +1 -1
  43. package/dist/src/schema/function.d.ts +15 -0
  44. package/dist/src/schema/function.d.ts.map +1 -1
  45. package/dist/src/schema/function.js.map +1 -1
  46. package/dist/src/schema/table.d.ts.map +1 -1
  47. package/dist/src/schema/table.js +10 -1
  48. package/dist/src/schema/table.js.map +1 -1
  49. package/dist/src/types/builtin-types.d.ts +37 -0
  50. package/dist/src/types/builtin-types.d.ts.map +1 -0
  51. package/dist/src/types/builtin-types.js +359 -0
  52. package/dist/src/types/builtin-types.js.map +1 -0
  53. package/dist/src/types/index.d.ts +7 -0
  54. package/dist/src/types/index.d.ts.map +1 -0
  55. package/dist/src/types/index.js +13 -0
  56. package/dist/src/types/index.js.map +1 -0
  57. package/dist/src/types/json-type.d.ts +8 -0
  58. package/dist/src/types/json-type.d.ts.map +1 -0
  59. package/dist/src/types/json-type.js +143 -0
  60. package/dist/src/types/json-type.js.map +1 -0
  61. package/dist/src/types/logical-type.d.ts +52 -0
  62. package/dist/src/types/logical-type.d.ts.map +1 -0
  63. package/dist/src/types/logical-type.js +34 -0
  64. package/dist/src/types/logical-type.js.map +1 -0
  65. package/dist/src/types/plugin-interface.d.ts +9 -0
  66. package/dist/src/types/plugin-interface.d.ts.map +1 -0
  67. package/dist/src/types/plugin-interface.js +2 -0
  68. package/dist/src/types/plugin-interface.js.map +1 -0
  69. package/dist/src/types/registry.d.ts +72 -0
  70. package/dist/src/types/registry.d.ts.map +1 -0
  71. package/dist/src/types/registry.js +168 -0
  72. package/dist/src/types/registry.js.map +1 -0
  73. package/dist/src/types/temporal-types.d.ts +17 -0
  74. package/dist/src/types/temporal-types.d.ts.map +1 -0
  75. package/dist/src/types/temporal-types.js +178 -0
  76. package/dist/src/types/temporal-types.js.map +1 -0
  77. package/dist/src/types/validation.d.ts +52 -0
  78. package/dist/src/types/validation.d.ts.map +1 -0
  79. package/dist/src/types/validation.js +96 -0
  80. package/dist/src/types/validation.js.map +1 -0
  81. package/dist/src/util/comparison.d.ts +24 -5
  82. package/dist/src/util/comparison.d.ts.map +1 -1
  83. package/dist/src/util/comparison.js +71 -9
  84. package/dist/src/util/comparison.js.map +1 -1
  85. package/dist/src/util/plugin-loader.d.ts.map +1 -1
  86. package/dist/src/util/plugin-loader.js +17 -0
  87. package/dist/src/util/plugin-loader.js.map +1 -1
  88. package/dist/src/vtab/manifest.d.ts +4 -0
  89. package/dist/src/vtab/manifest.d.ts.map +1 -1
  90. package/dist/src/vtab/memory/index.d.ts +1 -0
  91. package/dist/src/vtab/memory/index.d.ts.map +1 -1
  92. package/dist/src/vtab/memory/index.js +15 -4
  93. package/dist/src/vtab/memory/index.js.map +1 -1
  94. package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
  95. package/dist/src/vtab/memory/layer/manager.js +20 -2
  96. package/dist/src/vtab/memory/layer/manager.js.map +1 -1
  97. package/dist/src/vtab/memory/utils/primary-key.d.ts.map +1 -1
  98. package/dist/src/vtab/memory/utils/primary-key.js +17 -12
  99. package/dist/src/vtab/memory/utils/primary-key.js.map +1 -1
  100. package/package.json +5 -4
  101. package/src/core/database.ts +24 -0
  102. package/src/func/builtins/conversion.ts +201 -0
  103. package/src/func/builtins/index.ts +20 -1
  104. package/src/func/builtins/json.ts +121 -0
  105. package/src/func/builtins/schema.ts +1 -1
  106. package/src/index.ts +35 -0
  107. package/src/runtime/emit/binary.ts +8 -2
  108. package/src/runtime/emit/constraint-check.ts +9 -1
  109. package/src/runtime/emit/insert.ts +4 -16
  110. package/src/runtime/emit/scalar-function.ts +27 -1
  111. package/src/runtime/emit/unary.ts +4 -1
  112. package/src/schema/column.ts +8 -1
  113. package/src/schema/function.ts +18 -0
  114. package/src/schema/table.ts +411 -398
  115. package/src/types/builtin-types.ts +350 -0
  116. package/src/types/index.ts +17 -0
  117. package/src/types/json-type.ts +152 -0
  118. package/src/types/logical-type.ts +75 -0
  119. package/src/types/plugin-interface.ts +10 -0
  120. package/src/types/registry.ts +200 -0
  121. package/src/types/temporal-types.ts +167 -0
  122. package/src/types/validation.ts +120 -0
  123. package/src/util/comparison.ts +87 -14
  124. package/src/util/plugin-loader.ts +19 -0
  125. package/src/vtab/manifest.ts +7 -1
  126. package/src/vtab/memory/index.ts +191 -178
  127. package/src/vtab/memory/layer/manager.ts +28 -2
  128. package/src/vtab/memory/utils/primary-key.ts +19 -14
@@ -2,10 +2,20 @@ import fastJsonPatch from 'fast-json-patch';
2
2
  import type { Operation } from 'fast-json-patch';
3
3
  const { applyPatch } = fastJsonPatch;
4
4
 
5
+ // moat-maker: Runtime validation library with TypeScript-like syntax
6
+ // Used for json_schema() function to validate JSON against structural schemas
7
+ import { validator } from 'moat-maker';
8
+
5
9
  import { createLogger } from '../../common/logger.js';
6
10
  import type { SqlValue, JSONValue } from '../../common/types.js';
7
11
  import { createScalarFunction, createAggregateFunction } from '../registration.js';
8
12
  import { safeJsonParse, resolveJsonPathForModify, prepareJsonValue, deepCopyJson, getJsonType } from './json-helpers.js';
13
+ import type { ScalarFunctionCallNode } from '../../planner/nodes/function.js';
14
+ import type { EmissionContext } from '../../runtime/emission-context.js';
15
+ import type { Instruction, RuntimeContext } from '../../runtime/types.js';
16
+ import { PlanNodeType } from '../../planner/nodes/plan-node-type.js';
17
+ import { LiteralNode } from '../../planner/nodes/scalar.js';
18
+ import { emitPlanNode } from '../../runtime/emitters.js';
9
19
 
10
20
  const log = createLogger('func:builtins:json');
11
21
  const errorLog = log.extend('error');
@@ -20,6 +30,117 @@ export const jsonValidFunc = createScalarFunction(
20
30
  }
21
31
  );
22
32
 
33
+
34
+
35
+ /**
36
+ * Custom emitter for json_schema that caches compiled validators in the EmissionContext.
37
+ * This provides significant performance improvements for CHECK constraints and repeated validations.
38
+ */
39
+ function emitJsonSchema(
40
+ plan: ScalarFunctionCallNode,
41
+ ctx: EmissionContext,
42
+ defaultEmit: (plan: ScalarFunctionCallNode, ctx: EmissionContext) => Instruction
43
+ ): Instruction {
44
+ // Check if the second argument (schema definition) is a constant
45
+ const schemaDefArg = plan.operands[1];
46
+
47
+ if (schemaDefArg?.nodeType === PlanNodeType.Literal) {
48
+ const literalNode = schemaDefArg as LiteralNode;
49
+ const schemaDef = literalNode.getValue();
50
+
51
+ if (typeof schemaDef === 'string') {
52
+ try {
53
+ // Compile the validator once at emission time using moat-maker
54
+ // moat-maker uses template literals, so we need to create a validator dynamically
55
+ // Template literals have a special structure: an array with a 'raw' property
56
+ // We create this structure manually to simulate a template literal
57
+ const parts: any = [schemaDef];
58
+ parts.raw = [schemaDef];
59
+ const compiledValidator = validator(parts, ...[]);
60
+
61
+ // Emit only the JSON argument (first operand)
62
+ const jsonArgInstruction = emitPlanNode(plan.operands[0], ctx);
63
+
64
+ // Create optimized runtime function that uses the cached validator
65
+ // The validator is captured in the closure, so it lives with the plan
66
+ function run(_rctx: RuntimeContext, ...args: any[]): SqlValue {
67
+ const json = args[0];
68
+ if (typeof json !== 'string') return 0;
69
+
70
+ let data: JSONValue | null;
71
+ try {
72
+ data = JSON.parse(json) as JSONValue;
73
+ } catch {
74
+ return 0; // Invalid JSON
75
+ }
76
+
77
+ // Use the cached compiled validator
78
+ try {
79
+ // moat-maker's .matches() returns true if valid, false otherwise
80
+ const isValid = compiledValidator.matches(data);
81
+ return isValid ? 1 : 0;
82
+ } catch (e) {
83
+ errorLog('json_schema validation failed: %O', e);
84
+ return 0;
85
+ }
86
+ }
87
+
88
+ return {
89
+ params: [jsonArgInstruction],
90
+ run,
91
+ note: `json_schema(cached:${schemaDef.substring(0, 20)}...)`
92
+ };
93
+ } catch (e) {
94
+ errorLog('Failed to compile schema at emission time: %O', e);
95
+ // Fall through to default emission
96
+ }
97
+ }
98
+ }
99
+
100
+ // If schema is not a constant or compilation failed, use default emission
101
+ return defaultEmit(plan, ctx);
102
+ }
103
+
104
+ // json_schema(X, schema_def)
105
+ // Note: This function uses a custom emitter that caches compiled validators
106
+ // during plan emission for better performance.
107
+ export const jsonSchemaFunc = createScalarFunction(
108
+ { name: 'json_schema', numArgs: 2, deterministic: true },
109
+ (json: SqlValue, schemaDef: SqlValue): SqlValue => {
110
+ // This is the fallback implementation for when no cache is available
111
+ // (e.g., during direct function calls outside of query execution)
112
+
113
+ // Schema definition must be a string
114
+ if (typeof schemaDef !== 'string') return 0;
115
+
116
+ // Parse the JSON value - need to check if it's valid JSON first
117
+ if (typeof json !== 'string') return 0;
118
+
119
+ let data: JSONValue | null;
120
+ try {
121
+ data = JSON.parse(json) as JSONValue;
122
+ } catch {
123
+ return 0; // Invalid JSON
124
+ }
125
+
126
+ // Compile and validate using moat-maker (no caching in fallback path)
127
+ try {
128
+ const parts: any = [schemaDef];
129
+ parts.raw = [schemaDef];
130
+ const compiledValidator = validator(parts, ...[]);
131
+ // moat-maker's .matches() returns true if valid, false otherwise
132
+ const isValid = compiledValidator.matches(data);
133
+ return isValid ? 1 : 0;
134
+ } catch (e) {
135
+ errorLog('json_schema validation failed: %O', e);
136
+ return 0;
137
+ }
138
+ }
139
+ );
140
+
141
+ // Attach the custom emitter to the function schema
142
+ jsonSchemaFunc.customEmitter = emitJsonSchema;
143
+
23
144
  // json_type(X, P?)
24
145
  export const jsonTypeFunc = createScalarFunction(
25
146
  { name: 'json_type', numArgs: -1, deterministic: true },
@@ -49,7 +49,7 @@ export const schemaFunc = createIntegratedTableValuedFunction(
49
49
  for (const tableSchema of schemaInstance.getAllTables()) {
50
50
  let createSql: string | null = null;
51
51
  try {
52
- const columnsStr = tableSchema.columns.map((c: ColumnSchema) => `"${c.name}" ${c.affinity ?? SqlDataType.TEXT}`).join(', ');
52
+ const columnsStr = tableSchema.columns.map((c: ColumnSchema) => `"${c.name}" ${c.logicalType.name}`).join(', ');
53
53
  const argsStr = Object.entries(tableSchema.vtabArgs ?? {}).map(([key, value]) => `${key}=${value}`).join(', ');
54
54
  createSql = `create table "${tableSchema.name}" (${columnsStr}) using ${tableSchema.vtabModuleName}(${argsStr})`;
55
55
  } catch {
package/src/index.ts CHANGED
@@ -47,6 +47,40 @@ export {
47
47
  resolveCollation
48
48
  } from './util/comparison.js';
49
49
 
50
+ // Type system
51
+ export type { LogicalType, CollationFunction as TypeCollationFunction } from './types/logical-type.js';
52
+ export { PhysicalType } from './types/logical-type.js';
53
+ export {
54
+ NULL_TYPE,
55
+ INTEGER_TYPE,
56
+ REAL_TYPE,
57
+ TEXT_TYPE,
58
+ BLOB_TYPE,
59
+ BOOLEAN_TYPE,
60
+ NUMERIC_TYPE,
61
+ ANY_TYPE
62
+ } from './types/builtin-types.js';
63
+ export {
64
+ DATE_TYPE,
65
+ TIME_TYPE,
66
+ DATETIME_TYPE
67
+ } from './types/temporal-types.js';
68
+ export { JSON_TYPE } from './types/json-type.js';
69
+ export {
70
+ typeRegistry,
71
+ registerType,
72
+ getType,
73
+ getTypeOrDefault,
74
+ inferType
75
+ } from './types/registry.js';
76
+ export {
77
+ validateValue,
78
+ parseValue,
79
+ validateAndParse,
80
+ isValidForType,
81
+ tryParse
82
+ } from './types/validation.js';
83
+
50
84
  // SQL Parser and Compiler
51
85
  export { Parser } from './parser/parser.js';
52
86
  export { Lexer, TokenType, KEYWORDS } from './parser/lexer.js';
@@ -90,6 +124,7 @@ export type {
90
124
  VTablePluginInfo,
91
125
  FunctionPluginInfo,
92
126
  CollationPluginInfo,
127
+ TypePluginInfo,
93
128
  PluginRegistrations
94
129
  } from './vtab/manifest.js';
95
130
 
@@ -10,7 +10,10 @@ import { simpleLike } from "../../util/patterns.js";
10
10
  import type { EmissionContext } from "../emission-context.js";
11
11
 
12
12
  export function emitBinaryOp(plan: BinaryOpNode, ctx: EmissionContext): Instruction {
13
- switch (plan.expression.operator) {
13
+ // Normalize operator to uppercase for case-insensitive matching of keywords
14
+ const operator = plan.expression.operator.toUpperCase();
15
+
16
+ switch (operator) {
14
17
  case '+':
15
18
  case '-':
16
19
  case '*':
@@ -227,9 +230,12 @@ export function emitConcatOp(plan: BinaryOpNode, ctx: EmissionContext): Instruct
227
230
  }
228
231
 
229
232
  export function emitLogicalOp(plan: BinaryOpNode, ctx: EmissionContext): Instruction {
233
+ // Normalize operator to uppercase for case-insensitive matching
234
+ const operator = plan.expression.operator.toUpperCase();
235
+
230
236
  function run(ctx: RuntimeContext, v1: SqlValue, v2: SqlValue): SqlValue {
231
237
  // SQL three-valued logic
232
- switch (plan.expression.operator) {
238
+ switch (operator) {
233
239
  case 'AND': {
234
240
  // NULL AND x -> NULL if x is true or NULL, otherwise 0
235
241
  // 0 AND x -> 0
@@ -9,12 +9,14 @@ import type { RowConstraintSchema, TableSchema } from '../../schema/table.js';
9
9
  import type { RowDescriptor } from '../../planner/nodes/plan-node.js';
10
10
  import { RowOpFlag } from '../../schema/table.js';
11
11
  import { withAsyncRowContext, createRowSlot } from '../context-helpers.js';
12
+ import { expressionToString } from '../../util/ast-stringify.js';
12
13
 
13
14
  interface ConstraintMetadataEntry {
14
15
  schema: RowConstraintSchema;
15
16
  flatRowDescriptor: RowDescriptor;
16
17
  evaluator: (ctx: RuntimeContext) => OutputValue;
17
18
  constraintName: string;
19
+ constraintExpr: string; // Stringified constraint expression
18
20
  shouldDefer: boolean;
19
21
  baseTable: string;
20
22
  contextRow?: Row; // Mutation context row if present
@@ -52,11 +54,13 @@ export function emitConstraintCheck(plan: ConstraintCheckNode, ctx: EmissionCont
52
54
  const constraintMetadata: ConstraintMetadataEntry[] = plan.constraintChecks.map((check, idx) => {
53
55
  const evaluatorInstruction = checkEvaluators[idx];
54
56
  const constraintName = check.constraint.name ?? generateDefaultConstraintName(tableSchema, check.constraint);
57
+ const constraintExpr = expressionToString(check.constraint.expr);
55
58
  return {
56
59
  schema: check.constraint,
57
60
  flatRowDescriptor: plan.flatRowDescriptor,
58
61
  evaluator: evaluatorInstruction.run,
59
62
  constraintName,
63
+ constraintExpr,
60
64
  shouldDefer: Boolean(check.deferrable || check.initiallyDeferred || check.containsSubquery),
61
65
  baseTable: `${tableSchema.schemaName}.${tableSchema.name}`,
62
66
  contextRow: undefined,
@@ -261,8 +265,12 @@ async function checkCheckConstraints(
261
265
  // CHECK constraint passes if result is truthy or NULL
262
266
  // It fails only if result is false or 0 (SQLite-style numeric boolean)
263
267
  if (result === false || result === 0) {
268
+ // Include constraint expression in error message for better debugging
269
+ const exprHint = metadata.constraintExpr.length <= 60
270
+ ? ` (${metadata.constraintExpr})`
271
+ : '';
264
272
  throw new QuereusError(
265
- `CHECK constraint failed: ${metadata.constraintName}`,
273
+ `CHECK constraint failed: ${metadata.constraintName}${exprHint}`,
266
274
  StatusCode.CONSTRAINT
267
275
  );
268
276
  }
@@ -3,12 +3,11 @@ import type { Instruction, RuntimeContext, InstructionRun } from '../types.js';
3
3
  import { emitPlanNode } from '../emitters.js';
4
4
  import type { Row } from '../../common/types.js';
5
5
  import type { EmissionContext } from '../emission-context.js';
6
- import { SqlDataType, type SqlValue } from '../../common/types.js';
7
- import { applyIntegerAffinity, applyRealAffinity, applyNumericAffinity, applyTextAffinity, applyBlobAffinity } from '../../util/affinity.js';
8
6
 
9
7
  export function emitInsert(plan: InsertNode, ctx: EmissionContext): Instruction {
10
8
  // INSERT node now only handles data transformations and passes flat rows through.
11
9
  // The actual database insert operations are handled by DmlExecutorNode.
10
+ // Type conversion is handled by the table manager's validateAndParse in performInsert.
12
11
  async function* run(_ctx: RuntimeContext, sourceValue: AsyncIterable<Row>): AsyncIterable<Row> {
13
12
  const tableSchema = plan.table.tableSchema;
14
13
  const colCount = tableSchema.columns.length;
@@ -23,21 +22,10 @@ export function emitInsert(plan: InsertNode, ctx: EmissionContext): Instruction
23
22
  flatRow[i] = null;
24
23
  }
25
24
 
26
- // Fill NEW section with source values and apply type affinity (indices n..2n-1)
25
+ // Fill NEW section with source values (indices n..2n-1)
26
+ // No affinity conversion here - let the type system handle it
27
27
  for (let colIdx = 0; colIdx < colCount; colIdx++) {
28
- const sourceValue: SqlValue = sourceRow[colIdx];
29
-
30
- let converted: SqlValue;
31
- switch (tableSchema.columns[colIdx].affinity) {
32
- case SqlDataType.INTEGER: converted = applyIntegerAffinity(sourceValue); break;
33
- case SqlDataType.REAL: converted = applyRealAffinity(sourceValue); break;
34
- case SqlDataType.NUMERIC: converted = applyNumericAffinity(sourceValue); break;
35
- case SqlDataType.TEXT: converted = applyTextAffinity(sourceValue); break;
36
- case SqlDataType.BLOB: converted = applyBlobAffinity(sourceValue); break;
37
- default: converted = sourceValue;
38
- }
39
-
40
- flatRow[colCount + colIdx] = converted;
28
+ flatRow[colCount + colIdx] = sourceRow[colIdx];
41
29
  }
42
30
 
43
31
  yield flatRow;
@@ -7,7 +7,11 @@ import type { EmissionContext } from '../emission-context.js';
7
7
  import type { ScalarFunctionSchema } from '../../schema/function.js';
8
8
  import { isScalarFunctionSchema } from '../../schema/function.js';
9
9
 
10
- export function emitScalarFunctionCall(plan: ScalarFunctionCallNode, ctx: EmissionContext): Instruction {
10
+ /**
11
+ * Default emission logic for scalar function calls.
12
+ * This is exported so custom emitters can call it if needed.
13
+ */
14
+ export function emitScalarFunctionCallDefault(plan: ScalarFunctionCallNode, ctx: EmissionContext): Instruction {
11
15
  const functionName = plan.expression.name.toLowerCase();
12
16
  const functionSchema = plan.functionSchema;
13
17
 
@@ -41,3 +45,25 @@ export function emitScalarFunctionCall(plan: ScalarFunctionCallNode, ctx: Emissi
41
45
  `${plan.expression.name}(${plan.operands.length})`
42
46
  );
43
47
  }
48
+
49
+ /**
50
+ * Main emitter for scalar function calls.
51
+ * Checks if the function has a custom emitter and uses it, otherwise uses default logic.
52
+ */
53
+ export function emitScalarFunctionCall(plan: ScalarFunctionCallNode, ctx: EmissionContext): Instruction {
54
+ const functionSchema = plan.functionSchema;
55
+
56
+ // Validate that it's a scalar function
57
+ if (!isScalarFunctionSchema(functionSchema)) {
58
+ const functionName = plan.expression.name.toLowerCase();
59
+ throw new QuereusError(`Function ${functionName} is not a scalar function`, StatusCode.ERROR);
60
+ }
61
+
62
+ // Check if function has a custom emitter
63
+ if (functionSchema.customEmitter) {
64
+ return functionSchema.customEmitter(plan, ctx, emitScalarFunctionCallDefault);
65
+ }
66
+
67
+ // Use default emission logic
68
+ return emitScalarFunctionCallDefault(plan, ctx);
69
+ }
@@ -12,7 +12,10 @@ export function emitUnaryOp(plan: UnaryOpNode, ctx: EmissionContext): Instructio
12
12
  let run: (ctx: RuntimeContext, operand: SqlValue) => SqlValue;
13
13
  let note: string;
14
14
 
15
- switch (plan.expression.operator) {
15
+ // Normalize operator to uppercase for case-insensitive matching
16
+ const operator = plan.expression.operator.toUpperCase();
17
+
18
+ switch (operator) {
16
19
  case 'NOT':
17
20
  run = (ctx: RuntimeContext, operand: SqlValue) => {
18
21
  // SQL NOT: NULL -> NULL, 0 -> 1, anything else -> 0
@@ -1,5 +1,6 @@
1
1
  import { SqlDataType } from '../common/types.js';
2
2
  import type { Expression } from '../parser/ast.js';
3
+ import type { LogicalType } from '../types/logical-type.js';
3
4
 
4
5
  /**
5
6
  * Represents the schema definition of a single column in a table.
@@ -7,7 +8,9 @@ import type { Expression } from '../parser/ast.js';
7
8
  export interface ColumnSchema {
8
9
  /** Column name */
9
10
  name: string;
10
- /** Data type affinity (TEXT, INTEGER, REAL, BLOB, NUMERIC) */
11
+ /** Logical type definition */
12
+ logicalType: LogicalType;
13
+ /** Data type affinity (TEXT, INTEGER, REAL, BLOB, NUMERIC) - kept for backward compatibility during transition */
11
14
  affinity: SqlDataType;
12
15
  /** Whether the column has a NOT NULL constraint */
13
16
  notNull: boolean;
@@ -34,8 +37,12 @@ export interface ColumnSchema {
34
37
  * @returns A new column schema with default values
35
38
  */
36
39
  export function createDefaultColumnSchema(name: string, defaultNotNull: boolean = true): ColumnSchema {
40
+ // Import TEXT_TYPE lazily to avoid circular dependencies
41
+ const { TEXT_TYPE } = require('../types/builtin-types.js');
42
+
37
43
  return {
38
44
  name: name,
45
+ logicalType: TEXT_TYPE,
39
46
  affinity: SqlDataType.TEXT,
40
47
  notNull: defaultNotNull, // Third Manifesto: default to NOT NULL
41
48
  primaryKey: false,
@@ -33,6 +33,17 @@ export type AggregateReducer<T = any> = (accumulator: T, ...args: SqlValue[]) =>
33
33
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
34
  export type AggregateFinalizer<T = any> = (accumulator: T) => SqlValue;
35
35
 
36
+ /**
37
+ * Custom emitter hook for functions that need special emission logic.
38
+ * This allows functions to cache compiled state in the EmissionContext,
39
+ * optimize constant arguments, or perform other emission-time optimizations.
40
+ */
41
+ export type CustomEmitterHook = (
42
+ plan: any, // ScalarFunctionCallNode, but we avoid circular dependency
43
+ ctx: any, // EmissionContext, but we avoid circular dependency
44
+ defaultEmit: (plan: any, ctx: any) => any // Instruction
45
+ ) => any; // Instruction
46
+
36
47
  /**
37
48
  * Base interface for all function schemas with common properties.
38
49
  */
@@ -47,6 +58,13 @@ interface BaseFunctionSchema {
47
58
  userData?: unknown;
48
59
  /** Return type information */
49
60
  returnType: BaseType;
61
+ /**
62
+ * Optional custom emitter hook for emission-time optimizations.
63
+ * If provided, this function will be called during plan emission instead of
64
+ * the default scalar function emitter. The hook receives the plan node,
65
+ * emission context, and a reference to the default emitter.
66
+ */
67
+ customEmitter?: CustomEmitterHook;
50
68
  }
51
69
 
52
70
  /**