@vibeorm/sql 4.1.0 → 4.3.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/dist/ast.d.ts +221 -1
- package/dist/codecs.d.ts +2 -0
- package/dist/dialect.d.ts +118 -1
- package/dist/dialects/mysql-check-probe.d.ts +1 -0
- package/dist/dialects/mysql-generated-columns.d.ts +43 -0
- package/dist/dialects/mysql-index-expressions.d.ts +20 -0
- package/dist/dialects/mysql-row-rules.d.ts +8 -0
- package/dist/dialects/postgres-generated-columns.d.ts +30 -0
- package/dist/dialects/postgres-index-expressions.d.ts +10 -0
- package/dist/dialects/postgres-index-keys.d.ts +15 -0
- package/dist/dialects/postgres-row-rules.d.ts +25 -0
- package/dist/dialects/sqlite-generated-columns.d.ts +18 -0
- package/dist/dialects/sqlite-index-expressions.d.ts +23 -0
- package/dist/dialects/sqlite-row-rules.d.ts +8 -0
- package/dist/dialects/sqlite-table-definition.d.ts +25 -5
- package/dist/generated-columns.d.ts +18 -0
- package/dist/index-expressions.d.ts +61 -0
- package/dist/index.d.ts +19 -6
- package/dist/index.js +2358 -1079
- package/dist/index.js.map +32 -18
- package/dist/key-list.d.ts +83 -0
- package/dist/like.d.ts +17 -0
- package/dist/local-date.d.ts +9 -0
- package/dist/pg-array.d.ts +6 -0
- package/dist/predicate.d.ts +21 -7
- package/dist/query/index.js +59 -19
- package/dist/query/index.js.map +7 -7
- package/dist/query/state.d.ts +5 -0
- package/dist/query/types.d.ts +2 -0
- package/dist/row-rules.d.ts +57 -0
- package/dist/timestamp-identity.d.ts +8 -2
- package/package.json +2 -2
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key-list membership (`SqlExpr` `keyList`, the public `where: { IN }`):
|
|
3
|
+
* the postgres element type each member's array binds as.
|
|
4
|
+
*
|
|
5
|
+
* Postgres compares `"col" = $n` with the parameter typed as the column's
|
|
6
|
+
* type WITHOUT its modifier (measured on postgres 18.6: `timestamptz(3) =
|
|
7
|
+
* '…00.1234'` matches nothing, `numeric(10,2) = '1.234'` matches nothing,
|
|
8
|
+
* `varchar(3) = 'abcd'` matches nothing). The unnest form must compare the
|
|
9
|
+
* same way, so every member binds as `$n::<base type>[]`: a `timestamptz(3)[]`
|
|
10
|
+
* cast would round `.1234` onto the stored `.123` and match a row `=` does not.
|
|
11
|
+
* The base type also keeps the column's own operator and index: `bpchar` for
|
|
12
|
+
* `char(n)` (pad-insensitive like `=`), `citext` (case-insensitive like `=`;
|
|
13
|
+
* a `text[]` member would compare case-sensitively), the native enum's own
|
|
14
|
+
* type, and text collations resolve to the column's collation, as for `=`.
|
|
15
|
+
*/
|
|
16
|
+
import type { KeyListMember } from "./ast.ts";
|
|
17
|
+
import type { SqlDialect } from "./dialect.ts";
|
|
18
|
+
/**
|
|
19
|
+
* The postgres element type a key-list member's array binds as, or
|
|
20
|
+
* `undefined` when the member has none (Json, or a native type this list does
|
|
21
|
+
* not know). Never throws: a caller deciding whether a key list CAN carry a
|
|
22
|
+
* member (the automatic OR rewrite) asks here; {@link postgresKeyListElementType}
|
|
23
|
+
* is the throwing form the renderer uses.
|
|
24
|
+
*/
|
|
25
|
+
export declare function postgresKeyListElementTypeOf(params: {
|
|
26
|
+
dialect: SqlDialect;
|
|
27
|
+
member: KeyListMember;
|
|
28
|
+
}): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* The postgres element type a key-list member's array binds as
|
|
31
|
+
* (`$n::<type>[]`), derived from the column's declared type facts.
|
|
32
|
+
*
|
|
33
|
+
* @throws VibeError `VIBE_UNSUPPORTED_CAPABILITY` for a member whose type has
|
|
34
|
+
* no equality a key can use (Json) or a native type this list does not know.
|
|
35
|
+
*/
|
|
36
|
+
export declare function postgresKeyListElementType(params: {
|
|
37
|
+
dialect: SqlDialect;
|
|
38
|
+
member: KeyListMember;
|
|
39
|
+
}): string;
|
|
40
|
+
/**
|
|
41
|
+
* Whether a key list must bind its rows as a row-value list of UNTYPED
|
|
42
|
+
* parameters, `(a, b) IN (($1, $2), …)`, instead of typed arrays on the
|
|
43
|
+
* `unnest` strategy: some member is a `DateTime` without a declared native
|
|
44
|
+
* type. Its element type would be `timestamptz`, but such a field may sit on
|
|
45
|
+
* a zone-less `timestamp(3)` column (a Prisma-created database;
|
|
46
|
+
* docs/dialect-notes.md "DateTime without time zone on postgres"), where
|
|
47
|
+
* `timestamp = timestamptz` converts the column through the session TimeZone
|
|
48
|
+
* and matches other rows than `"col" = $n` (an untyped parameter the server
|
|
49
|
+
* types from the column). The row-value list keeps that typing; PostgreSQL
|
|
50
|
+
* plans it like the OR of conjunctions, so its planning grows with the list.
|
|
51
|
+
* Shared by the renderer and the runtime's render-cache walk.
|
|
52
|
+
*/
|
|
53
|
+
export declare function keyListBindsUntypedRows(params: {
|
|
54
|
+
members: readonly KeyListMember[];
|
|
55
|
+
}): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* The cast after a bulk-insert column's array placeholder (`$n::<cast>[]`),
|
|
58
|
+
* or `undefined` when the column has none and the insert must keep VALUES.
|
|
59
|
+
* Never throws: the query builder asks here while deciding admission.
|
|
60
|
+
*
|
|
61
|
+
* Every type the key list knows binds its BASE type, exactly as above — the
|
|
62
|
+
* INSERT's assignment then applies the column's modifier (length, precision,
|
|
63
|
+
* rounding) the way it does for a VALUES parameter. Json is the one addition:
|
|
64
|
+
* it binds through a text slot, `text::jsonb` (`text::json` for `@db.Json`),
|
|
65
|
+
* because PostgreSQL has no assignment cast from text to jsonb and bun:sql's
|
|
66
|
+
* named statements would JSON-encode pre-encoded text bound to a jsonb slot
|
|
67
|
+
* (the `JsonParamCell` rule, per element).
|
|
68
|
+
*/
|
|
69
|
+
export declare function postgresBulkInsertArrayCastOf(params: {
|
|
70
|
+
dialect: SqlDialect;
|
|
71
|
+
member: KeyListMember;
|
|
72
|
+
}): string | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* The throwing form of {@link postgresBulkInsertArrayCastOf}, for the renderer.
|
|
75
|
+
*
|
|
76
|
+
* @throws VibeError `VIBE_UNSUPPORTED_CAPABILITY` for a native type the
|
|
77
|
+
* bulk-insert form does not know.
|
|
78
|
+
*/
|
|
79
|
+
export declare function postgresBulkInsertArrayCast(params: {
|
|
80
|
+
dialect: SqlDialect;
|
|
81
|
+
member: KeyListMember;
|
|
82
|
+
}): string;
|
|
83
|
+
//# sourceMappingURL=key-list.d.ts.map
|
package/dist/like.d.ts
CHANGED
|
@@ -41,4 +41,21 @@ export declare function strictMatchPattern(params: {
|
|
|
41
41
|
op: "contains" | "startsWith" | "endsWith";
|
|
42
42
|
value: string;
|
|
43
43
|
}): string;
|
|
44
|
+
/**
|
|
45
|
+
* Does a `strictMatch` render the collation `LIKE` conjunct BEFORE its
|
|
46
|
+
* byte-exact test? Only the `binary` strategy's (mysql) non-negated
|
|
47
|
+
* `startsWith`: `(col LIKE ? AND col LIKE CAST(? AS BINARY))`, the same
|
|
48
|
+
* pattern bound twice. A byte-exact prefix match is always a collation match
|
|
49
|
+
* (a collation never separates identical characters, and the escaped prefix
|
|
50
|
+
* plus `%` has no other wildcard), so the AND keeps exactly the binary test's
|
|
51
|
+
* rows — while the plain conjunct lets a btree index on the column bound the
|
|
52
|
+
* scan (measured on mysql 26.7: 5.92 → 0.24 ms, 100 002 → 3 rows read). The
|
|
53
|
+
* ONE decision point for the renderer AND the runtime's render-cache mirror,
|
|
54
|
+
* which must bind the pattern twice exactly when the renderer does.
|
|
55
|
+
*/
|
|
56
|
+
export declare function strictMatchCollationPrefilter(params: {
|
|
57
|
+
strategy: "native" | "glob" | "binary";
|
|
58
|
+
op: "equals" | "contains" | "startsWith" | "endsWith";
|
|
59
|
+
negate: boolean;
|
|
60
|
+
}): boolean;
|
|
44
61
|
//# sourceMappingURL=like.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CheckIR } from "@vibeorm/schema";
|
|
2
|
+
import type { SqlDialect } from "./dialect.ts";
|
|
3
|
+
/** The single owned SQLite calendar/domain CHECK; native DATE dialects need no marker. */
|
|
4
|
+
export declare function renderLocalDateCheck({ dialect, table, column }: {
|
|
5
|
+
readonly dialect: SqlDialect;
|
|
6
|
+
readonly table: string;
|
|
7
|
+
readonly column: string;
|
|
8
|
+
}): CheckIR | undefined;
|
|
9
|
+
//# sourceMappingURL=local-date.d.ts.map
|
package/dist/pg-array.d.ts
CHANGED
|
@@ -7,6 +7,12 @@ export declare function toPgArrayLiteral(params: {
|
|
|
7
7
|
* not a literal (defensive — the value then passes through untouched).
|
|
8
8
|
* Unquoted `NULL` is SQL NULL; postgres quotes any element that could be
|
|
9
9
|
* mistaken for it.
|
|
10
|
+
*
|
|
11
|
+
* RD-07: elements are SLICED out of the literal (an unquoted element up to the
|
|
12
|
+
* next comma, a quoted one as the runs between its escapes) instead of built
|
|
13
|
+
* one character at a time; the output, malformed input included, is the same
|
|
14
|
+
* as the former char-by-char parser's (differential test
|
|
15
|
+
* `tests/pg-array-literal-sliced.test.ts`).
|
|
10
16
|
*/
|
|
11
17
|
export declare function parsePgArrayLiteral(params: {
|
|
12
18
|
literal: string;
|
package/dist/predicate.d.ts
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* `WHERE`. Constitution rule 4 — every SQL-text decision lives in a dialect
|
|
7
7
|
* here, never in migrate, the DSL or a query builder.
|
|
8
8
|
*
|
|
9
|
+
* Shared scalar rendering also serves expression-index keys. The caller owns
|
|
10
|
+
* expression-index DDL and its additional database-object qualification.
|
|
11
|
+
*
|
|
9
12
|
* What it guarantees:
|
|
10
13
|
*
|
|
11
14
|
* - **Logical field names resolve to physical columns.** `@map("qty_total")`
|
|
@@ -14,9 +17,8 @@
|
|
|
14
17
|
* - **Identifiers are quoted through `dialect.quoteIdent`, literals through
|
|
15
18
|
* `dialect.quoteStringLiteral`.** No value the author writes can escape its
|
|
16
19
|
* literal and become SQL structure.
|
|
17
|
-
* - **Operand scalars must match the
|
|
18
|
-
*
|
|
19
|
-
* and a relation can never disagree about what a column IS.
|
|
20
|
+
* - **Operand scalars must match the expression result type.** Local dates keep
|
|
21
|
+
* their distinct logical identity, shared with foreign-key pairing.
|
|
20
22
|
* - **A dialect that cannot express something refuses here**, at the earliest
|
|
21
23
|
* knowable moment (generate time), with a `VibeError` carrying a stable
|
|
22
24
|
* `meta.rule` — never silently different SQL.
|
|
@@ -26,13 +28,13 @@
|
|
|
26
28
|
* an implicit `IS NOT NULL` guard. An author who wants all-or-none writes it
|
|
27
29
|
* with `isNull`/`isNotNull`.
|
|
28
30
|
*/
|
|
29
|
-
import type { EnumIR, ModelIR, PredicateIR } from "@vibeorm/schema";
|
|
31
|
+
import type { EnumIR, ModelIR, PredicateBooleanUsage, PredicateExpression, PredicateIR, PredicateResultType } from "@vibeorm/schema";
|
|
30
32
|
import type { SqlDialect } from "./dialect.ts";
|
|
31
33
|
/**
|
|
32
|
-
* Where a predicate is being rendered.
|
|
33
|
-
* limits
|
|
34
|
+
* Where a Boolean predicate is being rendered. Each host has different database
|
|
35
|
+
* limits; scalar index-expression usage is handled by renderSchemaExpression.
|
|
34
36
|
*/
|
|
35
|
-
export type PredicateUsage =
|
|
37
|
+
export type PredicateUsage = PredicateBooleanUsage;
|
|
36
38
|
/**
|
|
37
39
|
* The part of a model a predicate reads: its name (for error messages) and its
|
|
38
40
|
* fields (for physical column names and scalar types). Narrow on purpose — a
|
|
@@ -50,4 +52,16 @@ export declare function renderPredicate(params: {
|
|
|
50
52
|
/** Human label naming the declaration, e.g. `CHECK "order_total_nonnegative"`. */
|
|
51
53
|
site: string;
|
|
52
54
|
}): string;
|
|
55
|
+
/** Render a scalar schema expression. Expression-index DDL and host qualification belong to the caller. */
|
|
56
|
+
export declare function renderSchemaExpression({ expression, model, dialect, enums, usage, site }: {
|
|
57
|
+
expression: PredicateExpression;
|
|
58
|
+
model: PredicateModel;
|
|
59
|
+
dialect: SqlDialect;
|
|
60
|
+
enums?: readonly EnumIR[];
|
|
61
|
+
usage: "indexExpression";
|
|
62
|
+
site: string;
|
|
63
|
+
}): {
|
|
64
|
+
readonly text: string;
|
|
65
|
+
readonly resultType: PredicateResultType;
|
|
66
|
+
};
|
|
53
67
|
//# sourceMappingURL=predicate.d.ts.map
|
package/dist/query/index.js
CHANGED
|
@@ -13,6 +13,11 @@ registerSqlHandleCheck({ check: (value) => expressionStates.has(value) || source
|
|
|
13
13
|
function columnContract(definition) {
|
|
14
14
|
return JSON.stringify({ scalar: definition.scalar, nullable: definition.nullable === true, list: definition.list === true, enumType: definition.enumType, enumValues: definition.enumValues, nativeType: definition.nativeType, transport: definition.transport });
|
|
15
15
|
}
|
|
16
|
+
function assertWritableColumn({ table, column }) {
|
|
17
|
+
if (table.columns[column]?.generated === true) {
|
|
18
|
+
throw new VibeError({ code: "VIBE_VALIDATION", message: `Generated SQL column "${column}" is read-only`, meta: { reason: "generated-column-write", table: table.name, field: column, path: [column] } });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
16
21
|
function invalid(message) {
|
|
17
22
|
throw new VibeError({ code: "VIBE_VALIDATION", message });
|
|
18
23
|
}
|
|
@@ -64,7 +69,8 @@ function checkScope(params) {
|
|
|
64
69
|
import { assertSqlNamespace } from "@vibeorm/schema";
|
|
65
70
|
|
|
66
71
|
// packages/sql/src/query/expressions.ts
|
|
67
|
-
import {
|
|
72
|
+
import { isLocalDateField } from "@vibeorm/schema";
|
|
73
|
+
import { assertSqlParamBuilding, decode, float32Codec, isSqlBindingCollector, isSqlFragment, isSqlParam, jsonListParameter, localDateCodec, nativeEnumArrayParameter, parsePgArrayLiteral, recordSqlBinding, renderSqlFragment, resultScalarContract, snapshotSqlValue, sqlParamContract, timeOfDayCodec, toPgArrayLiteral } from "@vibeorm/sql";
|
|
68
74
|
var nullableFloat = decode.nullable({ decoder: decode.float });
|
|
69
75
|
var countDefinition = Object.freeze({ name: "", scalar: "BigInt", decoder: decode.bigInt });
|
|
70
76
|
var floatDefinition = Object.freeze({ name: "", scalar: "Float", nullable: true, decoder: nullableFloat });
|
|
@@ -74,6 +80,8 @@ function nullableDecoder(decoder) {
|
|
|
74
80
|
if (lifted === undefined) {
|
|
75
81
|
lifted = Object.freeze({ decode: (params) => params.value === null ? null : decoder.decode(params) });
|
|
76
82
|
nullableDecoders.set(decoder, lifted);
|
|
83
|
+
if (localDateValueDecoders.has(decoder))
|
|
84
|
+
localDateValueDecoders.add(lifted);
|
|
77
85
|
}
|
|
78
86
|
return lifted;
|
|
79
87
|
}
|
|
@@ -87,11 +95,19 @@ function inputDecoder(definition) {
|
|
|
87
95
|
invalid("A decode-only projection requires a separate inputDecoder for parameters and writes");
|
|
88
96
|
return decoder;
|
|
89
97
|
}
|
|
98
|
+
function isLocalDateColumn({ definition }) {
|
|
99
|
+
return definition?.scalar === "String" && isLocalDateField({ field: { type: "String", nativeType: definition.nativeType } });
|
|
100
|
+
}
|
|
90
101
|
function nativeColumnDefinition(definition) {
|
|
102
|
+
const localDate = isLocalDateColumn({ definition });
|
|
103
|
+
if (localDate && definition.list === true)
|
|
104
|
+
invalid("Local-date list columns are unsupported");
|
|
105
|
+
if (localDate && localDateDecoders.has(definition.decoder))
|
|
106
|
+
return definition;
|
|
91
107
|
const contract = resultScalarContract({ decoder: definition.decoder });
|
|
92
|
-
if (contract === undefined || contract.scalar !== definition.scalar)
|
|
108
|
+
if (!localDate && (contract === undefined || contract.scalar !== definition.scalar))
|
|
93
109
|
return definition;
|
|
94
|
-
const codec = definition.scalar === "DateTime" ? timeOfDayCodec({ nativeType: definition.nativeType }) : definition.scalar === "Float" && /^(real|float4)$/i.test(definition.nativeType ?? "") ? float32Codec : undefined;
|
|
110
|
+
const codec = localDate ? localDateCodec : definition.scalar === "DateTime" ? timeOfDayCodec({ nativeType: definition.nativeType }) : definition.scalar === "Float" && /^(real|float4)$/i.test(definition.nativeType ?? "") ? float32Codec : undefined;
|
|
95
111
|
if (codec === undefined)
|
|
96
112
|
return definition;
|
|
97
113
|
const original = definition.decoder;
|
|
@@ -101,7 +117,7 @@ function nativeColumnDefinition(definition) {
|
|
|
101
117
|
decoders = new Map;
|
|
102
118
|
nativeDecoders.set(original, decoders);
|
|
103
119
|
}
|
|
104
|
-
const key = `${definition.nativeType}|${list}`;
|
|
120
|
+
const key = localDate ? "local-date" : `${definition.nativeType}|${list}`;
|
|
105
121
|
let decoder = decoders.get(key);
|
|
106
122
|
if (decoder === undefined) {
|
|
107
123
|
decoder = Object.freeze({
|
|
@@ -112,20 +128,32 @@ function nativeColumnDefinition(definition) {
|
|
|
112
128
|
const value = raw === null ? null : list && Array.isArray(elements) ? elements.map(convert) : convert(raw);
|
|
113
129
|
return original.decode({ ...params, value });
|
|
114
130
|
},
|
|
115
|
-
encode
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
131
|
+
..."encode" in original && typeof original.encode === "function" ? {
|
|
132
|
+
encode: (params) => {
|
|
133
|
+
const encoded = original.encode(params);
|
|
134
|
+
const convert = (value) => value === null ? null : codec.encode(value);
|
|
135
|
+
return list && Array.isArray(encoded) ? encoded.map(convert) : convert(encoded);
|
|
136
|
+
}
|
|
137
|
+
} : {}
|
|
120
138
|
});
|
|
121
139
|
decoders.set(key, decoder);
|
|
140
|
+
if (localDate) {
|
|
141
|
+
localDateDecoders.add(decoder);
|
|
142
|
+
if (contract?.scalar === "String" && !contract.list)
|
|
143
|
+
localDateValueDecoders.add(decoder);
|
|
144
|
+
}
|
|
122
145
|
}
|
|
123
146
|
return { ...definition, decoder };
|
|
124
147
|
}
|
|
125
148
|
var nativeDecoders = new WeakMap;
|
|
149
|
+
var localDateDecoders = new WeakSet;
|
|
150
|
+
var localDateValueDecoders = new WeakSet;
|
|
151
|
+
function hasLocalDateValueInput({ definition }) {
|
|
152
|
+
return isLocalDateColumn({ definition }) && localDateValueDecoders.has(definition.decoder) && (definition.inputDecoder === undefined || localDateValueDecoders.has(definition.inputDecoder));
|
|
153
|
+
}
|
|
126
154
|
function transportProjection(params) {
|
|
127
155
|
const { context, expression, definition } = params;
|
|
128
|
-
if (definition.transport === "text" || definition.scalar === "Json" || definition.scalar === "BigInt" || definition.list === true && definition.scalar === "Float") {
|
|
156
|
+
if (context.dialect.name !== "sqlite" && isLocalDateColumn({ definition }) || definition.transport === "text" || definition.scalar === "Json" || definition.scalar === "BigInt" || definition.list === true && definition.scalar === "Float") {
|
|
129
157
|
return `CAST(${expression} AS ${context.dialect.name === "mysql" ? "CHAR" : "TEXT"})`;
|
|
130
158
|
}
|
|
131
159
|
return expression;
|
|
@@ -141,7 +169,7 @@ function columnInputContract(definition) {
|
|
|
141
169
|
enumValues: definition.enumValues,
|
|
142
170
|
enumType: definition.enumType,
|
|
143
171
|
jsonStorage: definition.jsonStorage,
|
|
144
|
-
...definition.inputDecoder !== undefined && definition.inputDecoder !== definition.decoder ? { columnIdentity: encoder } : {}
|
|
172
|
+
...definition.inputDecoder !== undefined && definition.inputDecoder !== definition.decoder && !hasLocalDateValueInput({ definition }) ? { columnIdentity: encoder } : {}
|
|
145
173
|
};
|
|
146
174
|
}
|
|
147
175
|
function physicalInput(contract) {
|
|
@@ -190,6 +218,8 @@ function encodeColumnValue(params) {
|
|
|
190
218
|
invalid("A non-null SQL column cannot receive SQL NULL from its encoder");
|
|
191
219
|
return null;
|
|
192
220
|
}
|
|
221
|
+
if (isLocalDateColumn({ definition }))
|
|
222
|
+
return localDateCodec.encode(encoded);
|
|
193
223
|
if (definition.scalar === "BigInt" && definition.list !== true && dialect.name === "sqlite") {
|
|
194
224
|
const integer = decode.bigInt.decode({ value: encoded, dialect: "sqlite" });
|
|
195
225
|
if (integer < -9223372036854775808n || integer > 9223372036854775807n)
|
|
@@ -239,7 +269,7 @@ function bindColumnValue(params) {
|
|
|
239
269
|
}
|
|
240
270
|
function predicateDefinition(expression) {
|
|
241
271
|
const state = expressionState(expression);
|
|
242
|
-
if (state.definition?.inputDecoder !== undefined && (!("encode" in state.definition.decoder) || typeof state.definition.decoder.encode !== "function"))
|
|
272
|
+
if (state.definition?.inputDecoder !== undefined && (!("encode" in state.definition.decoder) || typeof state.definition.decoder.encode !== "function") && !hasLocalDateValueInput({ definition: state.definition }))
|
|
243
273
|
unsupported("Input tokens cannot infer predicates over read-transformed columns");
|
|
244
274
|
if (state.definition !== undefined)
|
|
245
275
|
return state.definition;
|
|
@@ -297,7 +327,7 @@ function literal(params) {
|
|
|
297
327
|
function comparedLiteral(params) {
|
|
298
328
|
const state = expressionState(params.expression);
|
|
299
329
|
const definition = state.definition;
|
|
300
|
-
if (definition?.inputDecoder !== undefined && (!("encode" in definition.decoder) || typeof definition.decoder.encode !== "function")) {
|
|
330
|
+
if (definition?.inputDecoder !== undefined && (!("encode" in definition.decoder) || typeof definition.decoder.encode !== "function") && !hasLocalDateValueInput({ definition })) {
|
|
301
331
|
unsupported("Implicit literal predicates are unavailable for read-transformed columns; use an explicit trusted SQL input contract");
|
|
302
332
|
}
|
|
303
333
|
const value = snapshotSqlValue(params.value);
|
|
@@ -335,6 +365,9 @@ function comparison(params) {
|
|
|
335
365
|
const right = typeof params.right === "object" && params.right !== null && expressionStates.has(params.right) ? params.right : comparedLiteral({ expression: params.left, value: params.right });
|
|
336
366
|
const leftState = expressionState(params.left);
|
|
337
367
|
const rightState = expressionState(right);
|
|
368
|
+
if (leftState.parameter === undefined && rightState.parameter === undefined && leftState.definition !== undefined && rightState.definition !== undefined && isLocalDateColumn({ definition: leftState.definition }) !== isLocalDateColumn({ definition: rightState.definition })) {
|
|
369
|
+
invalid("Local-date comparisons require matching local-date columns, not plain text or another scalar");
|
|
370
|
+
}
|
|
338
371
|
const unsafeDecimal = (state) => state.definition?.scalar === "Decimal";
|
|
339
372
|
return makeExpression(childrenState({
|
|
340
373
|
children: [params.left, right],
|
|
@@ -342,7 +375,7 @@ function comparison(params) {
|
|
|
342
375
|
render: (context) => {
|
|
343
376
|
if (context.dialect.name === "sqlite" && (unsafeDecimal(leftState) || unsafeDecimal(rightState)))
|
|
344
377
|
unsupported("SQLite Decimal comparisons are not exact; use an explicit raw numeric contract");
|
|
345
|
-
const left = leftState.render(context);
|
|
378
|
+
const left = leftState.parameter !== undefined && isLocalDateColumn({ definition: rightState.definition }) ? bindColumnValue({ context, definition: rightState.definition, encoder: leftState.parameter.decoder, value: leftState.parameter.value }) : leftState.render(context);
|
|
346
379
|
const operand = leftState.parameter === undefined && leftState.definition !== undefined && rightState.parameter !== undefined ? bindColumnValue({ context, definition: leftState.definition, encoder: rightState.parameter.decoder, value: rightState.parameter.value }) : rightState.render(context);
|
|
347
380
|
return `(${left} ${params.operator} ${operand})`;
|
|
348
381
|
}
|
|
@@ -363,7 +396,7 @@ function aggregate(params) {
|
|
|
363
396
|
invalid("Nested aggregate expressions are unsupported");
|
|
364
397
|
if (definition === undefined || definition.list === true || !["String", "Int", "BigInt", "Float", "DateTime"].includes(definition.scalar))
|
|
365
398
|
unsupported("MIN/MAX require a qualified ordered scalar column; use an explicit raw decoder otherwise");
|
|
366
|
-
if (definition.scalar === "String" && definition.nativeType !== undefined && !/^(text|varchar|char|bpchar|tinytext|mediumtext|longtext)(?:\s*\(.*\))?$/i.test(definition.nativeType))
|
|
399
|
+
if (definition.scalar === "String" && !isLocalDateColumn({ definition }) && definition.nativeType !== undefined && !/^(text|varchar|char|bpchar|tinytext|mediumtext|longtext)(?:\s*\(.*\))?$/i.test(definition.nativeType))
|
|
367
400
|
unsupported("MIN/MAX do not support this native String type; UUID and other non-text types need explicit SQL");
|
|
368
401
|
return makeExpression({
|
|
369
402
|
...state,
|
|
@@ -375,7 +408,7 @@ function aggregate(params) {
|
|
|
375
408
|
ungrouped: new Set,
|
|
376
409
|
aggregate: true,
|
|
377
410
|
render: (context) => `${params.fn}(${state.render(context)})`,
|
|
378
|
-
projection: definition.scalar === "BigInt" ? (context) =>
|
|
411
|
+
projection: definition.scalar === "BigInt" || isLocalDateColumn({ definition }) ? (context) => transportProjection({ context, expression: `${params.fn}(${state.render(context)})`, definition }) : undefined
|
|
379
412
|
});
|
|
380
413
|
}
|
|
381
414
|
function floatAggregate(params) {
|
|
@@ -488,6 +521,8 @@ function makeTable(params) {
|
|
|
488
521
|
identifier(key);
|
|
489
522
|
identifier(definition.name);
|
|
490
523
|
assertDecoder(definition.decoder);
|
|
524
|
+
if (definition.generated !== undefined && definition.generated !== true)
|
|
525
|
+
invalid("SQL generated-column metadata must be true when present");
|
|
491
526
|
if (definition.inputDecoder !== undefined && typeof definition.inputDecoder.encode !== "function")
|
|
492
527
|
invalid("SQL input decoders must have an encoder");
|
|
493
528
|
if (definition.enumType !== undefined) {
|
|
@@ -501,9 +536,11 @@ function makeTable(params) {
|
|
|
501
536
|
const physical = Object.values(columns).map((column) => column.name);
|
|
502
537
|
if (new Set(physical).size !== physical.length)
|
|
503
538
|
invalid("Physical column names must be unique within a source");
|
|
504
|
-
const required = [...params.required ?? Object.entries(columns).filter(([, definition]) => definition.nullable !== true && definition.dbDefault !== true).map(([name]) => name)];
|
|
539
|
+
const required = [...params.required ?? Object.entries(columns).filter(([, definition]) => definition.nullable !== true && definition.dbDefault !== true && definition.generated !== true).map(([name]) => name)];
|
|
505
540
|
if (required.some((key) => !Object.hasOwn(columns, key)))
|
|
506
541
|
invalid("Required insert columns must belong to the table");
|
|
542
|
+
for (const column of required)
|
|
543
|
+
assertWritableColumn({ table: { name: params.name, columns, required }, column });
|
|
507
544
|
const source = Object.freeze(Object.create(null));
|
|
508
545
|
sourceStates.set(source, Object.freeze({ name: params.name, namespace: params.namespace, columns: Object.freeze(columns), required: Object.freeze(required), readOnly: params.readOnly === true }));
|
|
509
546
|
return source;
|
|
@@ -874,9 +911,11 @@ function cells(params) {
|
|
|
874
911
|
invalid("SQL write rows must be field objects");
|
|
875
912
|
const table = sourceState(params.binding.source);
|
|
876
913
|
const row = params.value;
|
|
877
|
-
for (const name of Object.keys(row))
|
|
914
|
+
for (const name of Object.keys(row)) {
|
|
878
915
|
if (!Object.hasOwn(table.columns, name))
|
|
879
916
|
invalid(`Unknown SQL write field "${name}"`);
|
|
917
|
+
assertWritableColumn({ table, column: name });
|
|
918
|
+
}
|
|
880
919
|
return Object.freeze(Object.keys(table.columns).filter((name) => Object.hasOwn(row, name)).map((name) => {
|
|
881
920
|
const value = row[name];
|
|
882
921
|
if (isSqlParam3(value)) {
|
|
@@ -1009,6 +1048,7 @@ function input(params) {
|
|
|
1009
1048
|
invalid("qb.input requires a writable physical table");
|
|
1010
1049
|
if (!Object.hasOwn(table.columns, params.column))
|
|
1011
1050
|
invalid("SQL input column does not belong to its table");
|
|
1051
|
+
assertWritableColumn({ table, column: params.column });
|
|
1012
1052
|
const definition = table.columns[params.column];
|
|
1013
1053
|
let descriptor = columnInputs.get(definition);
|
|
1014
1054
|
if (descriptor === undefined) {
|
|
@@ -1077,4 +1117,4 @@ export {
|
|
|
1077
1117
|
sum
|
|
1078
1118
|
};
|
|
1079
1119
|
|
|
1080
|
-
//# debugId=
|
|
1120
|
+
//# debugId=3FADE857D3991B3164756E2164756E21
|