@quereus/quereus 0.5.1 → 0.6.0
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/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/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 +56 -47
- 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/binary.d.ts.map +1 -1
- package/dist/src/runtime/emit/binary.js +40 -2
- package/dist/src/runtime/emit/binary.js.map +1 -1
- package/dist/src/runtime/emit/set-operation.d.ts.map +1 -1
- package/dist/src/runtime/emit/set-operation.js +33 -22
- package/dist/src/runtime/emit/set-operation.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 +12 -0
- 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/ast-stringify.js +1 -1
- package/dist/src/util/ast-stringify.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/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/scalar.ts +205 -14
- package/src/func/builtins/schema.ts +18 -18
- package/src/func/builtins/string.ts +91 -78
- 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/binary.ts +48 -2
- package/src/runtime/emit/set-operation.ts +52 -22
- package/src/runtime/emit/temporal-arithmetic.ts +302 -0
- package/src/runtime/emit/unary.ts +13 -0
- 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/ast-stringify.ts +1 -1
- 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
|
@@ -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 (0 or 1) 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 ? 1 : 0;
|
|
288
|
+
case '!=':
|
|
289
|
+
case '<>':
|
|
290
|
+
return cmp !== 0 ? 1 : 0;
|
|
291
|
+
case '<':
|
|
292
|
+
return cmp < 0 ? 1 : 0;
|
|
293
|
+
case '<=':
|
|
294
|
+
return cmp <= 0 ? 1 : 0;
|
|
295
|
+
case '>':
|
|
296
|
+
return cmp > 0 ? 1 : 0;
|
|
297
|
+
case '>=':
|
|
298
|
+
return cmp >= 0 ? 1 : 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
|
|
@@ -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,
|
package/src/schema/function.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { MaybePromise, Row, SqlValue } from '../common/types.js';
|
|
1
|
+
import type { MaybePromise, Row, SqlValue, DeepReadonly } from '../common/types.js';
|
|
2
2
|
import { FunctionFlags } from '../common/constants.js';
|
|
3
3
|
import { SqlDataType } from '../common/types.js';
|
|
4
4
|
import type { Database } from '../core/database.js';
|
|
5
5
|
import type { BaseType, ScalarType, RelationType } from '../common/datatype.js';
|
|
6
6
|
import type { AggValue } from '../func/registration.js';
|
|
7
|
+
import type { LogicalType } from '../types/logical-type.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Type for a scalar function implementation.
|
|
@@ -74,6 +75,21 @@ export interface ScalarFunctionSchema extends BaseFunctionSchema {
|
|
|
74
75
|
returnType: ScalarType;
|
|
75
76
|
/** Direct scalar function implementation */
|
|
76
77
|
implementation: ScalarFunc;
|
|
78
|
+
/**
|
|
79
|
+
* Optional type inference function for polymorphic functions.
|
|
80
|
+
* If provided, this function will be called at planning time to determine
|
|
81
|
+
* the return type based on the actual argument types.
|
|
82
|
+
* This allows functions like abs() to return INTEGER when given INTEGER,
|
|
83
|
+
* and REAL when given REAL.
|
|
84
|
+
*/
|
|
85
|
+
inferReturnType?: (argTypes: ReadonlyArray<DeepReadonly<LogicalType>>) => ScalarType;
|
|
86
|
+
/**
|
|
87
|
+
* Optional argument type validation function.
|
|
88
|
+
* If provided, this function will be called at planning time to validate
|
|
89
|
+
* that the argument types are acceptable for this function.
|
|
90
|
+
* Should return true if types are valid, false otherwise.
|
|
91
|
+
*/
|
|
92
|
+
validateArgTypes?: (argTypes: ReadonlyArray<DeepReadonly<LogicalType>>) => boolean;
|
|
77
93
|
}
|
|
78
94
|
|
|
79
95
|
/**
|
|
@@ -98,6 +114,18 @@ export interface AggregateFunctionSchema extends BaseFunctionSchema {
|
|
|
98
114
|
finalizeFunction: AggregateFinalizer;
|
|
99
115
|
/** Initial accumulator value for aggregates */
|
|
100
116
|
initialValue?: AggValue;
|
|
117
|
+
/**
|
|
118
|
+
* Optional type inference function for polymorphic aggregate functions.
|
|
119
|
+
* If provided, this function will be called at planning time to determine
|
|
120
|
+
* the return type based on the actual argument types.
|
|
121
|
+
*/
|
|
122
|
+
inferReturnType?: (argTypes: ReadonlyArray<DeepReadonly<LogicalType>>) => ScalarType;
|
|
123
|
+
/**
|
|
124
|
+
* Optional argument type validation function.
|
|
125
|
+
* If provided, this function will be called at planning time to validate
|
|
126
|
+
* that the argument types are acceptable for this function.
|
|
127
|
+
*/
|
|
128
|
+
validateArgTypes?: (argTypes: ReadonlyArray<DeepReadonly<LogicalType>>) => boolean;
|
|
101
129
|
}
|
|
102
130
|
|
|
103
131
|
/**
|
package/src/schema/table.ts
CHANGED
|
@@ -3,7 +3,6 @@ import type { AnyVirtualTableModule } from '../vtab/module.js';
|
|
|
3
3
|
import { MemoryTableModule } from '../vtab/memory/module.js';
|
|
4
4
|
import type { Expression } from '../parser/ast.js';
|
|
5
5
|
import { type ColumnDef, type TableConstraint } from '../parser/ast.js';
|
|
6
|
-
import { getAffinity } from '../common/type-inference.js';
|
|
7
6
|
import { RowOp, SqlDataType, StatusCode, type SqlValue } from '../common/types.js';
|
|
8
7
|
import type * as AST from '../parser/ast.js';
|
|
9
8
|
import { quereusError, QuereusError } from '../common/errors.js';
|
|
@@ -99,7 +98,6 @@ export function columnDefToSchema(def: ColumnDef, defaultNotNull: boolean = true
|
|
|
99
98
|
const schema: Partial<ColumnSchema> & { name: string } = {
|
|
100
99
|
name: def.name,
|
|
101
100
|
logicalType: logicalType,
|
|
102
|
-
affinity: getAffinity(def.dataType), // Keep for backward compatibility during transition
|
|
103
101
|
notNull: defaultNotNull, // Default based on Third Manifesto principles
|
|
104
102
|
primaryKey: false,
|
|
105
103
|
pkOrder: 0,
|
|
@@ -166,8 +164,8 @@ export function columnDefToSchema(def: ColumnDef, defaultNotNull: boolean = true
|
|
|
166
164
|
export interface MutationContextDefinition {
|
|
167
165
|
/** Variable name */
|
|
168
166
|
name: string;
|
|
169
|
-
/**
|
|
170
|
-
|
|
167
|
+
/** Logical type of the variable */
|
|
168
|
+
logicalType: import('../types/logical-type.js').LogicalType;
|
|
171
169
|
/** Whether the variable is NOT NULL */
|
|
172
170
|
notNull: boolean;
|
|
173
171
|
}
|
|
@@ -182,7 +180,7 @@ export interface MutationContextDefinition {
|
|
|
182
180
|
export function mutationContextVarToSchema(varDef: AST.MutationContextVar, defaultNotNull: boolean = true): MutationContextDefinition {
|
|
183
181
|
return {
|
|
184
182
|
name: varDef.name,
|
|
185
|
-
|
|
183
|
+
logicalType: inferType(varDef.dataType),
|
|
186
184
|
notNull: varDef.notNull !== undefined ? varDef.notNull : defaultNotNull,
|
|
187
185
|
};
|
|
188
186
|
}
|
|
@@ -403,8 +401,8 @@ function findColumnPKDefinition(columns: ReadonlyArray<ColumnSchema>): ReadonlyA
|
|
|
403
401
|
|
|
404
402
|
return Object.freeze(pkCols.map(col => ({
|
|
405
403
|
index: col.originalIndex,
|
|
406
|
-
desc: col.
|
|
407
|
-
autoIncrement: col.
|
|
404
|
+
desc: col.logicalType.name === 'INTEGER' && col.pkDirection === 'desc',
|
|
405
|
+
autoIncrement: col.logicalType.name === 'INTEGER',
|
|
408
406
|
collation: col.collation || 'BINARY'
|
|
409
407
|
})));
|
|
410
408
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { PhysicalType, type LogicalType, getPhysicalType } from './logical-type.
|
|
|
5
5
|
export { NULL_TYPE, INTEGER_TYPE, REAL_TYPE, TEXT_TYPE, BLOB_TYPE, BOOLEAN_TYPE, NUMERIC_TYPE, ANY_TYPE } from './builtin-types.js';
|
|
6
6
|
|
|
7
7
|
// Temporal types
|
|
8
|
-
export { DATE_TYPE, TIME_TYPE, DATETIME_TYPE } from './temporal-types.js';
|
|
8
|
+
export { DATE_TYPE, TIME_TYPE, DATETIME_TYPE, TIMESPAN_TYPE } from './temporal-types.js';
|
|
9
9
|
|
|
10
10
|
// JSON type
|
|
11
11
|
export { JSON_TYPE } from './json-type.js';
|
package/src/types/registry.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
NUMERIC_TYPE,
|
|
10
10
|
ANY_TYPE,
|
|
11
11
|
} from './builtin-types.js';
|
|
12
|
-
import { DATE_TYPE, TIME_TYPE, DATETIME_TYPE } from './temporal-types.js';
|
|
12
|
+
import { DATE_TYPE, TIME_TYPE, DATETIME_TYPE, TIMESPAN_TYPE } from './temporal-types.js';
|
|
13
13
|
import { JSON_TYPE } from './json-type.js';
|
|
14
14
|
import { createLogger } from '../common/logger.js';
|
|
15
15
|
|
|
@@ -36,9 +36,13 @@ class TypeRegistry {
|
|
|
36
36
|
this.registerType(DATE_TYPE);
|
|
37
37
|
this.registerType(TIME_TYPE);
|
|
38
38
|
this.registerType(DATETIME_TYPE);
|
|
39
|
+
this.registerType(TIMESPAN_TYPE);
|
|
39
40
|
this.registerType(JSON_TYPE);
|
|
40
41
|
|
|
41
42
|
// Register common aliases
|
|
43
|
+
// Temporal type aliases
|
|
44
|
+
this.types.set('INTERVAL', TIMESPAN_TYPE); // SQL standard alias
|
|
45
|
+
this.types.set('DURATION', TIMESPAN_TYPE); // Alternative name
|
|
42
46
|
this.types.set('INT', INTEGER_TYPE);
|
|
43
47
|
this.types.set('BIGINT', INTEGER_TYPE);
|
|
44
48
|
this.types.set('SMALLINT', INTEGER_TYPE);
|
|
@@ -165,3 +165,126 @@ export const DATETIME_TYPE: LogicalType = {
|
|
|
165
165
|
supportedCollations: [],
|
|
166
166
|
};
|
|
167
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Parse human-readable duration strings into Temporal.Duration
|
|
170
|
+
* Supports formats like "1 hour", "30 minutes", "2 days 3 hours"
|
|
171
|
+
*/
|
|
172
|
+
function parseHumanReadableDuration(input: string): Temporal.Duration | null {
|
|
173
|
+
const normalized = input.trim().toLowerCase();
|
|
174
|
+
|
|
175
|
+
// Handle negative durations
|
|
176
|
+
const isNegative = normalized.startsWith('-');
|
|
177
|
+
const workingInput = isNegative ? normalized.substring(1).trim() : normalized;
|
|
178
|
+
|
|
179
|
+
// Pattern: [number] [unit]
|
|
180
|
+
// Units: year(s), month(s), week(s), day(s), hour(s), minute(s), second(s), min(s), sec(s)
|
|
181
|
+
const pattern = /(\d+(?:\.\d+)?)\s*(years?|months?|weeks?|days?|hours?|minutes?|seconds?|mins?|secs?)/g;
|
|
182
|
+
|
|
183
|
+
const components: Record<string, number> = {};
|
|
184
|
+
let match;
|
|
185
|
+
let hasMatch = false;
|
|
186
|
+
|
|
187
|
+
while ((match = pattern.exec(workingInput)) !== null) {
|
|
188
|
+
hasMatch = true;
|
|
189
|
+
const value = parseFloat(match[1]);
|
|
190
|
+
const unit = match[2];
|
|
191
|
+
|
|
192
|
+
// Map unit to Temporal.Duration field
|
|
193
|
+
if (unit.startsWith('year')) {
|
|
194
|
+
components.years = (components.years || 0) + value;
|
|
195
|
+
} else if (unit.startsWith('month')) {
|
|
196
|
+
components.months = (components.months || 0) + value;
|
|
197
|
+
} else if (unit.startsWith('week')) {
|
|
198
|
+
components.weeks = (components.weeks || 0) + value;
|
|
199
|
+
} else if (unit.startsWith('day')) {
|
|
200
|
+
components.days = (components.days || 0) + value;
|
|
201
|
+
} else if (unit.startsWith('hour')) {
|
|
202
|
+
components.hours = (components.hours || 0) + value;
|
|
203
|
+
} else if (unit.startsWith('min')) {
|
|
204
|
+
components.minutes = (components.minutes || 0) + value;
|
|
205
|
+
} else if (unit.startsWith('sec')) {
|
|
206
|
+
components.seconds = (components.seconds || 0) + value;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!hasMatch) return null;
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const duration = Temporal.Duration.from(components);
|
|
214
|
+
return isNegative ? duration.negated() : duration;
|
|
215
|
+
} catch {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* TIMESPAN type - stores ISO 8601 duration strings
|
|
222
|
+
* Uses Temporal.Duration for validation and parsing
|
|
223
|
+
*/
|
|
224
|
+
export const TIMESPAN_TYPE: LogicalType = {
|
|
225
|
+
name: 'TIMESPAN',
|
|
226
|
+
physicalType: PhysicalType.TEXT,
|
|
227
|
+
isTemporal: true,
|
|
228
|
+
|
|
229
|
+
validate: (v) => {
|
|
230
|
+
if (v === null) return true;
|
|
231
|
+
if (typeof v !== 'string') return false;
|
|
232
|
+
try {
|
|
233
|
+
Temporal.Duration.from(v);
|
|
234
|
+
return true;
|
|
235
|
+
} catch {
|
|
236
|
+
// Try parsing human-readable format
|
|
237
|
+
return parseHumanReadableDuration(v) !== null;
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
parse: (v) => {
|
|
242
|
+
if (v === null) return null;
|
|
243
|
+
|
|
244
|
+
if (typeof v === 'number') {
|
|
245
|
+
// Interpret as seconds
|
|
246
|
+
const duration = Temporal.Duration.from({ seconds: v });
|
|
247
|
+
return duration.toString();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (typeof v === 'string') {
|
|
251
|
+
try {
|
|
252
|
+
// Try ISO 8601 first
|
|
253
|
+
const duration = Temporal.Duration.from(v);
|
|
254
|
+
return duration.toString();
|
|
255
|
+
} catch {
|
|
256
|
+
// Try human-readable format
|
|
257
|
+
const duration = parseHumanReadableDuration(v);
|
|
258
|
+
if (duration) return duration.toString();
|
|
259
|
+
throw new TypeError(`Cannot convert '${v}' to TIMESPAN`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
throw new TypeError(`Cannot convert ${typeof v} to TIMESPAN`);
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
compare: (a, b) => {
|
|
267
|
+
if (a === null && b === null) return 0;
|
|
268
|
+
if (a === null) return -1;
|
|
269
|
+
if (b === null) return 1;
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const durationA = Temporal.Duration.from(a as string);
|
|
273
|
+
const durationB = Temporal.Duration.from(b as string);
|
|
274
|
+
|
|
275
|
+
// Use a reference date to resolve calendar units
|
|
276
|
+
// This ensures consistent comparison of durations with months/years
|
|
277
|
+
const referenceDate = Temporal.PlainDate.from('2024-01-01');
|
|
278
|
+
const totalA = durationA.total({ unit: 'seconds', relativeTo: referenceDate });
|
|
279
|
+
const totalB = durationB.total({ unit: 'seconds', relativeTo: referenceDate });
|
|
280
|
+
|
|
281
|
+
return totalA < totalB ? -1 : totalA > totalB ? 1 : 0;
|
|
282
|
+
} catch {
|
|
283
|
+
// If parsing fails, fall back to string comparison
|
|
284
|
+
return (a as string).localeCompare(b as string);
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
supportedCollations: [],
|
|
289
|
+
};
|
|
290
|
+
|
|
@@ -167,7 +167,7 @@ export function expressionToString(expr: AST.Expression): string {
|
|
|
167
167
|
// Handle postfix operators like IS NULL, IS NOT NULL
|
|
168
168
|
if (expr.operator === 'IS NULL' || expr.operator === 'IS NOT NULL') {
|
|
169
169
|
return `${exprStr} ${expr.operator.toLowerCase()}`;
|
|
170
|
-
} else if (expr.operator === 'NOT') {
|
|
170
|
+
} else if (expr.operator.toUpperCase() === 'NOT') {
|
|
171
171
|
return `${expr.operator.toLowerCase()} ${exprStr}`;
|
|
172
172
|
}
|
|
173
173
|
return `${expr.operator.toLowerCase()}${exprStr}`;
|
|
@@ -21,10 +21,7 @@ export function formatExpressionList(nodes: readonly ScalarPlanNode[]): string {
|
|
|
21
21
|
* Format a scalar type to a simple string representation.
|
|
22
22
|
*/
|
|
23
23
|
export function formatScalarType(type: ScalarType): string {
|
|
24
|
-
|
|
25
|
-
return SqlDataType[type.datatype];
|
|
26
|
-
}
|
|
27
|
-
return SqlDataType[type.affinity];
|
|
24
|
+
return type.logicalType.name;
|
|
28
25
|
}
|
|
29
26
|
|
|
30
27
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RowDescriptor, Attribute } from '../planner/nodes/plan-node.js';
|
|
2
2
|
import type { Row } from '../common/types.js';
|
|
3
|
-
import {
|
|
3
|
+
import { TEXT_TYPE } from '../types/builtin-types.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Utility to build a RowDescriptor (attributeId → columnIndex mapping)
|
|
@@ -94,7 +94,7 @@ export function buildAttributesFromFlatDescriptor(flatRowDescriptor: RowDescript
|
|
|
94
94
|
name: `attr_${attrId}`,
|
|
95
95
|
type: {
|
|
96
96
|
typeClass: 'scalar' as const,
|
|
97
|
-
|
|
97
|
+
logicalType: TEXT_TYPE,
|
|
98
98
|
nullable: true,
|
|
99
99
|
isReadOnly: false
|
|
100
100
|
},
|