@quereus/quereus 0.5.2 → 0.6.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/README.md +5 -1
- package/dist/src/common/datatype.d.ts +4 -5
- package/dist/src/common/datatype.d.ts.map +1 -1
- package/dist/src/common/datatype.js.map +1 -1
- package/dist/src/common/type-inference.d.ts +3 -6
- package/dist/src/common/type-inference.d.ts.map +1 -1
- package/dist/src/common/type-inference.js +17 -22
- package/dist/src/common/type-inference.js.map +1 -1
- package/dist/src/core/param.d.ts.map +1 -1
- package/dist/src/core/param.js +3 -18
- package/dist/src/core/param.js.map +1 -1
- package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
- package/dist/src/func/builtins/aggregate.js +24 -2
- package/dist/src/func/builtins/aggregate.js.map +1 -1
- package/dist/src/func/builtins/builtin-window-functions.js +10 -10
- package/dist/src/func/builtins/builtin-window-functions.js.map +1 -1
- package/dist/src/func/builtins/conversion.d.ts +10 -0
- package/dist/src/func/builtins/conversion.d.ts.map +1 -1
- package/dist/src/func/builtins/conversion.js +20 -1
- package/dist/src/func/builtins/conversion.js.map +1 -1
- package/dist/src/func/builtins/datetime.js +9 -9
- package/dist/src/func/builtins/datetime.js.map +1 -1
- package/dist/src/func/builtins/explain.js +53 -53
- package/dist/src/func/builtins/explain.js.map +1 -1
- package/dist/src/func/builtins/generation.js +2 -2
- package/dist/src/func/builtins/generation.js.map +1 -1
- package/dist/src/func/builtins/index.d.ts.map +1 -1
- package/dist/src/func/builtins/index.js +16 -1
- package/dist/src/func/builtins/index.js.map +1 -1
- package/dist/src/func/builtins/json-tvf.js +17 -17
- package/dist/src/func/builtins/json-tvf.js.map +1 -1
- package/dist/src/func/builtins/json.js +11 -11
- package/dist/src/func/builtins/json.js.map +1 -1
- package/dist/src/func/builtins/scalar.d.ts.map +1 -1
- package/dist/src/func/builtins/scalar.js +202 -13
- package/dist/src/func/builtins/scalar.js.map +1 -1
- package/dist/src/func/builtins/schema.js +18 -18
- package/dist/src/func/builtins/schema.js.map +1 -1
- package/dist/src/func/builtins/string.d.ts.map +1 -1
- package/dist/src/func/builtins/string.js +59 -50
- package/dist/src/func/builtins/string.js.map +1 -1
- package/dist/src/func/builtins/timespan.d.ts +45 -0
- package/dist/src/func/builtins/timespan.d.ts.map +1 -0
- package/dist/src/func/builtins/timespan.js +147 -0
- package/dist/src/func/builtins/timespan.js.map +1 -0
- package/dist/src/func/registration.d.ts +26 -0
- package/dist/src/func/registration.d.ts.map +1 -1
- package/dist/src/func/registration.js +9 -5
- package/dist/src/func/registration.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/parser/parser.js +2 -2
- package/dist/src/parser/parser.js.map +1 -1
- package/dist/src/planner/building/constraint-builder.js +2 -2
- package/dist/src/planner/building/constraint-builder.js.map +1 -1
- package/dist/src/planner/building/delete.js +3 -3
- package/dist/src/planner/building/delete.js.map +1 -1
- package/dist/src/planner/building/function-call.d.ts.map +1 -1
- package/dist/src/planner/building/function-call.js +24 -4
- package/dist/src/planner/building/function-call.js.map +1 -1
- package/dist/src/planner/building/insert.js +3 -3
- package/dist/src/planner/building/insert.js.map +1 -1
- package/dist/src/planner/building/select.d.ts.map +1 -1
- package/dist/src/planner/building/select.js +3 -2
- package/dist/src/planner/building/select.js.map +1 -1
- package/dist/src/planner/building/update.js +7 -7
- package/dist/src/planner/building/update.js.map +1 -1
- package/dist/src/planner/nodes/aggregate-function.d.ts +2 -1
- package/dist/src/planner/nodes/aggregate-function.d.ts.map +1 -1
- package/dist/src/planner/nodes/aggregate-function.js +10 -3
- package/dist/src/planner/nodes/aggregate-function.js.map +1 -1
- package/dist/src/planner/nodes/cte-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/cte-node.js +2 -2
- package/dist/src/planner/nodes/cte-node.js.map +1 -1
- package/dist/src/planner/nodes/declarative-schema.js +3 -3
- package/dist/src/planner/nodes/declarative-schema.js.map +1 -1
- package/dist/src/planner/nodes/function.d.ts +2 -1
- package/dist/src/planner/nodes/function.d.ts.map +1 -1
- package/dist/src/planner/nodes/function.js +6 -3
- package/dist/src/planner/nodes/function.js.map +1 -1
- package/dist/src/planner/nodes/insert-node.js +1 -1
- package/dist/src/planner/nodes/insert-node.js.map +1 -1
- package/dist/src/planner/nodes/pragma.d.ts +1 -1
- package/dist/src/planner/nodes/pragma.d.ts.map +1 -1
- package/dist/src/planner/nodes/pragma.js +3 -3
- package/dist/src/planner/nodes/pragma.js.map +1 -1
- package/dist/src/planner/nodes/reference.js +1 -1
- package/dist/src/planner/nodes/reference.js.map +1 -1
- package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
- package/dist/src/planner/nodes/scalar.js +55 -101
- package/dist/src/planner/nodes/scalar.js.map +1 -1
- package/dist/src/planner/nodes/sequencing-node.js +2 -2
- package/dist/src/planner/nodes/sequencing-node.js.map +1 -1
- package/dist/src/planner/nodes/sink-node.js +2 -2
- package/dist/src/planner/nodes/sink-node.js.map +1 -1
- package/dist/src/planner/nodes/subquery.d.ts.map +1 -1
- package/dist/src/planner/nodes/subquery.js +4 -7
- package/dist/src/planner/nodes/subquery.js.map +1 -1
- package/dist/src/planner/nodes/view-reference-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/view-reference-node.js +2 -2
- package/dist/src/planner/nodes/view-reference-node.js.map +1 -1
- package/dist/src/planner/nodes/window-function.js +3 -3
- package/dist/src/planner/nodes/window-function.js.map +1 -1
- package/dist/src/planner/rules/access/rule-select-access-path.js +1 -1
- package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
- package/dist/src/planner/scopes/global.js +3 -3
- package/dist/src/planner/scopes/global.js.map +1 -1
- package/dist/src/planner/scopes/param.d.ts.map +1 -1
- package/dist/src/planner/scopes/param.js +2 -2
- package/dist/src/planner/scopes/param.js.map +1 -1
- package/dist/src/planner/type-utils.d.ts +2 -12
- package/dist/src/planner/type-utils.d.ts.map +1 -1
- package/dist/src/planner/type-utils.js +6 -21
- package/dist/src/planner/type-utils.js.map +1 -1
- package/dist/src/runtime/emit/between.js +2 -2
- package/dist/src/runtime/emit/between.js.map +1 -1
- package/dist/src/runtime/emit/binary.d.ts.map +1 -1
- package/dist/src/runtime/emit/binary.js +66 -30
- package/dist/src/runtime/emit/binary.js.map +1 -1
- package/dist/src/runtime/emit/subquery.js +8 -8
- package/dist/src/runtime/emit/subquery.js.map +1 -1
- package/dist/src/runtime/emit/temporal-arithmetic.d.ts +33 -0
- package/dist/src/runtime/emit/temporal-arithmetic.d.ts.map +1 -0
- package/dist/src/runtime/emit/temporal-arithmetic.js +269 -0
- package/dist/src/runtime/emit/temporal-arithmetic.js.map +1 -0
- package/dist/src/runtime/emit/unary.d.ts.map +1 -1
- package/dist/src/runtime/emit/unary.js +16 -4
- package/dist/src/runtime/emit/unary.js.map +1 -1
- package/dist/src/schema/catalog.js +3 -3
- package/dist/src/schema/catalog.js.map +1 -1
- package/dist/src/schema/column.d.ts +0 -3
- package/dist/src/schema/column.d.ts.map +1 -1
- package/dist/src/schema/column.js +0 -2
- package/dist/src/schema/column.js.map +1 -1
- package/dist/src/schema/function.d.ts +29 -1
- package/dist/src/schema/function.d.ts.map +1 -1
- package/dist/src/schema/function.js.map +1 -1
- package/dist/src/schema/table.d.ts +3 -3
- package/dist/src/schema/table.d.ts.map +1 -1
- package/dist/src/schema/table.js +4 -6
- package/dist/src/schema/table.js.map +1 -1
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +1 -1
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/registry.d.ts.map +1 -1
- package/dist/src/types/registry.js +5 -1
- package/dist/src/types/registry.js.map +1 -1
- package/dist/src/types/temporal-types.d.ts +5 -0
- package/dist/src/types/temporal-types.d.ts.map +1 -1
- package/dist/src/types/temporal-types.js +122 -0
- package/dist/src/types/temporal-types.js.map +1 -1
- package/dist/src/util/plan-formatter.d.ts.map +1 -1
- package/dist/src/util/plan-formatter.js +1 -5
- package/dist/src/util/plan-formatter.js.map +1 -1
- package/dist/src/util/row-descriptor.js +2 -2
- package/dist/src/util/row-descriptor.js.map +1 -1
- package/dist/src/vtab/best-access-plan.d.ts +4 -3
- package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
- package/dist/src/vtab/best-access-plan.js.map +1 -1
- package/dist/src/vtab/memory/module.js +1 -1
- package/dist/src/vtab/memory/module.js.map +1 -1
- package/package.json +1 -1
- package/src/common/datatype.ts +4 -5
- package/src/common/type-inference.ts +13 -22
- package/src/core/param.ts +4 -11
- package/src/func/builtins/aggregate.ts +24 -2
- package/src/func/builtins/builtin-window-functions.ts +10 -10
- package/src/func/builtins/conversion.ts +26 -1
- package/src/func/builtins/datetime.ts +9 -9
- package/src/func/builtins/explain.ts +53 -53
- package/src/func/builtins/generation.ts +2 -2
- package/src/func/builtins/index.ts +20 -1
- package/src/func/builtins/json-tvf.ts +17 -17
- package/src/func/builtins/json.ts +11 -11
- package/src/func/builtins/scalar.ts +205 -14
- package/src/func/builtins/schema.ts +18 -18
- package/src/func/builtins/string.ts +93 -80
- package/src/func/builtins/timespan.ts +179 -0
- package/src/func/registration.ts +35 -5
- package/src/index.ts +2 -1
- package/src/parser/parser.ts +2 -2
- package/src/planner/building/constraint-builder.ts +2 -2
- package/src/planner/building/delete.ts +3 -3
- package/src/planner/building/function-call.ts +44 -3
- package/src/planner/building/insert.ts +3 -3
- package/src/planner/building/select.ts +3 -2
- package/src/planner/building/update.ts +7 -7
- package/src/planner/nodes/aggregate-function.ts +13 -3
- package/src/planner/nodes/cte-node.ts +2 -2
- package/src/planner/nodes/declarative-schema.ts +3 -3
- package/src/planner/nodes/function.ts +8 -3
- package/src/planner/nodes/insert-node.ts +1 -1
- package/src/planner/nodes/pragma.ts +4 -3
- package/src/planner/nodes/reference.ts +1 -1
- package/src/planner/nodes/scalar.ts +54 -102
- package/src/planner/nodes/sequencing-node.ts +2 -2
- package/src/planner/nodes/sink-node.ts +2 -2
- package/src/planner/nodes/subquery.ts +5 -7
- package/src/planner/nodes/view-reference-node.ts +2 -2
- package/src/planner/nodes/window-function.ts +3 -3
- package/src/planner/rules/access/rule-select-access-path.ts +1 -1
- package/src/planner/rules/retrieve/rule-grow-retrieve.ts +1 -1
- package/src/planner/scopes/global.ts +3 -3
- package/src/planner/scopes/param.ts +2 -2
- package/src/planner/type-utils.ts +6 -14
- package/src/runtime/emit/between.ts +2 -2
- package/src/runtime/emit/binary.ts +74 -30
- package/src/runtime/emit/subquery.ts +8 -8
- package/src/runtime/emit/temporal-arithmetic.ts +302 -0
- package/src/runtime/emit/unary.ts +17 -4
- package/src/schema/catalog.ts +3 -3
- package/src/schema/column.ts +0 -3
- package/src/schema/function.ts +29 -1
- package/src/schema/table.ts +5 -7
- package/src/types/index.ts +1 -1
- package/src/types/registry.ts +5 -1
- package/src/types/temporal-types.ts +123 -0
- package/src/util/plan-formatter.ts +1 -4
- package/src/util/row-descriptor.ts +2 -2
- package/src/vtab/best-access-plan.ts +4 -3
- package/src/vtab/memory/module.ts +1 -1
|
@@ -8,6 +8,7 @@ import { compareSqlValuesFast, resolveCollation } from "../../util/comparison.js
|
|
|
8
8
|
import { coerceForComparison, coerceToNumberForArithmetic } from "../../util/coercion.js";
|
|
9
9
|
import { simpleLike } from "../../util/patterns.js";
|
|
10
10
|
import type { EmissionContext } from "../emission-context.js";
|
|
11
|
+
import { tryTemporalArithmetic, tryTemporalComparison } from "./temporal-arithmetic.js";
|
|
11
12
|
|
|
12
13
|
export function emitBinaryOp(plan: BinaryOpNode, ctx: EmissionContext): Instruction {
|
|
13
14
|
// Normalize operator to uppercase for case-insensitive matching of keywords
|
|
@@ -73,6 +74,13 @@ export function emitNumericOp(plan: BinaryOpNode, ctx: EmissionContext): Instruc
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
function run(ctx: RuntimeContext, v1: SqlValue, v2: SqlValue): SqlValue {
|
|
77
|
+
// Try temporal arithmetic first
|
|
78
|
+
const temporalResult = tryTemporalArithmetic(plan.expression.operator, v1, v2);
|
|
79
|
+
if (temporalResult !== undefined) {
|
|
80
|
+
return temporalResult;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Fall back to numeric arithmetic
|
|
76
84
|
if (v1 !== null && v2 !== null) {
|
|
77
85
|
if (typeof v1 === 'bigint' || typeof v2 === 'bigint') {
|
|
78
86
|
try {
|
|
@@ -127,16 +135,24 @@ export function emitComparisonOp(plan: BinaryOpNode, ctx: EmissionContext): Inst
|
|
|
127
135
|
// Pre-resolve collation function for optimal performance
|
|
128
136
|
const collationFunc = resolveCollation(collationName);
|
|
129
137
|
|
|
130
|
-
|
|
138
|
+
const operator = plan.expression.operator;
|
|
139
|
+
|
|
140
|
+
switch (operator) {
|
|
131
141
|
case '=':
|
|
132
142
|
case '==':
|
|
133
143
|
run = (ctx: RuntimeContext, v1: SqlValue, v2: SqlValue): SqlValue => {
|
|
134
144
|
// SQL comparison: NULL = anything -> NULL
|
|
135
145
|
if (v1 === null || v2 === null) return null;
|
|
136
146
|
|
|
147
|
+
// Try temporal comparison first
|
|
148
|
+
const temporalResult = tryTemporalComparison(operator, v1, v2);
|
|
149
|
+
if (temporalResult !== undefined) {
|
|
150
|
+
return temporalResult;
|
|
151
|
+
}
|
|
152
|
+
|
|
137
153
|
// Apply type coercion before comparison
|
|
138
154
|
const [coercedV1, coercedV2] = coerceForComparison(v1, v2);
|
|
139
|
-
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) === 0
|
|
155
|
+
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) === 0;
|
|
140
156
|
};
|
|
141
157
|
break;
|
|
142
158
|
case '!=':
|
|
@@ -145,9 +161,15 @@ export function emitComparisonOp(plan: BinaryOpNode, ctx: EmissionContext): Inst
|
|
|
145
161
|
// SQL comparison: NULL != anything -> NULL
|
|
146
162
|
if (v1 === null || v2 === null) return null;
|
|
147
163
|
|
|
164
|
+
// Try temporal comparison first
|
|
165
|
+
const temporalResult = tryTemporalComparison(operator, v1, v2);
|
|
166
|
+
if (temporalResult !== undefined) {
|
|
167
|
+
return temporalResult;
|
|
168
|
+
}
|
|
169
|
+
|
|
148
170
|
// Apply type coercion before comparison
|
|
149
171
|
const [coercedV1, coercedV2] = coerceForComparison(v1, v2);
|
|
150
|
-
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) !== 0
|
|
172
|
+
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) !== 0;
|
|
151
173
|
};
|
|
152
174
|
break;
|
|
153
175
|
case '<':
|
|
@@ -155,9 +177,15 @@ export function emitComparisonOp(plan: BinaryOpNode, ctx: EmissionContext): Inst
|
|
|
155
177
|
// SQL comparison: NULL < anything -> NULL
|
|
156
178
|
if (v1 === null || v2 === null) return null;
|
|
157
179
|
|
|
180
|
+
// Try temporal comparison first
|
|
181
|
+
const temporalResult = tryTemporalComparison(operator, v1, v2);
|
|
182
|
+
if (temporalResult !== undefined) {
|
|
183
|
+
return temporalResult;
|
|
184
|
+
}
|
|
185
|
+
|
|
158
186
|
// Apply type coercion before comparison
|
|
159
187
|
const [coercedV1, coercedV2] = coerceForComparison(v1, v2);
|
|
160
|
-
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) < 0
|
|
188
|
+
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) < 0;
|
|
161
189
|
};
|
|
162
190
|
break;
|
|
163
191
|
case '<=':
|
|
@@ -165,9 +193,15 @@ export function emitComparisonOp(plan: BinaryOpNode, ctx: EmissionContext): Inst
|
|
|
165
193
|
// SQL comparison: NULL <= anything -> NULL
|
|
166
194
|
if (v1 === null || v2 === null) return null;
|
|
167
195
|
|
|
196
|
+
// Try temporal comparison first
|
|
197
|
+
const temporalResult = tryTemporalComparison(operator, v1, v2);
|
|
198
|
+
if (temporalResult !== undefined) {
|
|
199
|
+
return temporalResult;
|
|
200
|
+
}
|
|
201
|
+
|
|
168
202
|
// Apply type coercion before comparison
|
|
169
203
|
const [coercedV1, coercedV2] = coerceForComparison(v1, v2);
|
|
170
|
-
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) <= 0
|
|
204
|
+
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) <= 0;
|
|
171
205
|
};
|
|
172
206
|
break;
|
|
173
207
|
case '>':
|
|
@@ -177,11 +211,15 @@ export function emitComparisonOp(plan: BinaryOpNode, ctx: EmissionContext): Inst
|
|
|
177
211
|
return null;
|
|
178
212
|
}
|
|
179
213
|
|
|
214
|
+
// Try temporal comparison first
|
|
215
|
+
const temporalResult = tryTemporalComparison(operator, v1, v2);
|
|
216
|
+
if (temporalResult !== undefined) {
|
|
217
|
+
return temporalResult;
|
|
218
|
+
}
|
|
219
|
+
|
|
180
220
|
// Apply type coercion before comparison
|
|
181
221
|
const [coercedV1, coercedV2] = coerceForComparison(v1, v2);
|
|
182
|
-
|
|
183
|
-
const finalResult = comparisonResult > 0 ? 1 : 0;
|
|
184
|
-
return finalResult;
|
|
222
|
+
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) > 0;
|
|
185
223
|
};
|
|
186
224
|
break;
|
|
187
225
|
case '>=':
|
|
@@ -189,13 +227,19 @@ export function emitComparisonOp(plan: BinaryOpNode, ctx: EmissionContext): Inst
|
|
|
189
227
|
// SQL comparison: NULL >= anything -> NULL
|
|
190
228
|
if (v1 === null || v2 === null) return null;
|
|
191
229
|
|
|
230
|
+
// Try temporal comparison first
|
|
231
|
+
const temporalResult = tryTemporalComparison(operator, v1, v2);
|
|
232
|
+
if (temporalResult !== undefined) {
|
|
233
|
+
return temporalResult;
|
|
234
|
+
}
|
|
235
|
+
|
|
192
236
|
// Apply type coercion before comparison
|
|
193
237
|
const [coercedV1, coercedV2] = coerceForComparison(v1, v2);
|
|
194
|
-
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) >= 0
|
|
238
|
+
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) >= 0;
|
|
195
239
|
};
|
|
196
240
|
break;
|
|
197
241
|
default:
|
|
198
|
-
throw new QuereusError(`Unsupported comparison operator: ${
|
|
242
|
+
throw new QuereusError(`Unsupported comparison operator: ${operator}`, StatusCode.UNSUPPORTED);
|
|
199
243
|
}
|
|
200
244
|
|
|
201
245
|
const leftExpr = emitPlanNode(plan.left, ctx);
|
|
@@ -237,38 +281,38 @@ export function emitLogicalOp(plan: BinaryOpNode, ctx: EmissionContext): Instruc
|
|
|
237
281
|
// SQL three-valued logic
|
|
238
282
|
switch (operator) {
|
|
239
283
|
case 'AND': {
|
|
240
|
-
// NULL AND x -> NULL if x is true or NULL, otherwise
|
|
241
|
-
//
|
|
242
|
-
//
|
|
284
|
+
// NULL AND x -> NULL if x is true or NULL, otherwise false
|
|
285
|
+
// false AND x -> false
|
|
286
|
+
// true AND x -> x
|
|
243
287
|
if (v1 === null) {
|
|
244
|
-
return (v2 === null || v2) ? null :
|
|
288
|
+
return (v2 === null || v2) ? null : false;
|
|
245
289
|
}
|
|
246
|
-
if (!v1) return
|
|
247
|
-
return v2 === null ? null : (v2 ?
|
|
290
|
+
if (!v1) return false;
|
|
291
|
+
return v2 === null ? null : (v2 ? true : false);
|
|
248
292
|
}
|
|
249
293
|
|
|
250
294
|
case 'OR': {
|
|
251
|
-
// NULL OR x -> NULL if x is false or NULL, otherwise
|
|
252
|
-
//
|
|
253
|
-
//
|
|
295
|
+
// NULL OR x -> NULL if x is false or NULL, otherwise true
|
|
296
|
+
// true OR x -> true
|
|
297
|
+
// false OR x -> x
|
|
254
298
|
if (v1 === null) {
|
|
255
|
-
return (v2 === null || !v2) ? null :
|
|
299
|
+
return (v2 === null || !v2) ? null : true;
|
|
256
300
|
}
|
|
257
|
-
if (v1) return
|
|
258
|
-
return v2 === null ? null : (v2 ?
|
|
301
|
+
if (v1) return true;
|
|
302
|
+
return v2 === null ? null : (v2 ? true : false);
|
|
259
303
|
}
|
|
260
304
|
|
|
261
305
|
case 'XOR': {
|
|
262
306
|
// NULL XOR x -> NULL
|
|
263
307
|
// x XOR NULL -> NULL
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
308
|
+
// false XOR false -> false
|
|
309
|
+
// false XOR true -> true
|
|
310
|
+
// true XOR false -> true
|
|
311
|
+
// true XOR true -> false
|
|
268
312
|
if (v1 === null || v2 === null) return null;
|
|
269
|
-
const b1 = v1
|
|
270
|
-
const b2 = v2
|
|
271
|
-
return b1 !== b2
|
|
313
|
+
const b1 = !!v1;
|
|
314
|
+
const b2 = !!v2;
|
|
315
|
+
return b1 !== b2;
|
|
272
316
|
}
|
|
273
317
|
|
|
274
318
|
default:
|
|
@@ -298,7 +342,7 @@ export function emitLikeOp(plan: BinaryOpNode, ctx: EmissionContext): Instructio
|
|
|
298
342
|
const textStr = String(text);
|
|
299
343
|
const patternStr = String(pattern);
|
|
300
344
|
|
|
301
|
-
return simpleLike(patternStr, textStr)
|
|
345
|
+
return simpleLike(patternStr, textStr);
|
|
302
346
|
}
|
|
303
347
|
|
|
304
348
|
const leftExpr = emitPlanNode(plan.left, ctx);
|
|
@@ -63,13 +63,13 @@ export function emitIn(plan: InNode, ctx: EmissionContext): Instruction {
|
|
|
63
63
|
}
|
|
64
64
|
// Check for match immediately - no need to materialize
|
|
65
65
|
if (compareSqlValuesFast(condition, rowValue, collation) === 0) {
|
|
66
|
-
return
|
|
66
|
+
return true; // Found a match
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
// No match found - if any value was NULL, result is NULL
|
|
72
|
-
return hasNull ? null :
|
|
72
|
+
return hasNull ? null : false;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
const sourceInstruction = emitPlanNode(plan.source, ctx);
|
|
@@ -103,11 +103,11 @@ export function emitIn(plan: InNode, ctx: EmissionContext): Instruction {
|
|
|
103
103
|
// Check if condition exists in pre-built tree
|
|
104
104
|
const path = tree.find(condition);
|
|
105
105
|
if (path.on) {
|
|
106
|
-
return
|
|
106
|
+
return true; // Found a match
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
// No match found - if any value was NULL, result is NULL
|
|
110
|
-
return hasNull ? null :
|
|
110
|
+
return hasNull ? null : false;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
const values = plan.values.map(val => (val as unknown as ConstantNode).getValue());
|
|
@@ -163,12 +163,12 @@ export function emitIn(plan: InNode, ctx: EmissionContext): Instruction {
|
|
|
163
163
|
continue;
|
|
164
164
|
}
|
|
165
165
|
if (compareSqlValuesFast(condition, value, collation) === 0) {
|
|
166
|
-
return
|
|
166
|
+
return true; // Found a match
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
// No match found - if any value was NULL, result is NULL
|
|
171
|
-
return hasNull ? null :
|
|
171
|
+
return hasNull ? null : false;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
const conditionExpr = emitPlanNode(plan.condition, ctx);
|
|
@@ -188,9 +188,9 @@ export function emitIn(plan: InNode, ctx: EmissionContext): Instruction {
|
|
|
188
188
|
export function emitExists(plan: ExistsNode, ctx: EmissionContext): Instruction {
|
|
189
189
|
async function run(_rctx: RuntimeContext, input: AsyncIterable<Row>): Promise<SqlValue> {
|
|
190
190
|
for await (const _row of input) {
|
|
191
|
-
return
|
|
191
|
+
return true; // First row => TRUE
|
|
192
192
|
}
|
|
193
|
-
return
|
|
193
|
+
return false; // Empty => FALSE
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
const innerInstruction = emitPlanNode(plan.subquery, ctx);
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { StatusCode } from "../../common/types.js";
|
|
2
|
+
import { QuereusError } from "../../common/errors.js";
|
|
3
|
+
import type { SqlValue } from "../../common/types.js";
|
|
4
|
+
import type { Instruction, InstructionRun, RuntimeContext } from "../types.js";
|
|
5
|
+
import type { BinaryOpNode } from "../../planner/nodes/scalar.js";
|
|
6
|
+
import { emitPlanNode } from "../emitters.js";
|
|
7
|
+
import type { EmissionContext } from "../emission-context.js";
|
|
8
|
+
import { Temporal } from 'temporal-polyfill';
|
|
9
|
+
import { TIMESPAN_TYPE } from "../../types/temporal-types.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if a value is a date string (YYYY-MM-DD format)
|
|
13
|
+
*/
|
|
14
|
+
function isDateValue(v: SqlValue): boolean {
|
|
15
|
+
if (typeof v !== 'string') return false;
|
|
16
|
+
// Simple check for ISO 8601 date format
|
|
17
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(v);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if a value is a time string (HH:MM:SS format)
|
|
22
|
+
*/
|
|
23
|
+
function isTimeValue(v: SqlValue): boolean {
|
|
24
|
+
if (typeof v !== 'string') return false;
|
|
25
|
+
// Simple check for ISO 8601 time format
|
|
26
|
+
return /^\d{2}:\d{2}:\d{2}/.test(v);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if a value is a datetime string (ISO 8601 format)
|
|
31
|
+
*/
|
|
32
|
+
function isDateTimeValue(v: SqlValue): boolean {
|
|
33
|
+
if (typeof v !== 'string') return false;
|
|
34
|
+
// Check for ISO 8601 datetime format (with T separator)
|
|
35
|
+
return v.includes('T') && /^\d{4}-\d{2}-\d{2}T/.test(v);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if a value is a timespan/duration string (ISO 8601 duration format)
|
|
40
|
+
*/
|
|
41
|
+
function isTimespanValue(v: SqlValue): boolean {
|
|
42
|
+
if (typeof v !== 'string') return false;
|
|
43
|
+
// ISO 8601 duration starts with P (or -P for negative)
|
|
44
|
+
return v.startsWith('P') || v.startsWith('-P');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Try to perform temporal arithmetic on two values.
|
|
49
|
+
* Returns the result if successful, or undefined if the values are not temporal types.
|
|
50
|
+
* Throws QuereusError if the operation is invalid.
|
|
51
|
+
*/
|
|
52
|
+
export function tryTemporalArithmetic(operator: string, v1: SqlValue, v2: SqlValue): SqlValue | undefined {
|
|
53
|
+
if (v1 === null || v2 === null) return null;
|
|
54
|
+
|
|
55
|
+
// Detect types at runtime
|
|
56
|
+
const isV1Date = isDateValue(v1);
|
|
57
|
+
const isV1Time = isTimeValue(v1);
|
|
58
|
+
const isV1DateTime = isDateTimeValue(v1);
|
|
59
|
+
const isV1Timespan = isTimespanValue(v1);
|
|
60
|
+
|
|
61
|
+
const isV2Date = isDateValue(v2);
|
|
62
|
+
const isV2Time = isTimeValue(v2);
|
|
63
|
+
const isV2DateTime = isDateTimeValue(v2);
|
|
64
|
+
const isV2Timespan = isTimespanValue(v2);
|
|
65
|
+
|
|
66
|
+
// If neither operand is temporal, return undefined to signal non-temporal operation
|
|
67
|
+
const isV1Temporal = isV1Date || isV1Time || isV1DateTime || isV1Timespan;
|
|
68
|
+
const isV2Temporal = isV2Date || isV2Time || isV2DateTime || isV2Timespan;
|
|
69
|
+
if (!isV1Temporal && !isV2Temporal) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
|
|
75
|
+
// DATE/DATETIME - DATE/DATETIME → TIMESPAN
|
|
76
|
+
if (operator === '-' &&
|
|
77
|
+
(isV1Date || isV1DateTime) &&
|
|
78
|
+
(isV2Date || isV2DateTime)) {
|
|
79
|
+
|
|
80
|
+
// Parse both values as dates
|
|
81
|
+
const date1 = isV1DateTime
|
|
82
|
+
? Temporal.PlainDateTime.from(v1 as string).toPlainDate()
|
|
83
|
+
: Temporal.PlainDate.from(v1 as string);
|
|
84
|
+
const date2 = isV2DateTime
|
|
85
|
+
? Temporal.PlainDateTime.from(v2 as string).toPlainDate()
|
|
86
|
+
: Temporal.PlainDate.from(v2 as string);
|
|
87
|
+
|
|
88
|
+
const duration = date1.since(date2);
|
|
89
|
+
return duration.toString();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// TIME - TIME → TIMESPAN
|
|
93
|
+
if (operator === '-' && isV1Time && isV2Time) {
|
|
94
|
+
const time1 = Temporal.PlainTime.from(v1 as string);
|
|
95
|
+
const time2 = Temporal.PlainTime.from(v2 as string);
|
|
96
|
+
const duration = time1.since(time2);
|
|
97
|
+
return duration.toString();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// DATE + TIMESPAN → DATE
|
|
101
|
+
if (operator === '+' && isV1Date && isV2Timespan) {
|
|
102
|
+
const date = Temporal.PlainDate.from(v1 as string);
|
|
103
|
+
const duration = Temporal.Duration.from(v2 as string);
|
|
104
|
+
const result = date.add(duration);
|
|
105
|
+
return result.toString();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// TIMESPAN + DATE → DATE (commutative)
|
|
109
|
+
if (operator === '+' && isV1Timespan && isV2Date) {
|
|
110
|
+
const duration = Temporal.Duration.from(v1 as string);
|
|
111
|
+
const date = Temporal.PlainDate.from(v2 as string);
|
|
112
|
+
const result = date.add(duration);
|
|
113
|
+
return result.toString();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// DATE - TIMESPAN → DATE
|
|
117
|
+
if (operator === '-' && isV1Date && isV2Timespan) {
|
|
118
|
+
const date = Temporal.PlainDate.from(v1 as string);
|
|
119
|
+
const duration = Temporal.Duration.from(v2 as string);
|
|
120
|
+
const result = date.subtract(duration);
|
|
121
|
+
return result.toString();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// DATETIME + TIMESPAN → DATETIME
|
|
125
|
+
if (operator === '+' && isV1DateTime && isV2Timespan) {
|
|
126
|
+
const dt = Temporal.PlainDateTime.from(v1 as string);
|
|
127
|
+
const duration = Temporal.Duration.from(v2 as string);
|
|
128
|
+
const result = dt.add(duration);
|
|
129
|
+
return result.toString();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// TIMESPAN + DATETIME → DATETIME (commutative)
|
|
133
|
+
if (operator === '+' && isV1Timespan && isV2DateTime) {
|
|
134
|
+
const duration = Temporal.Duration.from(v1 as string);
|
|
135
|
+
const dt = Temporal.PlainDateTime.from(v2 as string);
|
|
136
|
+
const result = dt.add(duration);
|
|
137
|
+
return result.toString();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// DATETIME - TIMESPAN → DATETIME
|
|
141
|
+
if (operator === '-' && isV1DateTime && isV2Timespan) {
|
|
142
|
+
const dt = Temporal.PlainDateTime.from(v1 as string);
|
|
143
|
+
const duration = Temporal.Duration.from(v2 as string);
|
|
144
|
+
const result = dt.subtract(duration);
|
|
145
|
+
return result.toString();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// TIME + TIMESPAN → TIME
|
|
149
|
+
if (operator === '+' && isV1Time && isV2Timespan) {
|
|
150
|
+
const time = Temporal.PlainTime.from(v1 as string);
|
|
151
|
+
const duration = Temporal.Duration.from(v2 as string);
|
|
152
|
+
const result = time.add(duration);
|
|
153
|
+
return result.toString();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// TIMESPAN + TIME → TIME (commutative)
|
|
157
|
+
if (operator === '+' && isV1Timespan && isV2Time) {
|
|
158
|
+
const duration = Temporal.Duration.from(v1 as string);
|
|
159
|
+
const time = Temporal.PlainTime.from(v2 as string);
|
|
160
|
+
const result = time.add(duration);
|
|
161
|
+
return result.toString();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// TIME - TIMESPAN → TIME
|
|
165
|
+
if (operator === '-' && isV1Time && isV2Timespan) {
|
|
166
|
+
const time = Temporal.PlainTime.from(v1 as string);
|
|
167
|
+
const duration = Temporal.Duration.from(v2 as string);
|
|
168
|
+
const result = time.subtract(duration);
|
|
169
|
+
return result.toString();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// TIMESPAN + TIMESPAN → TIMESPAN
|
|
173
|
+
if (operator === '+' && isV1Timespan && isV2Timespan) {
|
|
174
|
+
const d1 = Temporal.Duration.from(v1 as string);
|
|
175
|
+
const d2 = Temporal.Duration.from(v2 as string);
|
|
176
|
+
const result = d1.add(d2);
|
|
177
|
+
return result.toString();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// TIMESPAN - TIMESPAN → TIMESPAN
|
|
181
|
+
if (operator === '-' && isV1Timespan && isV2Timespan) {
|
|
182
|
+
const d1 = Temporal.Duration.from(v1 as string);
|
|
183
|
+
const d2 = Temporal.Duration.from(v2 as string);
|
|
184
|
+
const result = d1.subtract(d2);
|
|
185
|
+
return result.toString();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// TIMESPAN * NUMBER → TIMESPAN
|
|
189
|
+
if (operator === '*' && isV1Timespan && typeof v2 === 'number') {
|
|
190
|
+
const duration = Temporal.Duration.from(v1 as string);
|
|
191
|
+
// Convert to seconds, multiply, convert back
|
|
192
|
+
const totalSeconds = duration.total({ unit: 'seconds' });
|
|
193
|
+
const newDuration = Temporal.Duration.from({ seconds: totalSeconds * v2 });
|
|
194
|
+
return newDuration.toString();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// NUMBER * TIMESPAN → TIMESPAN (commutative)
|
|
198
|
+
if (operator === '*' && typeof v1 === 'number' && isV2Timespan) {
|
|
199
|
+
const duration = Temporal.Duration.from(v2 as string);
|
|
200
|
+
const totalSeconds = duration.total({ unit: 'seconds' });
|
|
201
|
+
const newDuration = Temporal.Duration.from({ seconds: totalSeconds * v1 });
|
|
202
|
+
return newDuration.toString();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// TIMESPAN / NUMBER → TIMESPAN
|
|
206
|
+
if (operator === '/' && isV1Timespan && typeof v2 === 'number') {
|
|
207
|
+
if (v2 === 0) return null;
|
|
208
|
+
const duration = Temporal.Duration.from(v1 as string);
|
|
209
|
+
const totalSeconds = duration.total({ unit: 'seconds' });
|
|
210
|
+
const newDuration = Temporal.Duration.from({ seconds: totalSeconds / v2 });
|
|
211
|
+
return newDuration.toString();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// TIMESPAN / TIMESPAN → NUMBER (ratio)
|
|
215
|
+
if (operator === '/' && isV1Timespan && isV2Timespan) {
|
|
216
|
+
const d1 = Temporal.Duration.from(v1 as string);
|
|
217
|
+
const d2 = Temporal.Duration.from(v2 as string);
|
|
218
|
+
const total1 = d1.total({ unit: 'seconds' });
|
|
219
|
+
const total2 = d2.total({ unit: 'seconds' });
|
|
220
|
+
if (total2 === 0) return null;
|
|
221
|
+
return total1 / total2;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// If we get here, the operation is not supported
|
|
225
|
+
throw new QuereusError(
|
|
226
|
+
`Unsupported temporal operation`,
|
|
227
|
+
StatusCode.UNSUPPORTED
|
|
228
|
+
);
|
|
229
|
+
} catch (e) {
|
|
230
|
+
// Invalid temporal operation - return null
|
|
231
|
+
if (e instanceof QuereusError) throw e;
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Emit temporal arithmetic operations
|
|
238
|
+
* Handles operations between temporal types (DATE, TIME, DATETIME, TIMESPAN)
|
|
239
|
+
*/
|
|
240
|
+
export function emitTemporalArithmetic(plan: BinaryOpNode, ctx: EmissionContext): Instruction {
|
|
241
|
+
const operator = plan.expression.operator;
|
|
242
|
+
|
|
243
|
+
function run(ctx: RuntimeContext, v1: SqlValue, v2: SqlValue): SqlValue {
|
|
244
|
+
return tryTemporalArithmetic(operator, v1, v2) ?? null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const leftExpr = emitPlanNode(plan.left, ctx);
|
|
248
|
+
const rightExpr = emitPlanNode(plan.right, ctx);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
params: [leftExpr, rightExpr],
|
|
252
|
+
run: run as InstructionRun,
|
|
253
|
+
note: `${operator}(temporal)`
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Attempts to perform temporal comparison. Returns undefined if not a temporal comparison.
|
|
259
|
+
* This allows the caller to fall back to standard comparison logic.
|
|
260
|
+
*
|
|
261
|
+
* Temporal types that need special comparison logic (beyond lexicographic string comparison):
|
|
262
|
+
* - TIMESPAN: Durations need to be compared semantically, not lexicographically
|
|
263
|
+
* (e.g., "PT30M" > "PT1H" lexicographically, but 30 minutes < 1 hour semantically)
|
|
264
|
+
*
|
|
265
|
+
* Note: DATE, TIME, and DATETIME use ISO 8601 format which compares correctly lexicographically,
|
|
266
|
+
* so they don't need special handling here.
|
|
267
|
+
*
|
|
268
|
+
* @param operator The comparison operator (=, !=, <, <=, >, >=)
|
|
269
|
+
* @param v1 First value
|
|
270
|
+
* @param v2 Second value
|
|
271
|
+
* @returns Comparison result (boolean) if temporal comparison, undefined otherwise
|
|
272
|
+
*/
|
|
273
|
+
export function tryTemporalComparison(operator: string, v1: SqlValue, v2: SqlValue): SqlValue | undefined {
|
|
274
|
+
// Check if both values are timespans
|
|
275
|
+
// Timespans are the only temporal type that needs special comparison logic
|
|
276
|
+
// because ISO 8601 duration strings don't compare correctly lexicographically
|
|
277
|
+
if (!isTimespanValue(v1) || !isTimespanValue(v2)) {
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Use the TIMESPAN_TYPE's compare function
|
|
282
|
+
const cmp = TIMESPAN_TYPE.compare!(v1, v2);
|
|
283
|
+
|
|
284
|
+
switch (operator) {
|
|
285
|
+
case '=':
|
|
286
|
+
case '==':
|
|
287
|
+
return cmp === 0;
|
|
288
|
+
case '!=':
|
|
289
|
+
case '<>':
|
|
290
|
+
return cmp !== 0;
|
|
291
|
+
case '<':
|
|
292
|
+
return cmp < 0;
|
|
293
|
+
case '<=':
|
|
294
|
+
return cmp <= 0;
|
|
295
|
+
case '>':
|
|
296
|
+
return cmp > 0;
|
|
297
|
+
case '>=':
|
|
298
|
+
return cmp >= 0;
|
|
299
|
+
default:
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -6,6 +6,7 @@ import type { UnaryOpNode } from "../../planner/nodes/scalar.js";
|
|
|
6
6
|
import { emitPlanNode } from "../emitters.js";
|
|
7
7
|
import type { EmissionContext } from "../emission-context.js";
|
|
8
8
|
import { isTruthy } from "../../util/comparison.js";
|
|
9
|
+
import { Temporal } from 'temporal-polyfill';
|
|
9
10
|
|
|
10
11
|
export function emitUnaryOp(plan: UnaryOpNode, ctx: EmissionContext): Instruction {
|
|
11
12
|
// Select the operation function at emit time
|
|
@@ -18,23 +19,23 @@ export function emitUnaryOp(plan: UnaryOpNode, ctx: EmissionContext): Instructio
|
|
|
18
19
|
switch (operator) {
|
|
19
20
|
case 'NOT':
|
|
20
21
|
run = (ctx: RuntimeContext, operand: SqlValue) => {
|
|
21
|
-
// SQL NOT: NULL -> NULL,
|
|
22
|
+
// SQL NOT: NULL -> NULL, false -> true, true -> false
|
|
22
23
|
if (operand === null) return null;
|
|
23
|
-
return isTruthy(operand)
|
|
24
|
+
return !isTruthy(operand);
|
|
24
25
|
};
|
|
25
26
|
note = 'NOT';
|
|
26
27
|
break;
|
|
27
28
|
|
|
28
29
|
case 'IS NULL':
|
|
29
30
|
run = (ctx: RuntimeContext, operand: SqlValue) => {
|
|
30
|
-
return operand === null
|
|
31
|
+
return operand === null;
|
|
31
32
|
};
|
|
32
33
|
note = 'IS NULL';
|
|
33
34
|
break;
|
|
34
35
|
|
|
35
36
|
case 'IS NOT NULL':
|
|
36
37
|
run = (ctx: RuntimeContext, operand: SqlValue) => {
|
|
37
|
-
return operand !== null
|
|
38
|
+
return operand !== null;
|
|
38
39
|
};
|
|
39
40
|
note = 'IS NOT NULL';
|
|
40
41
|
break;
|
|
@@ -42,6 +43,18 @@ export function emitUnaryOp(plan: UnaryOpNode, ctx: EmissionContext): Instructio
|
|
|
42
43
|
case '-':
|
|
43
44
|
run = (ctx: RuntimeContext, operand: SqlValue) => {
|
|
44
45
|
if (operand === null) return null;
|
|
46
|
+
|
|
47
|
+
// Check if it's a timespan (ISO 8601 duration string)
|
|
48
|
+
if (typeof operand === 'string' && (operand.startsWith('P') || operand.startsWith('-P'))) {
|
|
49
|
+
try {
|
|
50
|
+
const duration = Temporal.Duration.from(operand);
|
|
51
|
+
return duration.negated().toString();
|
|
52
|
+
} catch {
|
|
53
|
+
// Not a valid duration, fall through to numeric handling
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Numeric negation
|
|
45
58
|
if (typeof operand === 'number') return -operand;
|
|
46
59
|
if (typeof operand === 'bigint') return -operand;
|
|
47
60
|
// Try to convert to number
|
package/src/schema/catalog.ts
CHANGED
|
@@ -101,7 +101,7 @@ function tableSchemaToCatalog(tableSchema: TableSchema): CatalogTable {
|
|
|
101
101
|
|
|
102
102
|
const columns = tableSchema.columns.map(col => ({
|
|
103
103
|
name: col.name,
|
|
104
|
-
type: col.
|
|
104
|
+
type: col.logicalType.name,
|
|
105
105
|
notNull: col.notNull,
|
|
106
106
|
primaryKey: col.primaryKey
|
|
107
107
|
}));
|
|
@@ -165,8 +165,8 @@ function generateTableDDL(tableSchema: TableSchema): string {
|
|
|
165
165
|
const columnDefs: string[] = [];
|
|
166
166
|
for (const col of tableSchema.columns) {
|
|
167
167
|
let colDef = `"${col.name}"`;
|
|
168
|
-
if (col.
|
|
169
|
-
colDef += ` ${col.
|
|
168
|
+
if (col.logicalType) {
|
|
169
|
+
colDef += ` ${col.logicalType.name}`;
|
|
170
170
|
}
|
|
171
171
|
if (col.notNull) {
|
|
172
172
|
colDef += ' NOT NULL';
|
package/src/schema/column.ts
CHANGED
|
@@ -10,8 +10,6 @@ export interface ColumnSchema {
|
|
|
10
10
|
name: string;
|
|
11
11
|
/** Logical type definition */
|
|
12
12
|
logicalType: LogicalType;
|
|
13
|
-
/** Data type affinity (TEXT, INTEGER, REAL, BLOB, NUMERIC) - kept for backward compatibility during transition */
|
|
14
|
-
affinity: SqlDataType;
|
|
15
13
|
/** Whether the column has a NOT NULL constraint */
|
|
16
14
|
notNull: boolean;
|
|
17
15
|
/** Whether the column is part of the primary key */
|
|
@@ -43,7 +41,6 @@ export function createDefaultColumnSchema(name: string, defaultNotNull: boolean
|
|
|
43
41
|
return {
|
|
44
42
|
name: name,
|
|
45
43
|
logicalType: TEXT_TYPE,
|
|
46
|
-
affinity: SqlDataType.TEXT,
|
|
47
44
|
notNull: defaultNotNull, // Third Manifesto: default to NOT NULL
|
|
48
45
|
primaryKey: false,
|
|
49
46
|
pkOrder: 0,
|