@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.
Files changed (221) hide show
  1. package/README.md +5 -1
  2. package/dist/src/common/datatype.d.ts +4 -5
  3. package/dist/src/common/datatype.d.ts.map +1 -1
  4. package/dist/src/common/datatype.js.map +1 -1
  5. package/dist/src/common/type-inference.d.ts +3 -6
  6. package/dist/src/common/type-inference.d.ts.map +1 -1
  7. package/dist/src/common/type-inference.js +17 -22
  8. package/dist/src/common/type-inference.js.map +1 -1
  9. package/dist/src/core/param.d.ts.map +1 -1
  10. package/dist/src/core/param.js +3 -18
  11. package/dist/src/core/param.js.map +1 -1
  12. package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
  13. package/dist/src/func/builtins/aggregate.js +24 -2
  14. package/dist/src/func/builtins/aggregate.js.map +1 -1
  15. package/dist/src/func/builtins/builtin-window-functions.js +10 -10
  16. package/dist/src/func/builtins/builtin-window-functions.js.map +1 -1
  17. package/dist/src/func/builtins/conversion.d.ts +10 -0
  18. package/dist/src/func/builtins/conversion.d.ts.map +1 -1
  19. package/dist/src/func/builtins/conversion.js +20 -1
  20. package/dist/src/func/builtins/conversion.js.map +1 -1
  21. package/dist/src/func/builtins/explain.js +53 -53
  22. package/dist/src/func/builtins/explain.js.map +1 -1
  23. package/dist/src/func/builtins/generation.js +2 -2
  24. package/dist/src/func/builtins/generation.js.map +1 -1
  25. package/dist/src/func/builtins/index.d.ts.map +1 -1
  26. package/dist/src/func/builtins/index.js +16 -1
  27. package/dist/src/func/builtins/index.js.map +1 -1
  28. package/dist/src/func/builtins/json-tvf.js +17 -17
  29. package/dist/src/func/builtins/json-tvf.js.map +1 -1
  30. package/dist/src/func/builtins/scalar.d.ts.map +1 -1
  31. package/dist/src/func/builtins/scalar.js +202 -13
  32. package/dist/src/func/builtins/scalar.js.map +1 -1
  33. package/dist/src/func/builtins/schema.js +18 -18
  34. package/dist/src/func/builtins/schema.js.map +1 -1
  35. package/dist/src/func/builtins/string.d.ts.map +1 -1
  36. package/dist/src/func/builtins/string.js +56 -47
  37. package/dist/src/func/builtins/string.js.map +1 -1
  38. package/dist/src/func/builtins/timespan.d.ts +45 -0
  39. package/dist/src/func/builtins/timespan.d.ts.map +1 -0
  40. package/dist/src/func/builtins/timespan.js +147 -0
  41. package/dist/src/func/builtins/timespan.js.map +1 -0
  42. package/dist/src/func/registration.d.ts +26 -0
  43. package/dist/src/func/registration.d.ts.map +1 -1
  44. package/dist/src/func/registration.js +9 -5
  45. package/dist/src/func/registration.js.map +1 -1
  46. package/dist/src/index.d.ts +1 -1
  47. package/dist/src/index.d.ts.map +1 -1
  48. package/dist/src/index.js +1 -1
  49. package/dist/src/index.js.map +1 -1
  50. package/dist/src/parser/parser.js +2 -2
  51. package/dist/src/parser/parser.js.map +1 -1
  52. package/dist/src/planner/building/constraint-builder.js +2 -2
  53. package/dist/src/planner/building/constraint-builder.js.map +1 -1
  54. package/dist/src/planner/building/delete.js +3 -3
  55. package/dist/src/planner/building/delete.js.map +1 -1
  56. package/dist/src/planner/building/function-call.d.ts.map +1 -1
  57. package/dist/src/planner/building/function-call.js +24 -4
  58. package/dist/src/planner/building/function-call.js.map +1 -1
  59. package/dist/src/planner/building/insert.js +3 -3
  60. package/dist/src/planner/building/insert.js.map +1 -1
  61. package/dist/src/planner/building/select.d.ts.map +1 -1
  62. package/dist/src/planner/building/select.js +3 -2
  63. package/dist/src/planner/building/select.js.map +1 -1
  64. package/dist/src/planner/building/update.js +7 -7
  65. package/dist/src/planner/building/update.js.map +1 -1
  66. package/dist/src/planner/nodes/aggregate-function.d.ts +2 -1
  67. package/dist/src/planner/nodes/aggregate-function.d.ts.map +1 -1
  68. package/dist/src/planner/nodes/aggregate-function.js +10 -3
  69. package/dist/src/planner/nodes/aggregate-function.js.map +1 -1
  70. package/dist/src/planner/nodes/cte-node.d.ts.map +1 -1
  71. package/dist/src/planner/nodes/cte-node.js +2 -2
  72. package/dist/src/planner/nodes/cte-node.js.map +1 -1
  73. package/dist/src/planner/nodes/declarative-schema.js +3 -3
  74. package/dist/src/planner/nodes/declarative-schema.js.map +1 -1
  75. package/dist/src/planner/nodes/function.d.ts +2 -1
  76. package/dist/src/planner/nodes/function.d.ts.map +1 -1
  77. package/dist/src/planner/nodes/function.js +6 -3
  78. package/dist/src/planner/nodes/function.js.map +1 -1
  79. package/dist/src/planner/nodes/insert-node.js +1 -1
  80. package/dist/src/planner/nodes/insert-node.js.map +1 -1
  81. package/dist/src/planner/nodes/pragma.d.ts +1 -1
  82. package/dist/src/planner/nodes/pragma.d.ts.map +1 -1
  83. package/dist/src/planner/nodes/pragma.js +3 -3
  84. package/dist/src/planner/nodes/pragma.js.map +1 -1
  85. package/dist/src/planner/nodes/reference.js +1 -1
  86. package/dist/src/planner/nodes/reference.js.map +1 -1
  87. package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
  88. package/dist/src/planner/nodes/scalar.js +55 -101
  89. package/dist/src/planner/nodes/scalar.js.map +1 -1
  90. package/dist/src/planner/nodes/sequencing-node.js +2 -2
  91. package/dist/src/planner/nodes/sequencing-node.js.map +1 -1
  92. package/dist/src/planner/nodes/sink-node.js +2 -2
  93. package/dist/src/planner/nodes/sink-node.js.map +1 -1
  94. package/dist/src/planner/nodes/subquery.d.ts.map +1 -1
  95. package/dist/src/planner/nodes/subquery.js +4 -7
  96. package/dist/src/planner/nodes/subquery.js.map +1 -1
  97. package/dist/src/planner/nodes/view-reference-node.d.ts.map +1 -1
  98. package/dist/src/planner/nodes/view-reference-node.js +2 -2
  99. package/dist/src/planner/nodes/view-reference-node.js.map +1 -1
  100. package/dist/src/planner/nodes/window-function.js +3 -3
  101. package/dist/src/planner/nodes/window-function.js.map +1 -1
  102. package/dist/src/planner/rules/access/rule-select-access-path.js +1 -1
  103. package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
  104. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +1 -1
  105. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
  106. package/dist/src/planner/scopes/global.js +3 -3
  107. package/dist/src/planner/scopes/global.js.map +1 -1
  108. package/dist/src/planner/scopes/param.d.ts.map +1 -1
  109. package/dist/src/planner/scopes/param.js +2 -2
  110. package/dist/src/planner/scopes/param.js.map +1 -1
  111. package/dist/src/planner/type-utils.d.ts +2 -12
  112. package/dist/src/planner/type-utils.d.ts.map +1 -1
  113. package/dist/src/planner/type-utils.js +6 -21
  114. package/dist/src/planner/type-utils.js.map +1 -1
  115. package/dist/src/runtime/emit/binary.d.ts.map +1 -1
  116. package/dist/src/runtime/emit/binary.js +40 -2
  117. package/dist/src/runtime/emit/binary.js.map +1 -1
  118. package/dist/src/runtime/emit/set-operation.d.ts.map +1 -1
  119. package/dist/src/runtime/emit/set-operation.js +33 -22
  120. package/dist/src/runtime/emit/set-operation.js.map +1 -1
  121. package/dist/src/runtime/emit/temporal-arithmetic.d.ts +33 -0
  122. package/dist/src/runtime/emit/temporal-arithmetic.d.ts.map +1 -0
  123. package/dist/src/runtime/emit/temporal-arithmetic.js +269 -0
  124. package/dist/src/runtime/emit/temporal-arithmetic.js.map +1 -0
  125. package/dist/src/runtime/emit/unary.d.ts.map +1 -1
  126. package/dist/src/runtime/emit/unary.js +12 -0
  127. package/dist/src/runtime/emit/unary.js.map +1 -1
  128. package/dist/src/schema/catalog.js +3 -3
  129. package/dist/src/schema/catalog.js.map +1 -1
  130. package/dist/src/schema/column.d.ts +0 -3
  131. package/dist/src/schema/column.d.ts.map +1 -1
  132. package/dist/src/schema/column.js +0 -2
  133. package/dist/src/schema/column.js.map +1 -1
  134. package/dist/src/schema/function.d.ts +29 -1
  135. package/dist/src/schema/function.d.ts.map +1 -1
  136. package/dist/src/schema/function.js.map +1 -1
  137. package/dist/src/schema/table.d.ts +3 -3
  138. package/dist/src/schema/table.d.ts.map +1 -1
  139. package/dist/src/schema/table.js +4 -6
  140. package/dist/src/schema/table.js.map +1 -1
  141. package/dist/src/types/index.d.ts +1 -1
  142. package/dist/src/types/index.d.ts.map +1 -1
  143. package/dist/src/types/index.js +1 -1
  144. package/dist/src/types/index.js.map +1 -1
  145. package/dist/src/types/registry.d.ts.map +1 -1
  146. package/dist/src/types/registry.js +5 -1
  147. package/dist/src/types/registry.js.map +1 -1
  148. package/dist/src/types/temporal-types.d.ts +5 -0
  149. package/dist/src/types/temporal-types.d.ts.map +1 -1
  150. package/dist/src/types/temporal-types.js +122 -0
  151. package/dist/src/types/temporal-types.js.map +1 -1
  152. package/dist/src/util/ast-stringify.js +1 -1
  153. package/dist/src/util/ast-stringify.js.map +1 -1
  154. package/dist/src/util/plan-formatter.d.ts.map +1 -1
  155. package/dist/src/util/plan-formatter.js +1 -5
  156. package/dist/src/util/plan-formatter.js.map +1 -1
  157. package/dist/src/util/row-descriptor.js +2 -2
  158. package/dist/src/util/row-descriptor.js.map +1 -1
  159. package/dist/src/vtab/best-access-plan.d.ts +4 -3
  160. package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
  161. package/dist/src/vtab/best-access-plan.js.map +1 -1
  162. package/dist/src/vtab/memory/module.js +1 -1
  163. package/dist/src/vtab/memory/module.js.map +1 -1
  164. package/package.json +1 -1
  165. package/src/common/datatype.ts +4 -5
  166. package/src/common/type-inference.ts +13 -22
  167. package/src/core/param.ts +4 -11
  168. package/src/func/builtins/aggregate.ts +24 -2
  169. package/src/func/builtins/builtin-window-functions.ts +10 -10
  170. package/src/func/builtins/conversion.ts +26 -1
  171. package/src/func/builtins/explain.ts +53 -53
  172. package/src/func/builtins/generation.ts +2 -2
  173. package/src/func/builtins/index.ts +20 -1
  174. package/src/func/builtins/json-tvf.ts +17 -17
  175. package/src/func/builtins/scalar.ts +205 -14
  176. package/src/func/builtins/schema.ts +18 -18
  177. package/src/func/builtins/string.ts +91 -78
  178. package/src/func/builtins/timespan.ts +179 -0
  179. package/src/func/registration.ts +35 -5
  180. package/src/index.ts +2 -1
  181. package/src/parser/parser.ts +2 -2
  182. package/src/planner/building/constraint-builder.ts +2 -2
  183. package/src/planner/building/delete.ts +3 -3
  184. package/src/planner/building/function-call.ts +44 -3
  185. package/src/planner/building/insert.ts +3 -3
  186. package/src/planner/building/select.ts +3 -2
  187. package/src/planner/building/update.ts +7 -7
  188. package/src/planner/nodes/aggregate-function.ts +13 -3
  189. package/src/planner/nodes/cte-node.ts +2 -2
  190. package/src/planner/nodes/declarative-schema.ts +3 -3
  191. package/src/planner/nodes/function.ts +8 -3
  192. package/src/planner/nodes/insert-node.ts +1 -1
  193. package/src/planner/nodes/pragma.ts +4 -3
  194. package/src/planner/nodes/reference.ts +1 -1
  195. package/src/planner/nodes/scalar.ts +54 -102
  196. package/src/planner/nodes/sequencing-node.ts +2 -2
  197. package/src/planner/nodes/sink-node.ts +2 -2
  198. package/src/planner/nodes/subquery.ts +5 -7
  199. package/src/planner/nodes/view-reference-node.ts +2 -2
  200. package/src/planner/nodes/window-function.ts +3 -3
  201. package/src/planner/rules/access/rule-select-access-path.ts +1 -1
  202. package/src/planner/rules/retrieve/rule-grow-retrieve.ts +1 -1
  203. package/src/planner/scopes/global.ts +3 -3
  204. package/src/planner/scopes/param.ts +2 -2
  205. package/src/planner/type-utils.ts +6 -14
  206. package/src/runtime/emit/binary.ts +48 -2
  207. package/src/runtime/emit/set-operation.ts +52 -22
  208. package/src/runtime/emit/temporal-arithmetic.ts +302 -0
  209. package/src/runtime/emit/unary.ts +13 -0
  210. package/src/schema/catalog.ts +3 -3
  211. package/src/schema/column.ts +0 -3
  212. package/src/schema/function.ts +29 -1
  213. package/src/schema/table.ts +5 -7
  214. package/src/types/index.ts +1 -1
  215. package/src/types/registry.ts +5 -1
  216. package/src/types/temporal-types.ts +123 -0
  217. package/src/util/ast-stringify.ts +1 -1
  218. package/src/util/plan-formatter.ts +1 -4
  219. package/src/util/row-descriptor.ts +2 -2
  220. package/src/vtab/best-access-plan.ts +4 -3
  221. 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
@@ -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.affinity?.toString() || 'TEXT',
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.affinity) {
169
- colDef += ` ${col.affinity}`;
168
+ if (col.logicalType) {
169
+ colDef += ` ${col.logicalType.name}`;
170
170
  }
171
171
  if (col.notNull) {
172
172
  colDef += ' NOT NULL';
@@ -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,
@@ -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
  /**
@@ -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
- /** Type affinity of the variable */
170
- affinity: SqlDataType;
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
- affinity: getAffinity(varDef.dataType),
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.affinity === SqlDataType.INTEGER && col.pkDirection === 'desc',
407
- autoIncrement: col.affinity === SqlDataType.INTEGER,
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
  }
@@ -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';
@@ -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
- if (type.datatype !== undefined) {
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 { SqlDataType } from '../common/types.js';
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
- affinity: SqlDataType.TEXT,
97
+ logicalType: TEXT_TYPE,
98
98
  nullable: true,
99
99
  isReadOnly: false
100
100
  },