@quereus/quereus 0.5.2 → 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/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/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/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/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
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { PlanNode, type ScalarPlanNode } from "./plan-node.js";
|
|
2
2
|
import type { ScalarType } from "../../common/datatype.js";
|
|
3
3
|
import type { RelationalPlanNode } from "./plan-node.js";
|
|
4
|
-
import { type CompareFn
|
|
4
|
+
import { type CompareFn } from "../../common/types.js";
|
|
5
5
|
import { PlanNodeType } from "./plan-node-type.js";
|
|
6
6
|
import type { Scope } from "../scopes/scope.js";
|
|
7
7
|
import { compareSqlValues } from "../../util/comparison.js";
|
|
8
8
|
import type { Expression } from "../../parser/ast.js";
|
|
9
9
|
import { formatExpression, formatScalarType } from "../../util/plan-formatter.js";
|
|
10
10
|
import { quereusError } from "../../common/errors.js";
|
|
11
|
+
import { BLOB_TYPE, INTEGER_TYPE } from "../../types/builtin-types.js";
|
|
11
12
|
import { StatusCode } from "../../common/types.js";
|
|
12
13
|
|
|
13
14
|
export class ScalarSubqueryNode extends PlanNode implements ScalarPlanNode {
|
|
@@ -31,10 +32,9 @@ export class ScalarSubqueryNode extends PlanNode implements ScalarPlanNode {
|
|
|
31
32
|
// Fallback to nullable BLOB if we can't determine type
|
|
32
33
|
return {
|
|
33
34
|
typeClass: 'scalar',
|
|
34
|
-
|
|
35
|
+
logicalType: BLOB_TYPE,
|
|
35
36
|
nullable: true,
|
|
36
37
|
isReadOnly: true,
|
|
37
|
-
datatype: SqlDataType.BLOB,
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -103,10 +103,9 @@ export class InNode extends PlanNode implements ScalarPlanNode {
|
|
|
103
103
|
getType(): ScalarType {
|
|
104
104
|
return {
|
|
105
105
|
typeClass: 'scalar',
|
|
106
|
-
|
|
106
|
+
logicalType: INTEGER_TYPE,
|
|
107
107
|
nullable: false,
|
|
108
108
|
isReadOnly: true,
|
|
109
|
-
datatype: SqlDataType.INTEGER,
|
|
110
109
|
}
|
|
111
110
|
}
|
|
112
111
|
|
|
@@ -216,10 +215,9 @@ export class ExistsNode extends PlanNode implements ScalarPlanNode {
|
|
|
216
215
|
getType(): ScalarType {
|
|
217
216
|
return {
|
|
218
217
|
typeClass: 'scalar',
|
|
219
|
-
|
|
218
|
+
logicalType: INTEGER_TYPE,
|
|
220
219
|
nullable: false,
|
|
221
220
|
isReadOnly: true,
|
|
222
|
-
datatype: SqlDataType.INTEGER,
|
|
223
221
|
};
|
|
224
222
|
}
|
|
225
223
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { PlanNode, type ZeroAryRelationalNode, type Attribute } from './plan-node.js';
|
|
2
2
|
import type { RelationType } from '../../common/datatype.js';
|
|
3
|
-
import { SqlDataType } from '../../common/types.js';
|
|
4
3
|
import { PlanNodeType } from './plan-node-type.js';
|
|
5
4
|
import type { Scope } from '../scopes/scope.js';
|
|
6
5
|
import type { ViewSchema } from '../../schema/view.js';
|
|
7
6
|
import { Cached } from '../../util/cached.js';
|
|
7
|
+
import { TEXT_TYPE } from '../../types/builtin-types.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Plan node for referencing a view in a FROM clause.
|
|
@@ -36,7 +36,7 @@ export class ViewReferenceNode extends PlanNode implements ZeroAryRelationalNode
|
|
|
36
36
|
name: columnName,
|
|
37
37
|
type: {
|
|
38
38
|
typeClass: 'scalar' as const,
|
|
39
|
-
|
|
39
|
+
logicalType: TEXT_TYPE,
|
|
40
40
|
nullable: true,
|
|
41
41
|
isReadOnly: false
|
|
42
42
|
}, // Default type, should be inferred
|
|
@@ -4,8 +4,8 @@ import type { ScalarType } from '../../common/datatype.js';
|
|
|
4
4
|
import type { Scope } from '../scopes/scope.js';
|
|
5
5
|
import type { WindowFunctionExpr } from '../../parser/ast.js';
|
|
6
6
|
import { Cached } from '../../util/cached.js';
|
|
7
|
-
import { SqlDataType } from '../../common/types.js';
|
|
8
7
|
import { formatScalarType } from '../../util/plan-formatter.js';
|
|
8
|
+
import { INTEGER_TYPE, REAL_TYPE } from '../../types/builtin-types.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Represents a window function call in the query plan.
|
|
@@ -30,10 +30,10 @@ export class WindowFunctionCallNode extends PlanNode implements ZeroAryScalarNod
|
|
|
30
30
|
// Most window functions return numeric types
|
|
31
31
|
// row_number() specifically returns an integer
|
|
32
32
|
if (this.functionName === 'row_number') {
|
|
33
|
-
return { typeClass: 'scalar',
|
|
33
|
+
return { typeClass: 'scalar', logicalType: INTEGER_TYPE, nullable: false } satisfies ScalarType;
|
|
34
34
|
}
|
|
35
35
|
// Other window functions would have their own type inference
|
|
36
|
-
return { typeClass: 'scalar',
|
|
36
|
+
return { typeClass: 'scalar', logicalType: REAL_TYPE, nullable: false } satisfies ScalarType;
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -120,7 +120,7 @@ function createIndexBasedAccess(retrieveNode: RetrieveNode, context: OptContext)
|
|
|
120
120
|
columns: tableSchema.columns.map((col, index) => ({
|
|
121
121
|
index,
|
|
122
122
|
name: col.name,
|
|
123
|
-
type: col.
|
|
123
|
+
type: col.logicalType,
|
|
124
124
|
isPrimaryKey: col.primaryKey || false,
|
|
125
125
|
isUnique: col.primaryKey || false // For now, assume only PK columns are unique
|
|
126
126
|
} as ColumnMeta)),
|
|
@@ -231,7 +231,7 @@ function fallbackIndexSupports(
|
|
|
231
231
|
columns: tableSchema.columns.map((col, index) => ({
|
|
232
232
|
index,
|
|
233
233
|
name: col.name,
|
|
234
|
-
type: col.
|
|
234
|
+
type: col.logicalType,
|
|
235
235
|
isPrimaryKey: col.primaryKey || false,
|
|
236
236
|
isUnique: col.primaryKey || false
|
|
237
237
|
})),
|
|
@@ -5,8 +5,8 @@ import * as AST from "../../parser/ast.js";
|
|
|
5
5
|
import { FunctionReferenceNode, TableReferenceNode } from "../nodes/reference.js";
|
|
6
6
|
import { Ambiguous } from "./scope.js";
|
|
7
7
|
import type { ScalarType } from "../../common/datatype.js";
|
|
8
|
-
import { SqlDataType } from "../../common/types.js";
|
|
9
8
|
import { isScalarFunctionSchema } from "../../schema/function.js";
|
|
9
|
+
import { REAL_TYPE } from "../../types/builtin-types.js";
|
|
10
10
|
|
|
11
11
|
export class GlobalScope extends BaseScope {
|
|
12
12
|
constructor(public readonly manager: SchemaManager) {
|
|
@@ -25,7 +25,7 @@ export class GlobalScope extends BaseScope {
|
|
|
25
25
|
// Get the proper scalar type from the function schema
|
|
26
26
|
const scalarType: ScalarType = isScalarFunctionSchema(func)
|
|
27
27
|
? func.returnType
|
|
28
|
-
: { typeClass: 'scalar',
|
|
28
|
+
: { typeClass: 'scalar', logicalType: REAL_TYPE, nullable: true, isReadOnly: true };
|
|
29
29
|
|
|
30
30
|
return new FunctionReferenceNode(this, func, scalarType);
|
|
31
31
|
}
|
|
@@ -54,7 +54,7 @@ export class GlobalScope extends BaseScope {
|
|
|
54
54
|
// Get the proper scalar type from the function schema
|
|
55
55
|
const scalarType: ScalarType = isScalarFunctionSchema(func)
|
|
56
56
|
? func.returnType
|
|
57
|
-
: { typeClass: 'scalar',
|
|
57
|
+
: { typeClass: 'scalar', logicalType: REAL_TYPE, nullable: true, isReadOnly: true };
|
|
58
58
|
|
|
59
59
|
return new FunctionReferenceNode(this, func, scalarType);
|
|
60
60
|
}
|
|
@@ -3,13 +3,13 @@ import { ParameterReferenceNode } from '../nodes/reference.js'; // Corrected imp
|
|
|
3
3
|
import { BaseScope } from './base.js';
|
|
4
4
|
import { Ambiguous, type Scope } from './scope.js';
|
|
5
5
|
import type { ScalarType } from '../../common/datatype.js';
|
|
6
|
-
import { SqlDataType } from '../../common/types.js';
|
|
7
6
|
import type { PlanNode } from '../nodes/plan-node.js';
|
|
7
|
+
import { TEXT_TYPE } from '../../types/builtin-types.js';
|
|
8
8
|
|
|
9
9
|
// Default type for parameters when not otherwise specified.
|
|
10
10
|
const DEFAULT_PARAMETER_TYPE: ScalarType = {
|
|
11
11
|
typeClass: 'scalar',
|
|
12
|
-
|
|
12
|
+
logicalType: TEXT_TYPE,
|
|
13
13
|
nullable: true,
|
|
14
14
|
};
|
|
15
15
|
|
|
@@ -4,8 +4,7 @@ import type { RelationType, ColumnDef, ScalarType, ColRef } from '../common/data
|
|
|
4
4
|
import { SqlDataType, StatusCode, type DeepReadonly, type SqlValue } from '../common/types.js'; // Import SqlValue and ensure SqlDataType is not type-only
|
|
5
5
|
import type { AstNode } from '../parser/ast.js';
|
|
6
6
|
import { QuereusError } from '../common/errors.js';
|
|
7
|
-
|
|
8
|
-
// If tableSchema.columns are of type from '../schema/column.js', then their affinity is already SqlDataType.
|
|
7
|
+
import { inferLogicalTypeFromValue } from '../common/type-inference.js';
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Converts a TableSchema (from src/schema/table.ts) to a RelationType (from src/common/datatype.ts).
|
|
@@ -17,7 +16,7 @@ export function relationTypeFromTableSchema(tableSchema: TableSchema): RelationT
|
|
|
17
16
|
name: col.name,
|
|
18
17
|
type: {
|
|
19
18
|
typeClass: 'scalar',
|
|
20
|
-
|
|
19
|
+
logicalType: col.logicalType,
|
|
21
20
|
collationName: col.collation,
|
|
22
21
|
nullable: !col.notNull,
|
|
23
22
|
isReadOnly: false,
|
|
@@ -53,18 +52,11 @@ export function relationTypeFromTableSchema(tableSchema: TableSchema): RelationT
|
|
|
53
52
|
* @returns A ScalarType representing the inferred type of the value.
|
|
54
53
|
*/
|
|
55
54
|
export function getParameterScalarType(value: SqlValue): ScalarType {
|
|
56
|
-
|
|
57
|
-
if (value === null) affinity = SqlDataType.NULL;
|
|
58
|
-
else if (typeof value === 'number') affinity = SqlDataType.REAL;
|
|
59
|
-
else if (typeof value === 'bigint') affinity = SqlDataType.INTEGER;
|
|
60
|
-
else if (typeof value === 'string') affinity = SqlDataType.TEXT;
|
|
61
|
-
else if (value instanceof Uint8Array) affinity = SqlDataType.BLOB;
|
|
62
|
-
else if (typeof value === 'boolean') affinity = SqlDataType.INTEGER; // Store booleans as INTEGER
|
|
63
|
-
else affinity = SqlDataType.BLOB; // Default for unknown types that might pass as SqlValue
|
|
55
|
+
const logicalType = inferLogicalTypeFromValue(value);
|
|
64
56
|
|
|
65
57
|
return {
|
|
66
58
|
typeClass: 'scalar',
|
|
67
|
-
|
|
59
|
+
logicalType,
|
|
68
60
|
nullable: true, // No guarantees about the value, so it's nullable
|
|
69
61
|
isReadOnly: true, // Parameters are read-only within the query execution context
|
|
70
62
|
};
|
|
@@ -80,12 +72,12 @@ export function checkRelationsAssignable(source: RelationType, target: RelationT
|
|
|
80
72
|
return checkColumnsAssignable(source.columns, target.columns, astNode);
|
|
81
73
|
}
|
|
82
74
|
|
|
83
|
-
export function columnSchemaToDef(colName: string, colDef: ColumnSchema):
|
|
75
|
+
export function columnSchemaToDef(colName: string, colDef: ColumnSchema): ColumnDef {
|
|
84
76
|
return {
|
|
85
77
|
name: colName,
|
|
86
78
|
type: {
|
|
87
79
|
typeClass: 'scalar',
|
|
88
|
-
|
|
80
|
+
logicalType: colDef.logicalType,
|
|
89
81
|
collationName: colDef.collation,
|
|
90
82
|
nullable: !colDef.notNull,
|
|
91
83
|
isReadOnly: false,
|
|
@@ -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,13 +135,21 @@ 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
155
|
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) === 0 ? 1 : 0;
|
|
@@ -145,6 +161,12 @@ 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
172
|
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) !== 0 ? 1 : 0;
|
|
@@ -155,6 +177,12 @@ 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
188
|
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) < 0 ? 1 : 0;
|
|
@@ -165,6 +193,12 @@ 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
204
|
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) <= 0 ? 1 : 0;
|
|
@@ -177,6 +211,12 @@ 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
222
|
const comparisonResult = compareSqlValuesFast(coercedV1, coercedV2, collationFunc);
|
|
@@ -189,13 +229,19 @@ export function emitComparisonOp(plan: BinaryOpNode, ctx: EmissionContext): Inst
|
|
|
189
229
|
// SQL comparison: NULL >= anything -> NULL
|
|
190
230
|
if (v1 === null || v2 === null) return null;
|
|
191
231
|
|
|
232
|
+
// Try temporal comparison first
|
|
233
|
+
const temporalResult = tryTemporalComparison(operator, v1, v2);
|
|
234
|
+
if (temporalResult !== undefined) {
|
|
235
|
+
return temporalResult;
|
|
236
|
+
}
|
|
237
|
+
|
|
192
238
|
// Apply type coercion before comparison
|
|
193
239
|
const [coercedV1, coercedV2] = coerceForComparison(v1, v2);
|
|
194
240
|
return compareSqlValuesFast(coercedV1, coercedV2, collationFunc) >= 0 ? 1 : 0;
|
|
195
241
|
};
|
|
196
242
|
break;
|
|
197
243
|
default:
|
|
198
|
-
throw new QuereusError(`Unsupported comparison operator: ${
|
|
244
|
+
throw new QuereusError(`Unsupported comparison operator: ${operator}`, StatusCode.UNSUPPORTED);
|
|
199
245
|
}
|
|
200
246
|
|
|
201
247
|
const leftExpr = emitPlanNode(plan.left, 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 (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
|