@vibeorm/sql 5.10.0 → 5.11.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 +217 -0
- package/dist/dialect.d.ts +49 -0
- package/dist/dialects/mysql-json-keys.d.ts +26 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +519 -262
- package/dist/index.js.map +9 -8
- package/dist/key-list.d.ts +38 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3605,6 +3605,9 @@ function renderSqliteJsonEquality(params) {
|
|
|
3605
3605
|
return `(SELECT CASE WHEN ${left} IS NULL THEN NULL ELSE NOT EXISTS (` + `WITH RECURSIVE ${tree} AS (` + `SELECT 1 AS ${side}, ${id}, ${parentId}, ${key}, ${type}, ${atom} FROM json_tree(CASE WHEN json_valid(${left}) THEN ${left} ELSE 'null' END) ` + `UNION ALL SELECT -1 AS ${side}, ${id}, ${parentId}, ${key}, ${type}, ${atom} FROM json_tree(${right})` + `), ${paths} (${side}, ${id}, ${path}, ${type}, ${atom}) AS (` + `SELECT ${side}, ${id}, json_array(), ${type}, ${atom} FROM ${tree} WHERE ${parentId} IS NULL ` + `UNION ALL SELECT ${child}.${side}, ${child}.${id}, json_insert(${parent}.${path}, '$[#]', ${child}.${key}), ${child}.${type}, ${child}.${atom} ` + `FROM ${tree} AS ${child} JOIN ${paths} AS ${parent} ON ${child}.${side} = ${parent}.${side} AND ${child}.${parentId} = ${parent}.${id}` + `) SELECT 1 FROM ${paths} GROUP BY ${path}, CASE WHEN ${type} IN ('integer', 'real') THEN 'number' ELSE ${type} END, ${atom} HAVING SUM(${side}) <> 0` + `) END FROM (SELECT ${params.left} AS ${q("left")}, ${params.right} AS ${q("right")}) AS ${values})`;
|
|
3606
3606
|
}
|
|
3607
3607
|
|
|
3608
|
+
// packages/sql/src/dialects/mysql-json-keys.ts
|
|
3609
|
+
import { internalError as internalError2 } from "@vibeorm/schema";
|
|
3610
|
+
|
|
3608
3611
|
// packages/sql/src/json-transport.ts
|
|
3609
3612
|
var JSON_TRANSPORT = Object.freeze({ chunkSize: 100 });
|
|
3610
3613
|
function projectionOutputName(params) {
|
|
@@ -3612,8 +3615,176 @@ function projectionOutputName(params) {
|
|
|
3612
3615
|
return column.alias ?? column.name;
|
|
3613
3616
|
}
|
|
3614
3617
|
|
|
3618
|
+
// packages/sql/src/key-list.ts
|
|
3619
|
+
import { unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
|
|
3620
|
+
var SCALAR_ELEMENT_TYPES = Object.freeze({
|
|
3621
|
+
String: "text",
|
|
3622
|
+
Boolean: "bool",
|
|
3623
|
+
Int: "int4",
|
|
3624
|
+
BigInt: "int8",
|
|
3625
|
+
Float: "float8",
|
|
3626
|
+
Decimal: "numeric",
|
|
3627
|
+
DateTime: "timestamptz",
|
|
3628
|
+
Json: null,
|
|
3629
|
+
Bytes: "bytea"
|
|
3630
|
+
});
|
|
3631
|
+
var NATIVE_ELEMENT_TYPES = Object.freeze({
|
|
3632
|
+
text: "text",
|
|
3633
|
+
varchar: "varchar",
|
|
3634
|
+
charactervarying: "varchar",
|
|
3635
|
+
char: "bpchar",
|
|
3636
|
+
character: "bpchar",
|
|
3637
|
+
bpchar: "bpchar",
|
|
3638
|
+
uuid: "uuid",
|
|
3639
|
+
citext: "citext",
|
|
3640
|
+
xml: "xml",
|
|
3641
|
+
inet: "inet",
|
|
3642
|
+
money: "money",
|
|
3643
|
+
bit: '"bit"',
|
|
3644
|
+
varbit: "varbit",
|
|
3645
|
+
bitvarying: "varbit",
|
|
3646
|
+
smallint: "int2",
|
|
3647
|
+
int2: "int2",
|
|
3648
|
+
integer: "int4",
|
|
3649
|
+
int: "int4",
|
|
3650
|
+
int4: "int4",
|
|
3651
|
+
bigint: "int8",
|
|
3652
|
+
int8: "int8",
|
|
3653
|
+
oid: "oid",
|
|
3654
|
+
real: "float4",
|
|
3655
|
+
float4: "float4",
|
|
3656
|
+
doubleprecision: "float8",
|
|
3657
|
+
double: "float8",
|
|
3658
|
+
float8: "float8",
|
|
3659
|
+
decimal: "numeric",
|
|
3660
|
+
numeric: "numeric",
|
|
3661
|
+
boolean: "bool",
|
|
3662
|
+
bool: "bool",
|
|
3663
|
+
timestamp: "timestamp",
|
|
3664
|
+
timestampwithouttimezone: "timestamp",
|
|
3665
|
+
timestamptz: "timestamptz",
|
|
3666
|
+
timestampwithtimezone: "timestamptz",
|
|
3667
|
+
date: "date",
|
|
3668
|
+
time: "time",
|
|
3669
|
+
timewithouttimezone: "time",
|
|
3670
|
+
timetz: "timetz",
|
|
3671
|
+
timewithtimezone: "timetz",
|
|
3672
|
+
bytea: "bytea"
|
|
3673
|
+
});
|
|
3674
|
+
function postgresKeyListElementTypeOf(params) {
|
|
3675
|
+
const { dialect, member } = params;
|
|
3676
|
+
if (member.type === "Enum") {
|
|
3677
|
+
if (member.enumType === undefined)
|
|
3678
|
+
return "text";
|
|
3679
|
+
const name = dialect.quoteIdent({ name: member.enumType.name });
|
|
3680
|
+
return member.enumType.namespace === undefined ? name : `${dialect.quoteIdent({ name: member.enumType.namespace })}.${name}`;
|
|
3681
|
+
}
|
|
3682
|
+
const native = member.nativeType === undefined ? undefined : NATIVE_ELEMENT_TYPES[member.nativeType.toLowerCase().replace(/\([^)]*\)/g, "").replace(/\s+/g, "")];
|
|
3683
|
+
const type = member.nativeType === undefined ? SCALAR_ELEMENT_TYPES[member.type] : native;
|
|
3684
|
+
return type === null ? undefined : type;
|
|
3685
|
+
}
|
|
3686
|
+
function postgresKeyListElementType(params) {
|
|
3687
|
+
const { dialect, member } = params;
|
|
3688
|
+
const type = postgresKeyListElementTypeOf({ dialect, member });
|
|
3689
|
+
if (type === undefined) {
|
|
3690
|
+
throw unsupportedCapability16({
|
|
3691
|
+
dialect: dialect.name,
|
|
3692
|
+
feature: `the key-list filter (IN) on a ${member.type}${member.nativeType === undefined ? "" : ` @db.${member.nativeType}`} member`,
|
|
3693
|
+
hint: "IN binds every member as a typed array; filter this field with equality inside OR instead"
|
|
3694
|
+
});
|
|
3695
|
+
}
|
|
3696
|
+
return type;
|
|
3697
|
+
}
|
|
3698
|
+
function keyListBindsUntypedRows(params) {
|
|
3699
|
+
return params.members.some((member) => member.type === "DateTime" && member.nativeType === undefined);
|
|
3700
|
+
}
|
|
3701
|
+
var MYSQL_JSON_TEXT_KEY_TYPES = new Set(["varchar", "char", "character", "text", "tinytext", "mediumtext", "longtext"]);
|
|
3702
|
+
var MYSQL_JSON_INTEGER_KEY_TYPES = new Set(["int", "integer", "bigint", "smallint", "tinyint", "mediumint"]);
|
|
3703
|
+
function mysqlJsonKeyTypeOf(params) {
|
|
3704
|
+
const { member } = params;
|
|
3705
|
+
const native = member.nativeType?.toLowerCase().replace(/\([^)]*\)/g, "").replace(/\s+/g, "");
|
|
3706
|
+
if (member.type === "String")
|
|
3707
|
+
return native === undefined || MYSQL_JSON_TEXT_KEY_TYPES.has(native) ? "text" : undefined;
|
|
3708
|
+
if (member.type !== "Int" && member.type !== "BigInt")
|
|
3709
|
+
return;
|
|
3710
|
+
if (native === undefined)
|
|
3711
|
+
return "signed";
|
|
3712
|
+
const unsigned = native.startsWith("unsigned") || native.endsWith("unsigned");
|
|
3713
|
+
const base = native.replace(/^unsigned|unsigned$/g, "");
|
|
3714
|
+
if (!MYSQL_JSON_INTEGER_KEY_TYPES.has(base))
|
|
3715
|
+
return;
|
|
3716
|
+
return unsigned ? "unsigned" : "signed";
|
|
3717
|
+
}
|
|
3718
|
+
function jsonKeyTokenCast(params) {
|
|
3719
|
+
const { dialect, member } = params;
|
|
3720
|
+
return !(dialect.lateralJson?.keysFromJson === "jsonTable" && mysqlJsonKeyTypeOf({ member }) === "text");
|
|
3721
|
+
}
|
|
3722
|
+
function jsonKeysTyped(params) {
|
|
3723
|
+
const { dialect, members } = params;
|
|
3724
|
+
if (dialect.lateralJson?.keysFromJson === "jsonTable")
|
|
3725
|
+
return members.every((member) => mysqlJsonKeyTypeOf({ member }) !== undefined);
|
|
3726
|
+
return !keyListBindsUntypedRows({ members }) && members.every((member) => postgresKeyListElementTypeOf({ dialect, member }) !== undefined);
|
|
3727
|
+
}
|
|
3728
|
+
function postgresBulkInsertArrayCastOf(params) {
|
|
3729
|
+
const { dialect, member } = params;
|
|
3730
|
+
if (member.type === "Json")
|
|
3731
|
+
return member.nativeType?.trim().toLowerCase() === "json" ? "text::json" : "text::jsonb";
|
|
3732
|
+
return postgresKeyListElementTypeOf({ dialect, member });
|
|
3733
|
+
}
|
|
3734
|
+
function postgresBulkInsertArrayCast(params) {
|
|
3735
|
+
const { dialect, member } = params;
|
|
3736
|
+
const cast = postgresBulkInsertArrayCastOf({ dialect, member });
|
|
3737
|
+
if (cast === undefined) {
|
|
3738
|
+
throw unsupportedCapability16({
|
|
3739
|
+
dialect: dialect.name,
|
|
3740
|
+
feature: `a bulk insert binding a ${member.type}${member.nativeType === undefined ? "" : ` @db.${member.nativeType}`} column as a typed array`,
|
|
3741
|
+
hint: "the query builder keeps VALUES for this column type"
|
|
3742
|
+
});
|
|
3743
|
+
}
|
|
3744
|
+
return cast;
|
|
3745
|
+
}
|
|
3746
|
+
|
|
3747
|
+
// packages/sql/src/dialects/mysql-json-keys.ts
|
|
3748
|
+
var SOURCE_ALIAS = "__vibe_s";
|
|
3749
|
+
var ROWS_ALIAS = "__vibe_e";
|
|
3750
|
+
var SET_ALIAS = "__vibe_k";
|
|
3751
|
+
function slotPath(params) {
|
|
3752
|
+
const { slot } = params;
|
|
3753
|
+
if (!Number.isSafeInteger(slot.index) || !Number.isSafeInteger(slot.width) || slot.index < 0 || slot.index >= slot.width) {
|
|
3754
|
+
throw internalError2({ message: "a JSON slot must be an index inside its row" });
|
|
3755
|
+
}
|
|
3756
|
+
const size = JSON_TRANSPORT.chunkSize;
|
|
3757
|
+
return slot.width <= size ? `[${slot.index}]` : `[${Math.floor(slot.index / size)}][${slot.index % size}]`;
|
|
3758
|
+
}
|
|
3759
|
+
function renderMysqlJsonKeys(params) {
|
|
3760
|
+
const { dialect, statement, keys } = params;
|
|
3761
|
+
const quote = (name) => dialect.quoteIdent({ name });
|
|
3762
|
+
const source = quote(SOURCE_ALIAS);
|
|
3763
|
+
const rows = quote(ROWS_ALIAS);
|
|
3764
|
+
const set = quote(SET_ALIAS);
|
|
3765
|
+
const rowPath = `$${keys.steps.map((step) => step.kind === "elements" ? "[*]" : slotPath({ slot: step.slot })).join("")}`;
|
|
3766
|
+
const columns = keys.keys.map((key) => `${quote(key.token)} JSON PATH '$${slotPath({ slot: key.slot })}'`);
|
|
3767
|
+
const typed = keys.keys.flatMap((key) => {
|
|
3768
|
+
const type = mysqlJsonKeyTypeOf({ member: key.member });
|
|
3769
|
+
if (type === undefined) {
|
|
3770
|
+
throw internalError2({ message: "fromJsonKeys cannot type this key from its JSON text on mysql: only string and integer keys are admitted" });
|
|
3771
|
+
}
|
|
3772
|
+
const token = `${set}.${quote(key.token)}`;
|
|
3773
|
+
const text = `CASE WHEN JSON_TYPE(${token}) = 'NULL' THEN NULL ELSE JSON_UNQUOTE(${token}) END`;
|
|
3774
|
+
const value = type === "text" ? text : `CAST(${text} AS ${type === "signed" ? "SIGNED" : "UNSIGNED"})`;
|
|
3775
|
+
return [`${text} AS ${quote(key.token)}`, `${value} AS ${quote(key.alias)}`];
|
|
3776
|
+
});
|
|
3777
|
+
const distinct = keys.keys.map((key) => `${rows}.${quote(key.token)}`);
|
|
3778
|
+
const from = `${quote(keys.source.table)} AS ${source} JOIN JSON_TABLE(${source}.${quote(keys.source.name)}, '${rowPath}' COLUMNS (${columns.join(", ")})) AS ${rows}`;
|
|
3779
|
+
return `(SELECT ${typed.join(", ")} FROM (SELECT DISTINCT ${distinct.join(", ")} FROM ${from}) AS ${set}) AS ${quote(statement.table.name)}`;
|
|
3780
|
+
}
|
|
3781
|
+
function mysqlPinnedCommonTableSuffix(params) {
|
|
3782
|
+
const { select } = params.table;
|
|
3783
|
+
return params.table.materialized !== true || select.limit !== undefined || select.offset !== undefined ? "" : " LIMIT 18446744073709551615";
|
|
3784
|
+
}
|
|
3785
|
+
|
|
3615
3786
|
// packages/sql/src/dialects/diagnostics.ts
|
|
3616
|
-
import { assertSqlNamespace as assertSqlNamespace2, unsupportedCapability as
|
|
3787
|
+
import { assertSqlNamespace as assertSqlNamespace2, unsupportedCapability as unsupportedCapability17 } from "@vibeorm/schema";
|
|
3617
3788
|
var QUERIES = Object.freeze({
|
|
3618
3789
|
postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
|
|
3619
3790
|
postgresServerVersion: { dialect: "postgres", text: `SELECT pg_catalog.current_setting('server_version_num') AS "serverVersionNum"` },
|
|
@@ -3622,7 +3793,7 @@ var QUERIES = Object.freeze({
|
|
|
3622
3793
|
function renderDiagnostic(params) {
|
|
3623
3794
|
const query = QUERIES[params.query];
|
|
3624
3795
|
if (params.dialect.name !== query.dialect) {
|
|
3625
|
-
throw
|
|
3796
|
+
throw unsupportedCapability17({ dialect: params.dialect.name, feature: params.query, hint: `this diagnostic requires ${query.dialect}` });
|
|
3626
3797
|
}
|
|
3627
3798
|
return { text: query.text, values: [], returning: "none" };
|
|
3628
3799
|
}
|
|
@@ -3633,7 +3804,7 @@ var CAPTURE_READINESS_SQL = `SELECT pg_catalog.current_setting('server_version_n
|
|
|
3633
3804
|
function renderCaptureReadiness(params) {
|
|
3634
3805
|
const { dialect, tables } = params;
|
|
3635
3806
|
if (dialect.name !== "postgres") {
|
|
3636
|
-
throw
|
|
3807
|
+
throw unsupportedCapability17({ dialect: dialect.name, feature: "postgresCaptureReadiness", hint: "row capture reads RETURNING old/new, PostgreSQL 18 or newer" });
|
|
3637
3808
|
}
|
|
3638
3809
|
const entries = tables.map((entry, index) => {
|
|
3639
3810
|
const { name, namespace } = entry.table;
|
|
@@ -3647,7 +3818,7 @@ function renderCaptureReadiness(params) {
|
|
|
3647
3818
|
}
|
|
3648
3819
|
|
|
3649
3820
|
// packages/sql/src/dialects/postgres-diagnostics.ts
|
|
3650
|
-
import { VibeError as VibeError16, unsupportedCapability as
|
|
3821
|
+
import { VibeError as VibeError16, unsupportedCapability as unsupportedCapability18 } from "@vibeorm/schema";
|
|
3651
3822
|
function renderPostgresExplain(params) {
|
|
3652
3823
|
const { dialect, query, options } = params;
|
|
3653
3824
|
requirePostgres({ dialect, feature: "query plan inspection (EXPLAIN)" });
|
|
@@ -3682,7 +3853,7 @@ var POSTGRES_WORKLOAD_LIMITS = Object.freeze({
|
|
|
3682
3853
|
});
|
|
3683
3854
|
function requirePostgres(params) {
|
|
3684
3855
|
if (params.dialect.name !== "postgres") {
|
|
3685
|
-
throw
|
|
3856
|
+
throw unsupportedCapability18({
|
|
3686
3857
|
dialect: params.dialect.name,
|
|
3687
3858
|
feature: params.feature,
|
|
3688
3859
|
hint: "plan inspection and workload statistics are PostgreSQL-shaped; portable operation tracing works on every dialect"
|
|
@@ -3729,10 +3900,10 @@ function requireArg(params) {
|
|
|
3729
3900
|
}
|
|
3730
3901
|
|
|
3731
3902
|
// packages/sql/src/dialects/postgres-rls-readiness.ts
|
|
3732
|
-
import { unsupportedCapability as
|
|
3903
|
+
import { unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
|
|
3733
3904
|
|
|
3734
3905
|
// packages/sql/src/dialects/postgres-rls.ts
|
|
3735
|
-
import { RlsContextError, VibeError as VibeError17, assertNativeRlsAdmission, assertValidSchemaIR, unsupportedCapability as
|
|
3906
|
+
import { RlsContextError, VibeError as VibeError17, assertNativeRlsAdmission, assertValidSchemaIR, unsupportedCapability as unsupportedCapability19, validateRlsContextIR } from "@vibeorm/schema";
|
|
3736
3907
|
var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
|
|
3737
3908
|
var COLUMN_TYPES = {
|
|
3738
3909
|
String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
|
|
@@ -3745,7 +3916,7 @@ var COLUMN_TYPES = {
|
|
|
3745
3916
|
};
|
|
3746
3917
|
function requireRls(params) {
|
|
3747
3918
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
3748
|
-
throw
|
|
3919
|
+
throw unsupportedCapability19({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
3749
3920
|
}
|
|
3750
3921
|
}
|
|
3751
3922
|
function identifier2(params) {
|
|
@@ -3756,7 +3927,7 @@ function identifier2(params) {
|
|
|
3756
3927
|
}
|
|
3757
3928
|
function tableRef(params) {
|
|
3758
3929
|
if (params.schema !== undefined && params.schema !== "public" && params.schema !== "pg_temp") {
|
|
3759
|
-
throw
|
|
3930
|
+
throw unsupportedCapability19({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: "Only public tables and temporary policy probes are supported" });
|
|
3760
3931
|
}
|
|
3761
3932
|
return `${identifier2({ dialect: params.dialect, name: params.schema ?? NATIVE_RLS.tableSchema })}.${identifier2({ dialect: params.dialect, name: params.table })}`;
|
|
3762
3933
|
}
|
|
@@ -3797,7 +3968,7 @@ function renderRlsColumnType(params) {
|
|
|
3797
3968
|
if (native !== undefined)
|
|
3798
3969
|
return native;
|
|
3799
3970
|
}
|
|
3800
|
-
throw
|
|
3971
|
+
throw unsupportedCapability19({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: `Unsupported physical ownership type on field ${field.name}` });
|
|
3801
3972
|
}
|
|
3802
3973
|
function protectedModel(params) {
|
|
3803
3974
|
requireRls(params);
|
|
@@ -4037,7 +4208,7 @@ ORDER BY c.relname, p.polname`,
|
|
|
4037
4208
|
});
|
|
4038
4209
|
function requireRls2(params) {
|
|
4039
4210
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
4040
|
-
throw
|
|
4211
|
+
throw unsupportedCapability20({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
4041
4212
|
}
|
|
4042
4213
|
}
|
|
4043
4214
|
function renderRlsReadinessQuery(params) {
|
|
@@ -4069,7 +4240,7 @@ function renderRlsPolicyPath(params) {
|
|
|
4069
4240
|
}
|
|
4070
4241
|
|
|
4071
4242
|
// packages/sql/src/render.ts
|
|
4072
|
-
import { assertSqlNamespace as assertSqlNamespace3, internalError as
|
|
4243
|
+
import { assertSqlNamespace as assertSqlNamespace3, internalError as internalError5, unsupportedCapability as unsupportedCapability23 } from "@vibeorm/schema";
|
|
4073
4244
|
|
|
4074
4245
|
// packages/sql/src/like.ts
|
|
4075
4246
|
function escapeLikePattern(params) {
|
|
@@ -4100,7 +4271,7 @@ function strictMatchCollationPrefilter(params) {
|
|
|
4100
4271
|
}
|
|
4101
4272
|
|
|
4102
4273
|
// packages/sql/src/locks.ts
|
|
4103
|
-
import { internalError as
|
|
4274
|
+
import { internalError as internalError3, unsupportedCapability as unsupportedCapability21 } from "@vibeorm/schema";
|
|
4104
4275
|
var ROW_LOCK_PROSE = Object.freeze({
|
|
4105
4276
|
strengths: Object.freeze({
|
|
4106
4277
|
update: "FOR UPDATE",
|
|
@@ -4120,7 +4291,7 @@ function renderRowLockClause(params) {
|
|
|
4120
4291
|
if (syntax === null) {
|
|
4121
4292
|
if (lock.strength === "update" && lock.wait === "wait")
|
|
4122
4293
|
return null;
|
|
4123
|
-
throw
|
|
4294
|
+
throw unsupportedCapability21({
|
|
4124
4295
|
dialect: dialect.name,
|
|
4125
4296
|
feature: `a ${describeRowLock({ lock })} locking read`,
|
|
4126
4297
|
hint: `${dialect.name} has no locking-read clause at all`
|
|
@@ -4128,7 +4299,7 @@ function renderRowLockClause(params) {
|
|
|
4128
4299
|
}
|
|
4129
4300
|
const strengthText = syntax.strengths[lock.strength];
|
|
4130
4301
|
if (strengthText === undefined) {
|
|
4131
|
-
throw
|
|
4302
|
+
throw unsupportedCapability21({
|
|
4132
4303
|
dialect: dialect.name,
|
|
4133
4304
|
feature: `the ${describeRowLock({ lock: { ...lock, wait: "wait" } })} row-lock strength`,
|
|
4134
4305
|
hint: `${dialect.name} can express: ${Object.keys(syntax.strengths).join(", ")}`
|
|
@@ -4136,7 +4307,7 @@ function renderRowLockClause(params) {
|
|
|
4136
4307
|
}
|
|
4137
4308
|
const waitText = syntax.wait[lock.wait];
|
|
4138
4309
|
if (waitText === undefined) {
|
|
4139
|
-
throw
|
|
4310
|
+
throw unsupportedCapability21({
|
|
4140
4311
|
dialect: dialect.name,
|
|
4141
4312
|
feature: `the "${lock.wait}" lock wait policy`,
|
|
4142
4313
|
hint: `${dialect.name} can express: ${Object.keys(syntax.wait).join(", ")}`
|
|
@@ -4144,7 +4315,7 @@ function renderRowLockClause(params) {
|
|
|
4144
4315
|
}
|
|
4145
4316
|
if (lock.of !== undefined) {
|
|
4146
4317
|
if (lock.of.length === 0)
|
|
4147
|
-
throw
|
|
4318
|
+
throw internalError3({ message: "a row lock's `of` list cannot be empty" });
|
|
4148
4319
|
const tables = lock.of.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
4149
4320
|
return `${strengthText} OF ${tables}${waitText}`;
|
|
4150
4321
|
}
|
|
@@ -4162,7 +4333,7 @@ function renderAdvisoryLock(params) {
|
|
|
4162
4333
|
const { dialect, statement, collector } = params;
|
|
4163
4334
|
const functions = dialect.advisoryLockFunctions;
|
|
4164
4335
|
if (functions === null) {
|
|
4165
|
-
throw
|
|
4336
|
+
throw unsupportedCapability21({
|
|
4166
4337
|
dialect: dialect.name,
|
|
4167
4338
|
feature: "advisory locks",
|
|
4168
4339
|
hint: dialect.name === "mysql" ? "mysql's GET_LOCK is SESSION-scoped \u2014 it survives a rollback, so it cannot be released with the transaction" : `${dialect.name} has no advisory-lock functions`
|
|
@@ -4183,11 +4354,11 @@ function renderAdvisoryLocks(params) {
|
|
|
4183
4354
|
const { dialect, keys, mode } = params;
|
|
4184
4355
|
const functions = dialect.advisoryLockFunctions;
|
|
4185
4356
|
if (functions === null)
|
|
4186
|
-
throw
|
|
4357
|
+
throw unsupportedCapability21({ dialect: dialect.name, feature: "advisory locks" });
|
|
4187
4358
|
if (mode !== "exclusive" && mode !== "shared")
|
|
4188
|
-
throw
|
|
4359
|
+
throw internalError3({ message: "advisory lock mode must be exclusive or shared" });
|
|
4189
4360
|
if (keys.some((key) => typeof key !== "bigint" || key < ADVISORY_KEY_BOUNDS.single.min || key > ADVISORY_KEY_BOUNDS.single.max)) {
|
|
4190
|
-
throw
|
|
4361
|
+
throw internalError3({ message: "advisory lock keys must be signed 64-bit integers" });
|
|
4191
4362
|
}
|
|
4192
4363
|
const collector = new ParamCollector(dialect);
|
|
4193
4364
|
const binding = collector.add(JSON.stringify(keys.map((key) => key.toString())));
|
|
@@ -4204,7 +4375,7 @@ function renderAdvisoryLocks(params) {
|
|
|
4204
4375
|
function assertInt32(params) {
|
|
4205
4376
|
const { value, part } = params;
|
|
4206
4377
|
if (!Number.isInteger(value) || value < ADVISORY_KEY_BOUNDS.pair.min || value > ADVISORY_KEY_BOUNDS.pair.max) {
|
|
4207
|
-
throw
|
|
4378
|
+
throw internalError3({
|
|
4208
4379
|
message: `advisory lock ${part} must be a 32-bit integer, got ${String(value)}`,
|
|
4209
4380
|
meta: { part, value }
|
|
4210
4381
|
});
|
|
@@ -4213,7 +4384,7 @@ function assertInt32(params) {
|
|
|
4213
4384
|
}
|
|
4214
4385
|
|
|
4215
4386
|
// packages/sql/src/date-truncation.ts
|
|
4216
|
-
import { DATE_TRUNCATION_UNITS, VibeError as VibeError18, internalError as
|
|
4387
|
+
import { DATE_TRUNCATION_UNITS, VibeError as VibeError18, internalError as internalError4, unsupportedCapability as unsupportedCapability22 } from "@vibeorm/schema";
|
|
4217
4388
|
var TIME_ZONE_NAME = /^[A-Za-z][A-Za-z0-9_+\-/]{0,63}$/;
|
|
4218
4389
|
function isTimeZoneName(params) {
|
|
4219
4390
|
return typeof params.value === "string" && TIME_ZONE_NAME.test(params.value);
|
|
@@ -4221,14 +4392,14 @@ function isTimeZoneName(params) {
|
|
|
4221
4392
|
function renderDateTruncation(params) {
|
|
4222
4393
|
const { dialect, reference, truncation } = params;
|
|
4223
4394
|
if (!dialect.capabilities.dateTruncation) {
|
|
4224
|
-
throw
|
|
4395
|
+
throw unsupportedCapability22({
|
|
4225
4396
|
dialect: dialect.name,
|
|
4226
4397
|
feature: "a truncated groupBy key ({ field, truncate, timeZone })",
|
|
4227
4398
|
hint: "only PostgreSQL and PGlite truncate a timestamp in a named time zone \u2014 group by the plain field and bucket the rows in code instead"
|
|
4228
4399
|
});
|
|
4229
4400
|
}
|
|
4230
4401
|
if (!DATE_TRUNCATION_UNITS.includes(truncation.unit)) {
|
|
4231
|
-
throw
|
|
4402
|
+
throw internalError4({ message: `unknown date truncation unit ${JSON.stringify(truncation.unit)}` });
|
|
4232
4403
|
}
|
|
4233
4404
|
if (!isTimeZoneName({ value: truncation.timeZone })) {
|
|
4234
4405
|
throw new VibeError18({
|
|
@@ -4245,110 +4416,8 @@ function renderDateTruncation(params) {
|
|
|
4245
4416
|
case "timestamp":
|
|
4246
4417
|
return `(date_trunc(${unit}, ${reference} AT TIME ZONE 'UTC', ${zone}) AT TIME ZONE 'UTC')`;
|
|
4247
4418
|
default:
|
|
4248
|
-
throw
|
|
4249
|
-
}
|
|
4250
|
-
}
|
|
4251
|
-
|
|
4252
|
-
// packages/sql/src/key-list.ts
|
|
4253
|
-
import { unsupportedCapability as unsupportedCapability22 } from "@vibeorm/schema";
|
|
4254
|
-
var SCALAR_ELEMENT_TYPES = Object.freeze({
|
|
4255
|
-
String: "text",
|
|
4256
|
-
Boolean: "bool",
|
|
4257
|
-
Int: "int4",
|
|
4258
|
-
BigInt: "int8",
|
|
4259
|
-
Float: "float8",
|
|
4260
|
-
Decimal: "numeric",
|
|
4261
|
-
DateTime: "timestamptz",
|
|
4262
|
-
Json: null,
|
|
4263
|
-
Bytes: "bytea"
|
|
4264
|
-
});
|
|
4265
|
-
var NATIVE_ELEMENT_TYPES = Object.freeze({
|
|
4266
|
-
text: "text",
|
|
4267
|
-
varchar: "varchar",
|
|
4268
|
-
charactervarying: "varchar",
|
|
4269
|
-
char: "bpchar",
|
|
4270
|
-
character: "bpchar",
|
|
4271
|
-
bpchar: "bpchar",
|
|
4272
|
-
uuid: "uuid",
|
|
4273
|
-
citext: "citext",
|
|
4274
|
-
xml: "xml",
|
|
4275
|
-
inet: "inet",
|
|
4276
|
-
money: "money",
|
|
4277
|
-
bit: '"bit"',
|
|
4278
|
-
varbit: "varbit",
|
|
4279
|
-
bitvarying: "varbit",
|
|
4280
|
-
smallint: "int2",
|
|
4281
|
-
int2: "int2",
|
|
4282
|
-
integer: "int4",
|
|
4283
|
-
int: "int4",
|
|
4284
|
-
int4: "int4",
|
|
4285
|
-
bigint: "int8",
|
|
4286
|
-
int8: "int8",
|
|
4287
|
-
oid: "oid",
|
|
4288
|
-
real: "float4",
|
|
4289
|
-
float4: "float4",
|
|
4290
|
-
doubleprecision: "float8",
|
|
4291
|
-
double: "float8",
|
|
4292
|
-
float8: "float8",
|
|
4293
|
-
decimal: "numeric",
|
|
4294
|
-
numeric: "numeric",
|
|
4295
|
-
boolean: "bool",
|
|
4296
|
-
bool: "bool",
|
|
4297
|
-
timestamp: "timestamp",
|
|
4298
|
-
timestampwithouttimezone: "timestamp",
|
|
4299
|
-
timestamptz: "timestamptz",
|
|
4300
|
-
timestampwithtimezone: "timestamptz",
|
|
4301
|
-
date: "date",
|
|
4302
|
-
time: "time",
|
|
4303
|
-
timewithouttimezone: "time",
|
|
4304
|
-
timetz: "timetz",
|
|
4305
|
-
timewithtimezone: "timetz",
|
|
4306
|
-
bytea: "bytea"
|
|
4307
|
-
});
|
|
4308
|
-
function postgresKeyListElementTypeOf(params) {
|
|
4309
|
-
const { dialect, member } = params;
|
|
4310
|
-
if (member.type === "Enum") {
|
|
4311
|
-
if (member.enumType === undefined)
|
|
4312
|
-
return "text";
|
|
4313
|
-
const name = dialect.quoteIdent({ name: member.enumType.name });
|
|
4314
|
-
return member.enumType.namespace === undefined ? name : `${dialect.quoteIdent({ name: member.enumType.namespace })}.${name}`;
|
|
4419
|
+
throw internalError4({ message: `unknown date truncation storage ${JSON.stringify(truncation.storage)}` });
|
|
4315
4420
|
}
|
|
4316
|
-
const native = member.nativeType === undefined ? undefined : NATIVE_ELEMENT_TYPES[member.nativeType.toLowerCase().replace(/\([^)]*\)/g, "").replace(/\s+/g, "")];
|
|
4317
|
-
const type = member.nativeType === undefined ? SCALAR_ELEMENT_TYPES[member.type] : native;
|
|
4318
|
-
return type === null ? undefined : type;
|
|
4319
|
-
}
|
|
4320
|
-
function postgresKeyListElementType(params) {
|
|
4321
|
-
const { dialect, member } = params;
|
|
4322
|
-
const type = postgresKeyListElementTypeOf({ dialect, member });
|
|
4323
|
-
if (type === undefined) {
|
|
4324
|
-
throw unsupportedCapability22({
|
|
4325
|
-
dialect: dialect.name,
|
|
4326
|
-
feature: `the key-list filter (IN) on a ${member.type}${member.nativeType === undefined ? "" : ` @db.${member.nativeType}`} member`,
|
|
4327
|
-
hint: "IN binds every member as a typed array; filter this field with equality inside OR instead"
|
|
4328
|
-
});
|
|
4329
|
-
}
|
|
4330
|
-
return type;
|
|
4331
|
-
}
|
|
4332
|
-
function keyListBindsUntypedRows(params) {
|
|
4333
|
-
return params.members.some((member) => member.type === "DateTime" && member.nativeType === undefined);
|
|
4334
|
-
}
|
|
4335
|
-
function postgresBulkInsertArrayCastOf(params) {
|
|
4336
|
-
const { dialect, member } = params;
|
|
4337
|
-
if (member.type === "Json")
|
|
4338
|
-
return member.nativeType?.trim().toLowerCase() === "json" ? "text::json" : "text::jsonb";
|
|
4339
|
-
return postgresKeyListElementTypeOf({ dialect, member });
|
|
4340
|
-
}
|
|
4341
|
-
function postgresBulkInsertArrayCast(params) {
|
|
4342
|
-
const { dialect, member } = params;
|
|
4343
|
-
const cast = postgresBulkInsertArrayCastOf({ dialect, member });
|
|
4344
|
-
if (cast === undefined) {
|
|
4345
|
-
throw unsupportedCapability22({
|
|
4346
|
-
dialect: dialect.name,
|
|
4347
|
-
feature: `a bulk insert binding a ${member.type}${member.nativeType === undefined ? "" : ` @db.${member.nativeType}`} column as a typed array`,
|
|
4348
|
-
hint: "the query builder keeps VALUES for this column type"
|
|
4349
|
-
});
|
|
4350
|
-
}
|
|
4351
|
-
return cast;
|
|
4352
4421
|
}
|
|
4353
4422
|
|
|
4354
4423
|
// packages/sql/src/render.ts
|
|
@@ -4371,7 +4440,7 @@ function renderStatement(params) {
|
|
|
4371
4440
|
return renderPostgresWorkload({ dialect, ...statement });
|
|
4372
4441
|
}
|
|
4373
4442
|
case "select":
|
|
4374
|
-
return renderSelect({ dialect, statement, collector });
|
|
4443
|
+
return renderSelect({ dialect, statement, collector, outermost: true });
|
|
4375
4444
|
case "advisoryLocks":
|
|
4376
4445
|
return renderAdvisoryLocks({ dialect, keys: statement.keys, mode: statement.mode });
|
|
4377
4446
|
case "advisoryLock":
|
|
@@ -4411,7 +4480,7 @@ function renderStatement(params) {
|
|
|
4411
4480
|
function quoteCol(dialect, column) {
|
|
4412
4481
|
if (column.coalesce !== undefined) {
|
|
4413
4482
|
if (column.coalesce.length < 2 || column.coalesce.some((member) => member.coalesce !== undefined)) {
|
|
4414
|
-
throw
|
|
4483
|
+
throw internalError5({ message: "ColRef.coalesce needs two or more plain member references" });
|
|
4415
4484
|
}
|
|
4416
4485
|
return `COALESCE(${column.coalesce.map((member) => quoteCol(dialect, member)).join(", ")})`;
|
|
4417
4486
|
}
|
|
@@ -4425,13 +4494,13 @@ function quoteCol(dialect, column) {
|
|
|
4425
4494
|
return dialect.renderEnumOrdinal({ column: ref });
|
|
4426
4495
|
if (column.castReal === true) {
|
|
4427
4496
|
if (dialect.name !== "sqlite") {
|
|
4428
|
-
throw
|
|
4497
|
+
throw internalError5({ message: "ColRef.castReal is a sqlite Decimal construct" });
|
|
4429
4498
|
}
|
|
4430
4499
|
return `CAST(${ref} AS REAL)`;
|
|
4431
4500
|
}
|
|
4432
4501
|
if (column.castJsonb !== undefined) {
|
|
4433
4502
|
if (dialect.name !== "postgres") {
|
|
4434
|
-
throw
|
|
4503
|
+
throw internalError5({ message: "ColRef.castJsonb is a postgres json construct" });
|
|
4435
4504
|
}
|
|
4436
4505
|
return `${ref}::${column.castJsonb}`;
|
|
4437
4506
|
}
|
|
@@ -4450,7 +4519,7 @@ function jsonCellCast(dialect, cell) {
|
|
|
4450
4519
|
if (cell.storage !== "json" || dialect.jsonParamCast === "")
|
|
4451
4520
|
return dialect.jsonParamCast;
|
|
4452
4521
|
if (dialect.name !== "postgres") {
|
|
4453
|
-
throw
|
|
4522
|
+
throw internalError5({ message: "JsonParamCell storage json is a postgres construct" });
|
|
4454
4523
|
}
|
|
4455
4524
|
return "::text::json";
|
|
4456
4525
|
}
|
|
@@ -4474,7 +4543,7 @@ function quoteProjection(dialect, column) {
|
|
|
4474
4543
|
if (column.transform === "encodeBase64" && dialect.name === "mysql")
|
|
4475
4544
|
return `TO_BASE64(${ref}) AS ${alias}`;
|
|
4476
4545
|
if (dialect.name !== "postgres") {
|
|
4477
|
-
throw
|
|
4546
|
+
throw internalError5({ message: `${column.transform} is a postgres construct` });
|
|
4478
4547
|
}
|
|
4479
4548
|
if (column.transform === "encodeArrayBase64")
|
|
4480
4549
|
return `encode(array_send(${ref}), 'base64') AS ${alias}`;
|
|
@@ -4498,7 +4567,7 @@ function renderTable(dialect, table) {
|
|
|
4498
4567
|
}
|
|
4499
4568
|
function assertCount(value, what) {
|
|
4500
4569
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
4501
|
-
throw
|
|
4570
|
+
throw internalError5({ message: `${what} must be a non-negative integer, got ${value}` });
|
|
4502
4571
|
}
|
|
4503
4572
|
}
|
|
4504
4573
|
var COMPARISON_SQL2 = {
|
|
@@ -4554,14 +4623,14 @@ function renderGroupKey(params) {
|
|
|
4554
4623
|
const { dialect, reference, key } = params;
|
|
4555
4624
|
if (key.kind === "prefix") {
|
|
4556
4625
|
if (!Number.isSafeInteger(key.length) || key.length < 1 || key.length > MAX_GROUP_KEY_PREFIX) {
|
|
4557
|
-
throw
|
|
4626
|
+
throw internalError5({ message: `a groupBy prefix length must be an integer from 1 to ${MAX_GROUP_KEY_PREFIX}, got ${String(key.length)}` });
|
|
4558
4627
|
}
|
|
4559
4628
|
if (dialect.name === "sqlite")
|
|
4560
4629
|
return `substr(${reference}, 1, ${key.length})`;
|
|
4561
4630
|
return `${dialect.name === "mysql" ? "LEFT" : "left"}(${reference}, ${key.length})`;
|
|
4562
4631
|
}
|
|
4563
4632
|
if (key.path.length === 0 || key.path.some((step) => typeof step !== "string" || step.length === 0 || step.includes("\x00"))) {
|
|
4564
|
-
throw
|
|
4633
|
+
throw internalError5({ message: "a groupBy JSON path needs one or more non-empty object keys without NUL characters" });
|
|
4565
4634
|
}
|
|
4566
4635
|
switch (dialect.jsonPathMode) {
|
|
4567
4636
|
case "operators": {
|
|
@@ -4600,11 +4669,11 @@ function renderGroupingSets(params) {
|
|
|
4600
4669
|
function groupingSetIndexes(params) {
|
|
4601
4670
|
const { keys, grouping } = params;
|
|
4602
4671
|
if (grouping.sets.length === 0 || keys.length === 0 || keys.length > 31) {
|
|
4603
|
-
throw
|
|
4672
|
+
throw internalError5({ message: "grouping sets need one or more sets over 1 to 31 groupBy keys" });
|
|
4604
4673
|
}
|
|
4605
4674
|
for (const set of grouping.sets) {
|
|
4606
4675
|
if (set.some((index) => !Number.isSafeInteger(index) || index < 0 || index >= keys.length)) {
|
|
4607
|
-
throw
|
|
4676
|
+
throw internalError5({ message: "a grouping set names a groupBy key that does not exist" });
|
|
4608
4677
|
}
|
|
4609
4678
|
}
|
|
4610
4679
|
return grouping.sets;
|
|
@@ -4650,7 +4719,7 @@ function renderExpr(params) {
|
|
|
4650
4719
|
case "cmp": {
|
|
4651
4720
|
const op = COMPARISON_SQL2[expr.op];
|
|
4652
4721
|
if (op === undefined) {
|
|
4653
|
-
throw
|
|
4722
|
+
throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
|
|
4654
4723
|
}
|
|
4655
4724
|
if (isDbNowMarker(expr.value)) {
|
|
4656
4725
|
return `${quoteCol(dialect, expr.column)} ${op} ${dialect.renderDbNow({ offsetSeconds: expr.value.offsetSeconds, collector })}`;
|
|
@@ -4661,7 +4730,7 @@ function renderExpr(params) {
|
|
|
4661
4730
|
case "cmpCol": {
|
|
4662
4731
|
const op = COMPARISON_SQL2[expr.op];
|
|
4663
4732
|
if (op === undefined) {
|
|
4664
|
-
throw
|
|
4733
|
+
throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
|
|
4665
4734
|
}
|
|
4666
4735
|
return `${quoteCol(dialect, expr.left)} ${op} ${quoteCol(dialect, expr.right)}`;
|
|
4667
4736
|
}
|
|
@@ -4685,11 +4754,11 @@ function renderExpr(params) {
|
|
|
4685
4754
|
}
|
|
4686
4755
|
case "tupleIn": {
|
|
4687
4756
|
if (expr.columns.length === 0) {
|
|
4688
|
-
throw
|
|
4757
|
+
throw internalError5({ message: "tupleIn needs at least one column" });
|
|
4689
4758
|
}
|
|
4690
4759
|
for (const row of expr.rows) {
|
|
4691
4760
|
if (row.length !== expr.columns.length) {
|
|
4692
|
-
throw
|
|
4761
|
+
throw internalError5({
|
|
4693
4762
|
message: `tupleIn row has ${row.length} values for ${expr.columns.length} columns`
|
|
4694
4763
|
});
|
|
4695
4764
|
}
|
|
@@ -4715,7 +4784,7 @@ function renderExpr(params) {
|
|
|
4715
4784
|
case "rowCompare": {
|
|
4716
4785
|
const op = COMPARISON_SQL2[expr.op];
|
|
4717
4786
|
if (op === undefined || expr.columns.length === 0 || expr.values.length !== expr.columns.length) {
|
|
4718
|
-
throw
|
|
4787
|
+
throw internalError5({ message: `rowCompare needs a range operator and one value per column (${expr.columns.length} columns, ${expr.values.length} values)` });
|
|
4719
4788
|
}
|
|
4720
4789
|
const columns = expr.columns.map((column) => quoteCol(dialect, column));
|
|
4721
4790
|
const placeholders = expr.values.map((value) => collector.add(value));
|
|
@@ -4723,7 +4792,7 @@ function renderExpr(params) {
|
|
|
4723
4792
|
}
|
|
4724
4793
|
case "inSelect": {
|
|
4725
4794
|
if (expr.columns.length === 0)
|
|
4726
|
-
throw
|
|
4795
|
+
throw internalError5({ message: "inSelect needs at least one column" });
|
|
4727
4796
|
if (dialect.name === "mysql" && (expr.select.limit !== undefined || expr.select.offset !== undefined)) {
|
|
4728
4797
|
throw unsupportedCapability23({
|
|
4729
4798
|
dialect: dialect.name,
|
|
@@ -4733,7 +4802,7 @@ function renderExpr(params) {
|
|
|
4733
4802
|
}
|
|
4734
4803
|
const columns = expr.columns.map((column) => quoteCol(dialect, column));
|
|
4735
4804
|
if (expr.single === true && columns.length !== 1)
|
|
4736
|
-
throw
|
|
4805
|
+
throw internalError5({ message: "a single-row inSelect compares exactly one column" });
|
|
4737
4806
|
const tested = columns.length === 1 ? columns[0] : `(${columns.join(", ")})`;
|
|
4738
4807
|
const sub = renderSelect({ dialect, statement: expr.select, collector });
|
|
4739
4808
|
return expr.single === true ? `${tested} = (${sub.text})` : `${tested} IN (${sub.text})`;
|
|
@@ -4798,7 +4867,7 @@ function renderExpr(params) {
|
|
|
4798
4867
|
case "aggCmp": {
|
|
4799
4868
|
const op = COMPARISON_SQL2[expr.op];
|
|
4800
4869
|
if (op === undefined) {
|
|
4801
|
-
throw
|
|
4870
|
+
throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
|
|
4802
4871
|
}
|
|
4803
4872
|
return `${renderAggRef(dialect, expr.agg, collector)} ${op} ${collector.add(expr.value)}`;
|
|
4804
4873
|
}
|
|
@@ -4867,13 +4936,13 @@ function renderKeyList(params) {
|
|
|
4867
4936
|
const { dialect, expr, collector } = params;
|
|
4868
4937
|
const arity = expr.columns.length;
|
|
4869
4938
|
if (arity === 0)
|
|
4870
|
-
throw
|
|
4939
|
+
throw internalError5({ message: "keyList needs at least one column" });
|
|
4871
4940
|
if (expr.members.length !== arity) {
|
|
4872
|
-
throw
|
|
4941
|
+
throw internalError5({ message: `keyList has ${expr.members.length} members for ${arity} columns` });
|
|
4873
4942
|
}
|
|
4874
4943
|
for (const row of expr.rows) {
|
|
4875
4944
|
if (row.length !== arity)
|
|
4876
|
-
throw
|
|
4945
|
+
throw internalError5({ message: `keyList row has ${row.length} values for ${arity} columns` });
|
|
4877
4946
|
}
|
|
4878
4947
|
if (expr.rows.length === 0)
|
|
4879
4948
|
return "1 = 0";
|
|
@@ -4905,22 +4974,22 @@ function renderKeyPage(params) {
|
|
|
4905
4974
|
const { select } = page;
|
|
4906
4975
|
const arity = page.keyColumns.length;
|
|
4907
4976
|
if (arity === 0 || page.members.length !== arity) {
|
|
4908
|
-
throw
|
|
4977
|
+
throw internalError5({ message: `keyPage has ${page.members.length} members for ${arity} key columns` });
|
|
4909
4978
|
}
|
|
4910
4979
|
if (page.rows.length === 0)
|
|
4911
|
-
throw
|
|
4980
|
+
throw internalError5({ message: "keyPage needs at least one key" });
|
|
4912
4981
|
for (const row of page.rows) {
|
|
4913
4982
|
if (row.length !== arity)
|
|
4914
|
-
throw
|
|
4983
|
+
throw internalError5({ message: `keyPage row has ${row.length} values for ${arity} key columns` });
|
|
4915
4984
|
}
|
|
4916
4985
|
if (page.identity.length === 0)
|
|
4917
|
-
throw
|
|
4986
|
+
throw internalError5({ message: "keyPage needs the child's identity columns" });
|
|
4918
4987
|
const order = select.orderBy ?? [];
|
|
4919
4988
|
if (order.length === 0 || order.some((term) => !("column" in term))) {
|
|
4920
|
-
throw
|
|
4989
|
+
throw internalError5({ message: "keyPage needs a non-empty order of plain column terms" });
|
|
4921
4990
|
}
|
|
4922
4991
|
if (select.table.alias !== undefined || Object.keys(select).some((member) => !KEY_PAGE_SELECT_MEMBERS.has(member))) {
|
|
4923
|
-
throw
|
|
4992
|
+
throw internalError5({ message: "keyPage select carries a member the per-key page cannot render" });
|
|
4924
4993
|
}
|
|
4925
4994
|
if (dialect.keyPageStrategy === "window") {
|
|
4926
4995
|
throw unsupportedCapability23({
|
|
@@ -4979,19 +5048,19 @@ function renderKeyPage(params) {
|
|
|
4979
5048
|
function renderKeyRows(params) {
|
|
4980
5049
|
const { dialect, statement, keys, collector } = params;
|
|
4981
5050
|
if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.fromUnion !== undefined || statement.table.alias !== undefined) {
|
|
4982
|
-
throw
|
|
5051
|
+
throw internalError5({ message: "fromKeys uses table.name as the derived-table alias \u2014 fromSelect, keyPage, fromUnion and table.alias must be unset" });
|
|
4983
5052
|
}
|
|
4984
5053
|
if (statement.rowLock !== undefined)
|
|
4985
|
-
throw
|
|
5054
|
+
throw internalError5({ message: "a locking read cannot read from key rows" });
|
|
4986
5055
|
const arity = keys.columns.length;
|
|
4987
5056
|
if (arity === 0 || keys.members.length !== arity) {
|
|
4988
|
-
throw
|
|
5057
|
+
throw internalError5({ message: `fromKeys has ${keys.members.length} members for ${arity} columns` });
|
|
4989
5058
|
}
|
|
4990
5059
|
if (keys.rows.length === 0)
|
|
4991
|
-
throw
|
|
5060
|
+
throw internalError5({ message: "fromKeys needs at least one key row" });
|
|
4992
5061
|
for (const row of keys.rows) {
|
|
4993
5062
|
if (row.length !== arity)
|
|
4994
|
-
throw
|
|
5063
|
+
throw internalError5({ message: `fromKeys row has ${row.length} values for ${arity} columns` });
|
|
4995
5064
|
}
|
|
4996
5065
|
if (dialect.keyListStrategy !== "unnest" || keyListBindsUntypedRows({ members: keys.members })) {
|
|
4997
5066
|
throw unsupportedCapability23({
|
|
@@ -5005,25 +5074,145 @@ function renderKeyRows(params) {
|
|
|
5005
5074
|
const names = keys.columns.map((name) => dialect.quoteIdent({ name }));
|
|
5006
5075
|
return `unnest(${arrays.join(", ")}) AS ${dialect.quoteIdent({ name: statement.table.name })} (${names.join(", ")})`;
|
|
5007
5076
|
}
|
|
5077
|
+
function renderOuterRow(params) {
|
|
5078
|
+
const { dialect, statement, row } = params;
|
|
5079
|
+
if (!dialect.capabilities.carrierRows) {
|
|
5080
|
+
throw unsupportedCapability23({
|
|
5081
|
+
dialect: dialect.name,
|
|
5082
|
+
feature: "carrier rows",
|
|
5083
|
+
hint: "lists below a shared to-one nest on small levels and load by key otherwise on this dialect"
|
|
5084
|
+
});
|
|
5085
|
+
}
|
|
5086
|
+
if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.fromUnion !== undefined || statement.table.alias !== undefined || statement.table.namespace !== undefined || statement.rowLock !== undefined) {
|
|
5087
|
+
throw internalError5({ message: "fromRow uses table.name as the derived-table alias \u2014 fromSelect, keyPage, fromUnion, table.alias, a namespace and a row lock must be unset" });
|
|
5088
|
+
}
|
|
5089
|
+
const names = row.map((entry) => entry.alias);
|
|
5090
|
+
if (row.length === 0 || new Set(names).size !== names.length || row.some((entry) => entry.column.table === undefined)) {
|
|
5091
|
+
throw internalError5({ message: "fromRow needs one or more table-qualified outer columns under distinct names" });
|
|
5092
|
+
}
|
|
5093
|
+
const cells = row.map((entry) => `${quoteCol(dialect, entry.column)} AS ${dialect.quoteIdent({ name: entry.alias })}`);
|
|
5094
|
+
return `(SELECT ${cells.join(", ")}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
5095
|
+
}
|
|
5096
|
+
function refuseSharedListDictionary(params) {
|
|
5097
|
+
if (params.dialect.capabilities.sharedListDictionary)
|
|
5098
|
+
return;
|
|
5099
|
+
throw unsupportedCapability23({
|
|
5100
|
+
dialect: params.dialect.name,
|
|
5101
|
+
feature: "a dictionary of shared lists",
|
|
5102
|
+
hint: "lists below a to-one under a list nest on small levels and load by key otherwise on this dialect"
|
|
5103
|
+
});
|
|
5104
|
+
}
|
|
5105
|
+
function renderCommonTables(params) {
|
|
5106
|
+
const { dialect, tables, collector } = params;
|
|
5107
|
+
refuseSharedListDictionary({ dialect });
|
|
5108
|
+
const names = tables.map((table) => table.name);
|
|
5109
|
+
if (tables.length === 0 || new Set(names).size !== names.length || tables.some((table) => table.select.with !== undefined)) {
|
|
5110
|
+
throw internalError5({ message: "common tables need distinct names and selects without common tables of their own" });
|
|
5111
|
+
}
|
|
5112
|
+
const pinned = lateralJsonOf({ dialect }).pinnedCommonTable;
|
|
5113
|
+
const rendered = tables.map((table) => {
|
|
5114
|
+
const select = renderSelect({ dialect, statement: table.select, collector }).text;
|
|
5115
|
+
if (pinned === "limit")
|
|
5116
|
+
return `${dialect.quoteIdent({ name: table.name })} AS (${select}${mysqlPinnedCommonTableSuffix({ table })})`;
|
|
5117
|
+
return `${dialect.quoteIdent({ name: table.name })} AS ${table.materialized === true ? "MATERIALIZED " : ""}(${select})`;
|
|
5118
|
+
});
|
|
5119
|
+
return `WITH ${rendered.join(", ")} `;
|
|
5120
|
+
}
|
|
5121
|
+
var JSON_KEYS_SOURCE_ALIAS = "__vibe_s";
|
|
5122
|
+
var JSON_KEYS_SET_ALIAS = "__vibe_k";
|
|
5123
|
+
function renderJsonSlot(params) {
|
|
5124
|
+
const { row, slot } = params;
|
|
5125
|
+
if (!Number.isSafeInteger(slot.index) || !Number.isSafeInteger(slot.width) || slot.index < 0 || slot.index >= slot.width) {
|
|
5126
|
+
throw internalError5({ message: "a JSON slot must be an index inside its row" });
|
|
5127
|
+
}
|
|
5128
|
+
const last = params.text ? "->>" : "->";
|
|
5129
|
+
const size = JSON_TRANSPORT.chunkSize;
|
|
5130
|
+
if (slot.width <= size)
|
|
5131
|
+
return `${row} ${last} ${slot.index}`;
|
|
5132
|
+
return `${row} -> ${Math.floor(slot.index / size)} ${last} ${slot.index % size}`;
|
|
5133
|
+
}
|
|
5134
|
+
function renderJsonKeys(params) {
|
|
5135
|
+
const { dialect, statement, keys } = params;
|
|
5136
|
+
refuseSharedListDictionary({ dialect });
|
|
5137
|
+
if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.fromUnion !== undefined || statement.fromKeys !== undefined || statement.fromRow !== undefined || statement.table.alias !== undefined || statement.table.namespace !== undefined || statement.rowLock !== undefined) {
|
|
5138
|
+
throw internalError5({ message: "fromJsonKeys uses table.name as the derived-table alias \u2014 every other FROM form, table.alias, a namespace and a row lock must be unset" });
|
|
5139
|
+
}
|
|
5140
|
+
const outputs = keys.keys.flatMap((key) => [key.token, key.alias]);
|
|
5141
|
+
if (keys.keys.length === 0 || keys.source.table === undefined || new Set(outputs).size !== outputs.length) {
|
|
5142
|
+
throw internalError5({ message: "fromJsonKeys needs a table-qualified source and one or more keys under distinct names" });
|
|
5143
|
+
}
|
|
5144
|
+
if (lateralJsonOf({ dialect }).keysFromJson === "jsonTable")
|
|
5145
|
+
return renderMysqlJsonKeys({ dialect, statement, keys });
|
|
5146
|
+
if (keyListBindsUntypedRows({ members: keys.keys.map((key) => key.member) })) {
|
|
5147
|
+
throw internalError5({ message: "fromJsonKeys cannot type a DateTime key without a declared native type" });
|
|
5148
|
+
}
|
|
5149
|
+
const source = dialect.quoteIdent({ name: JSON_KEYS_SOURCE_ALIAS });
|
|
5150
|
+
const set = dialect.quoteIdent({ name: JSON_KEYS_SET_ALIAS });
|
|
5151
|
+
const column = dialect.quoteIdent({ name: "value" });
|
|
5152
|
+
let value = `${source}.${dialect.quoteIdent({ name: keys.source.name })}`;
|
|
5153
|
+
let from = `${dialect.quoteIdent({ name: keys.source.table })} AS ${source}`;
|
|
5154
|
+
let lists = 0;
|
|
5155
|
+
for (const step of keys.steps) {
|
|
5156
|
+
if (step.kind === "slot") {
|
|
5157
|
+
value = renderJsonSlot({ row: value, slot: step.slot, text: false });
|
|
5158
|
+
continue;
|
|
5159
|
+
}
|
|
5160
|
+
const all = dialect.quoteIdent({ name: `__vibe_d${lists}` });
|
|
5161
|
+
const element = dialect.quoteIdent({ name: `__vibe_e${lists++}` });
|
|
5162
|
+
from = `json_array_elements((SELECT json_agg(${value}) FROM ${from} WHERE ${value} IS NOT NULL)) AS ${all}(${column}) ` + `CROSS JOIN LATERAL json_array_elements(${all}.${column}) AS ${element}(${column})`;
|
|
5163
|
+
value = `${element}.${column}`;
|
|
5164
|
+
}
|
|
5165
|
+
const tokens = keys.keys.map((key) => `${renderJsonSlot({ row: value, slot: key.slot, text: true })} AS ${dialect.quoteIdent({ name: key.token })}`);
|
|
5166
|
+
const typed = keys.keys.flatMap((key) => {
|
|
5167
|
+
const token = `${set}.${dialect.quoteIdent({ name: key.token })}`;
|
|
5168
|
+
return [token, `CAST(${token} AS ${postgresKeyListElementType({ dialect, member: key.member })}) AS ${dialect.quoteIdent({ name: key.alias })}`];
|
|
5169
|
+
});
|
|
5170
|
+
return `(SELECT ${typed.join(", ")} FROM (SELECT DISTINCT ${tokens.join(", ")} FROM ${from}) AS ${set}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
5171
|
+
}
|
|
5172
|
+
var TABLE_ARRAYS_ALIAS = "__vibe_a";
|
|
5173
|
+
function renderTableArrays(params) {
|
|
5174
|
+
const { dialect, statement, rows } = params;
|
|
5175
|
+
if (!dialect.capabilities.carrierRows || dialect.lateralJson?.carriedLists !== "firstRow") {
|
|
5176
|
+
throw unsupportedCapability23({
|
|
5177
|
+
dialect: dialect.name,
|
|
5178
|
+
feature: "the rows of a common table read back through arrays (fromTableArrays)",
|
|
5179
|
+
hint: "carrier rows return their lists on the row that carries, or load by key, on this dialect"
|
|
5180
|
+
});
|
|
5181
|
+
}
|
|
5182
|
+
if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.fromUnion !== undefined || statement.fromKeys !== undefined || statement.fromRow !== undefined || statement.fromJsonKeys !== undefined || statement.table.alias !== undefined || statement.table.namespace !== undefined || statement.rowLock !== undefined) {
|
|
5183
|
+
throw internalError5({ message: "fromTableArrays uses table.name as the row alias \u2014 every other FROM form, table.alias, a namespace and a row lock must be unset" });
|
|
5184
|
+
}
|
|
5185
|
+
const names = rows.columns.map((entry) => entry.alias);
|
|
5186
|
+
if (rows.columns.length === 0 || new Set(names).size !== names.length) {
|
|
5187
|
+
throw internalError5({ message: "fromTableArrays needs one or more columns under distinct names" });
|
|
5188
|
+
}
|
|
5189
|
+
const quote = (name) => dialect.quoteIdent({ name });
|
|
5190
|
+
const source = quote(rows.source);
|
|
5191
|
+
const arrays = quote(TABLE_ARRAYS_ALIAS);
|
|
5192
|
+
const order = `${source}.${quote(rows.orderBy)}`;
|
|
5193
|
+
const cells = rows.columns.map((entry, index) => `array_agg(${source}.${quote(entry.name)} ORDER BY ${order}) AS ${quote(`__vibe_a${index}`)}`);
|
|
5194
|
+
const read = rows.columns.map((_, index) => `${arrays}.${quote(`__vibe_a${index}`)}`);
|
|
5195
|
+
return `(SELECT ${cells.join(", ")} FROM ${source}) AS ${arrays} CROSS JOIN LATERAL unnest(${read.join(", ")}) AS ${quote(statement.table.name)}(${names.map(quote).join(", ")})`;
|
|
5196
|
+
}
|
|
5008
5197
|
var UNION_BRANCH_MEMBERS = new Set(["kind", "table", "columns", "where", "orderBy", "limit"]);
|
|
5009
5198
|
function renderUnionPage(params) {
|
|
5010
5199
|
const { dialect, statement, collector } = params;
|
|
5011
5200
|
const branches = statement.fromUnion ?? [];
|
|
5012
5201
|
if (branches.length < 2)
|
|
5013
|
-
throw
|
|
5202
|
+
throw internalError5({ message: "fromUnion needs at least two branches" });
|
|
5014
5203
|
if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.table.alias !== undefined) {
|
|
5015
|
-
throw
|
|
5204
|
+
throw internalError5({ message: "fromUnion uses table.name as the derived-table alias \u2014 fromSelect, keyPage and table.alias must be unset" });
|
|
5016
5205
|
}
|
|
5017
5206
|
if (statement.rowLock !== undefined)
|
|
5018
|
-
throw
|
|
5207
|
+
throw internalError5({ message: "a locking read cannot read from a union page" });
|
|
5019
5208
|
const outputNames = (branch) => branch.columns.map((column) => column.alias ?? column.name).join("\x00");
|
|
5020
5209
|
const names = outputNames(branches[0]);
|
|
5021
5210
|
return branches.map((branch, index) => {
|
|
5022
5211
|
if (branch.columns.length === 0 || Object.keys(branch).some((member) => !UNION_BRANCH_MEMBERS.has(member))) {
|
|
5023
|
-
throw
|
|
5212
|
+
throw internalError5({ message: "a fromUnion branch must be a plain SELECT <columns> FROM <table> [WHERE] [ORDER BY] [LIMIT]" });
|
|
5024
5213
|
}
|
|
5025
5214
|
if (outputNames(branch) !== names)
|
|
5026
|
-
throw
|
|
5215
|
+
throw internalError5({ message: "fromUnion branches must project the same output names in the same order" });
|
|
5027
5216
|
const inner = renderSelect({ dialect, statement: branch, collector });
|
|
5028
5217
|
return `SELECT * FROM (${inner.text}) AS ${dialect.quoteIdent({ name: `__vibe_u${index}` })}`;
|
|
5029
5218
|
}).join(" UNION ALL ");
|
|
@@ -5031,7 +5220,7 @@ function renderUnionPage(params) {
|
|
|
5031
5220
|
function jsonStringPattern(params) {
|
|
5032
5221
|
const { expr } = params;
|
|
5033
5222
|
if (typeof expr.value !== "string") {
|
|
5034
|
-
throw
|
|
5223
|
+
throw internalError5({ message: `json ${expr.op} expects a string operand` });
|
|
5035
5224
|
}
|
|
5036
5225
|
if (expr.op === "string_contains")
|
|
5037
5226
|
return containsPattern({ raw: expr.value });
|
|
@@ -5152,7 +5341,7 @@ function renderJsonFilter(params) {
|
|
|
5152
5341
|
return tests.length === 0 ? guard : `(${[guard, ...tests].join(" AND ")})`;
|
|
5153
5342
|
}
|
|
5154
5343
|
default:
|
|
5155
|
-
throw
|
|
5344
|
+
throw internalError5({ message: `json op "${expr.op}" leaked past supportedJsonOps on sqlite` });
|
|
5156
5345
|
}
|
|
5157
5346
|
}
|
|
5158
5347
|
const column = quoteCol(dialect, expr.column);
|
|
@@ -5211,7 +5400,7 @@ function renderSelectExpr(params) {
|
|
|
5211
5400
|
if (expr.kind === "one")
|
|
5212
5401
|
return "1";
|
|
5213
5402
|
if (expr.kind === "exists")
|
|
5214
|
-
throw
|
|
5403
|
+
throw internalError5({ message: "an exists projection renders as its whole statement" });
|
|
5215
5404
|
const alias = expr.alias === undefined ? "" : ` AS ${dialect.quoteIdent({ name: expr.alias })}`;
|
|
5216
5405
|
return `COUNT(*)${alias}`;
|
|
5217
5406
|
}
|
|
@@ -5221,16 +5410,16 @@ function renderPositionalRow(params) {
|
|
|
5221
5410
|
const position = join.select.rowNumber;
|
|
5222
5411
|
const positioned = position !== undefined && json.order === "positionSlot" && join.agg === "jsonArray" && position.partitionBy.length === 0 && position.orderBy.length > 0;
|
|
5223
5412
|
if (join.select.columns.length === 0 || join.select.selectExpr !== undefined || (join.select.aggregates?.length ?? 0) > 0 || position !== undefined && !positioned) {
|
|
5224
|
-
throw
|
|
5413
|
+
throw internalError5({ message: "positional lateral rows require an explicit column-only projection" });
|
|
5225
5414
|
}
|
|
5226
5415
|
const projected = join.select.columns.map((column) => projectionOutputName({ column }));
|
|
5227
5416
|
const hidden = join.hidden ?? [];
|
|
5228
5417
|
if (hidden.some((name) => !projected.includes(name)) || hidden.length >= projected.length) {
|
|
5229
|
-
throw
|
|
5418
|
+
throw internalError5({ message: "a hidden lateral column must be one of the projected columns, and one column must remain" });
|
|
5230
5419
|
}
|
|
5231
5420
|
const names = [...projected.filter((name) => !hidden.includes(name)), ...positioned ? [position.alias] : []];
|
|
5232
5421
|
if (new Set(names).size !== names.length) {
|
|
5233
|
-
throw
|
|
5422
|
+
throw internalError5({ message: "positional lateral projection has duplicate output names" });
|
|
5234
5423
|
}
|
|
5235
5424
|
const values = names.map((name) => `${subAlias}.${dialect.quoteIdent({ name })}`);
|
|
5236
5425
|
const chunks = [];
|
|
@@ -5238,30 +5427,34 @@ function renderPositionalRow(params) {
|
|
|
5238
5427
|
chunks.push(`${json.rowArray}(${values.slice(index, index + JSON_TRANSPORT.chunkSize).join(", ")})`);
|
|
5239
5428
|
}
|
|
5240
5429
|
if (chunks.length > JSON_TRANSPORT.chunkSize) {
|
|
5241
|
-
throw
|
|
5430
|
+
throw internalError5({ message: "positional lateral projection exceeds the transport width limit" });
|
|
5242
5431
|
}
|
|
5243
5432
|
return chunks.length === 1 ? chunks[0] : `${json.rowArray}(${chunks.join(", ")})`;
|
|
5244
5433
|
}
|
|
5245
5434
|
function lateralJsonOf(params) {
|
|
5246
5435
|
const json = params.dialect.lateralJson;
|
|
5247
5436
|
if (json === null)
|
|
5248
|
-
throw
|
|
5437
|
+
throw internalError5({ message: "lateral JSON rendering on a dialect without LATERAL" });
|
|
5249
5438
|
return json;
|
|
5250
5439
|
}
|
|
5251
5440
|
function renderLateralJoin(params) {
|
|
5441
|
+
const { dialect, join } = params;
|
|
5442
|
+
return `LEFT JOIN LATERAL (${renderLateralSubquery(params)}) AS ${dialect.quoteIdent({ name: join.alias })} ON true`;
|
|
5443
|
+
}
|
|
5444
|
+
function renderLateralSubquery(params) {
|
|
5252
5445
|
const { dialect, join, collector } = params;
|
|
5253
5446
|
const inner = renderSelect({ dialect, statement: join.select, collector });
|
|
5254
5447
|
if (join.agg === "count") {
|
|
5255
5448
|
if (join.rowFormat === "positional" || join.select.columns.length !== 0 || join.select.selectExpr?.kind !== "countAll" || join.select.selectExpr.alias !== join.columnAlias || join.select.groupBy !== undefined || join.select.having !== undefined || join.select.limit !== undefined || join.select.offset !== undefined || join.select.distinctOn !== undefined || join.select.distinct !== undefined) {
|
|
5256
|
-
throw
|
|
5449
|
+
throw internalError5({ message: "count lateral must return one ungrouped COUNT(*) under its column alias" });
|
|
5257
5450
|
}
|
|
5258
|
-
return
|
|
5451
|
+
return inner.text;
|
|
5259
5452
|
}
|
|
5260
5453
|
if (join.agg === "aggregate") {
|
|
5261
5454
|
if (join.rowFormat === "positional" || join.select.columns.length !== 0 || join.select.selectExpr !== undefined || (join.select.aggregates?.length ?? 0) === 0 || join.select.groupBy !== undefined || join.select.having !== undefined || join.select.groupingSets !== undefined || join.select.limit !== undefined || join.select.offset !== undefined || join.select.distinctOn !== undefined || join.select.distinct !== undefined || join.select.rowLock !== undefined) {
|
|
5262
|
-
throw
|
|
5455
|
+
throw internalError5({ message: "aggregate lateral must return one ungrouped row of aggregates" });
|
|
5263
5456
|
}
|
|
5264
|
-
return
|
|
5457
|
+
return inner.text;
|
|
5265
5458
|
}
|
|
5266
5459
|
const columnAlias = dialect.quoteIdent({ name: join.columnAlias });
|
|
5267
5460
|
const subAlias = dialect.quoteIdent({ name: "__sub" });
|
|
@@ -5290,14 +5483,30 @@ function renderLateralJoin(params) {
|
|
|
5290
5483
|
}
|
|
5291
5484
|
const rowJson = join.rowFormat === "positional" ? renderPositionalRow({ dialect, join, subAlias }) : `to_json(${subAlias}.*)`;
|
|
5292
5485
|
const projection = join.agg === "jsonArray" ? `COALESCE(${json.arrayAgg}(${rowJson}${aggOrder}), ${json.emptyArray}) AS ${columnAlias}` : `${rowJson} AS ${columnAlias}`;
|
|
5293
|
-
const
|
|
5294
|
-
|
|
5486
|
+
const exposed = join.expose ?? [];
|
|
5487
|
+
if (exposed.length > 0) {
|
|
5488
|
+
const projected = join.select.columns.map((column) => projectionOutputName({ column }));
|
|
5489
|
+
if (join.agg !== "jsonObject" || exposed.some((name) => !projected.includes(name) || name === join.columnAlias) || new Set(exposed).size !== exposed.length) {
|
|
5490
|
+
throw internalError5({ message: "a lateral exposes distinct projected columns of a one-row subselect, beside its JSON column" });
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
5493
|
+
const exposure = exposed.map((name) => `, ${subAlias}.${dialect.quoteIdent({ name })}`).join("");
|
|
5494
|
+
return `SELECT ${projection}${exposure} FROM (${inner.text}) AS ${subAlias}`;
|
|
5495
|
+
}
|
|
5496
|
+
function renderFirstRowList(params) {
|
|
5497
|
+
const { dialect, entry, collector } = params;
|
|
5498
|
+
refuseSharedListDictionary({ dialect });
|
|
5499
|
+
if (entry.list.agg !== "jsonArray" || entry.list.expose !== undefined || entry.ordinal.table === undefined) {
|
|
5500
|
+
throw internalError5({ message: "a first-row list is a JSON list gated by a table-qualified ordinal of the statement's row" });
|
|
5501
|
+
}
|
|
5502
|
+
const list = renderLateralSubquery({ dialect, join: entry.list, collector });
|
|
5503
|
+
return `CASE WHEN ${quoteCol(dialect, entry.ordinal)} = 1 THEN (${list}) END AS ${dialect.quoteIdent({ name: entry.list.columnAlias })}`;
|
|
5295
5504
|
}
|
|
5296
5505
|
function renderCountSubquery(params) {
|
|
5297
5506
|
const { dialect, subquery, collector } = params;
|
|
5298
5507
|
const select = subquery.select;
|
|
5299
5508
|
if (select.columns.length !== 0 || select.selectExpr?.kind !== "countAll" || (select.aggregates?.length ?? 0) > 0 || select.rowNumber !== undefined || select.groupBy !== undefined || select.having !== undefined || select.limit !== undefined || select.offset !== undefined || select.distinctOn !== undefined || select.distinct !== undefined || select.rowLock !== undefined || select.countSubqueries !== undefined || select.scalarSubqueries !== undefined) {
|
|
5300
|
-
throw
|
|
5509
|
+
throw internalError5({ message: "count subquery must be one ungrouped COUNT(*)" });
|
|
5301
5510
|
}
|
|
5302
5511
|
const inner = renderSelect({ dialect, statement: select, collector });
|
|
5303
5512
|
return `(${inner.text}) AS ${dialect.quoteIdent({ name: subquery.alias })}`;
|
|
@@ -5362,7 +5571,7 @@ function renderCaptureReturning(params) {
|
|
|
5362
5571
|
const alias = dialect.quoteIdent({ name: column.alias });
|
|
5363
5572
|
if (column.capture === "cascade") {
|
|
5364
5573
|
if (params.collector === undefined)
|
|
5365
|
-
throw
|
|
5574
|
+
throw internalError5({ message: `capture cascade column "${column.alias}" rendered without a parameter collector` });
|
|
5366
5575
|
const child = dialect.quoteIdent({ name: CAPTURE_CHILD_ALIAS });
|
|
5367
5576
|
const match = column.cascade.on.map((pair) => `${child}.${dialect.quoteIdent({ name: pair.child })} = ${rows.old}.${dialect.quoteIdent({ name: pair.parent })}`).join(" AND ");
|
|
5368
5577
|
return `${renderCascadeImages({ dialect, cascade: column.cascade, match, collector: params.collector })} AS ${alias}`;
|
|
@@ -5382,7 +5591,7 @@ function renderCaptureCell(params) {
|
|
|
5382
5591
|
if (cell.hop === undefined)
|
|
5383
5592
|
return textProjection({ dialect, expression: `${row}.${name}` });
|
|
5384
5593
|
if (cell.hop.on.length === 0)
|
|
5385
|
-
throw
|
|
5594
|
+
throw internalError5({ message: `capture hop to "${cell.hop.table.name}" needs at least one key column` });
|
|
5386
5595
|
const related = dialect.quoteIdent({ name: CAPTURE_HOP_ALIAS });
|
|
5387
5596
|
const match = cell.hop.on.map((pair) => `${related}.${dialect.quoteIdent({ name: pair.target })} = ${row}.${dialect.quoteIdent({ name: pair.source })}`).join(" AND ");
|
|
5388
5597
|
return `(SELECT ${textProjection({ dialect, expression: `${related}.${name}` })} FROM ${renderTable(dialect, cell.hop.table)} AS ${related} WHERE ${match})`;
|
|
@@ -5390,9 +5599,9 @@ function renderCaptureCell(params) {
|
|
|
5390
5599
|
function renderCascadeImages(params) {
|
|
5391
5600
|
const { dialect, cascade, match, collector } = params;
|
|
5392
5601
|
if (cascade.cells.length === 0 || cascade.on.length === 0)
|
|
5393
|
-
throw
|
|
5602
|
+
throw internalError5({ message: `capture cascade into "${cascade.table.name}" needs at least one cell and one key column` });
|
|
5394
5603
|
if (!Number.isSafeInteger(cascade.limit) || cascade.limit < 1)
|
|
5395
|
-
throw
|
|
5604
|
+
throw internalError5({ message: `capture cascade into "${cascade.table.name}" needs a positive integer limit` });
|
|
5396
5605
|
const child = dialect.quoteIdent({ name: CAPTURE_CHILD_ALIAS });
|
|
5397
5606
|
const item = dialect.quoteIdent({ name: "__vibe_i" });
|
|
5398
5607
|
const cells = cascade.cells.map((cell) => renderCaptureCell({ dialect, row: child, cell })).join(", ");
|
|
@@ -5425,20 +5634,20 @@ function renderKeyTableJoin(params) {
|
|
|
5425
5634
|
});
|
|
5426
5635
|
}
|
|
5427
5636
|
if (join.columns.length === 0 || join.columns.length !== join.members.length) {
|
|
5428
|
-
throw
|
|
5637
|
+
throw internalError5({ message: `key table needs one member per column, got ${join.members.length} for ${join.columns.length}` });
|
|
5429
5638
|
}
|
|
5430
5639
|
if (join.rows.length === 0)
|
|
5431
|
-
throw
|
|
5640
|
+
throw internalError5({ message: "key table needs at least one key row" });
|
|
5432
5641
|
for (const member of join.members) {
|
|
5433
5642
|
if (member.kind === "decimal" && !(Number.isInteger(member.scale) && member.scale >= 0 && member.scale <= 30)) {
|
|
5434
|
-
throw
|
|
5643
|
+
throw internalError5({ message: `key table decimal scale must be an integer 0-30, got ${String(member.scale)}` });
|
|
5435
5644
|
}
|
|
5436
5645
|
}
|
|
5437
5646
|
const alias = dialect.quoteIdent({ name: join.alias });
|
|
5438
5647
|
const memberNames = join.members.map((_, index) => dialect.quoteIdent({ name: `__vibe_k${index}` }));
|
|
5439
5648
|
const rows = join.rows.map((row, ordinal) => {
|
|
5440
5649
|
if (row.length !== join.members.length) {
|
|
5441
|
-
throw
|
|
5650
|
+
throw internalError5({ message: `key table row has ${row.length} values for ${join.members.length} members` });
|
|
5442
5651
|
}
|
|
5443
5652
|
const cells = row.map((value, index) => {
|
|
5444
5653
|
const placeholder = collector.add(value);
|
|
@@ -5468,10 +5677,10 @@ function renderKeyTableJoin(params) {
|
|
|
5468
5677
|
function renderLeftJoin(params) {
|
|
5469
5678
|
const { dialect, join, collector } = params;
|
|
5470
5679
|
if (join.on.length === 0) {
|
|
5471
|
-
throw
|
|
5680
|
+
throw internalError5({ message: "left join needs at least one ON pair" });
|
|
5472
5681
|
}
|
|
5473
5682
|
if (join.select.columns.length === 0 || join.select.columns.some((column) => column.alias === undefined)) {
|
|
5474
|
-
throw
|
|
5683
|
+
throw internalError5({ message: "a left join's derived select must alias every projected column" });
|
|
5475
5684
|
}
|
|
5476
5685
|
const inner = renderSelect({ dialect, statement: join.select, collector });
|
|
5477
5686
|
const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
|
|
@@ -5481,19 +5690,28 @@ function renderExistsSelect(params) {
|
|
|
5481
5690
|
const { dialect, statement, alias, collector } = params;
|
|
5482
5691
|
const extra = statement.columns.length > 0 || (statement.aggregates?.length ?? 0) > 0 || statement.countSubqueries !== undefined || statement.scalarSubqueries !== undefined || statement.rowNumber !== undefined || statement.groupBy !== undefined || statement.groupingSets !== undefined || statement.having !== undefined || statement.orderBy !== undefined || statement.limit !== undefined || statement.offset !== undefined || statement.rowLock !== undefined || statement.distinct !== undefined || statement.distinctOn !== undefined || statement.lateralJoins !== undefined || statement.innerJoins !== undefined || statement.leftJoins !== undefined || statement.keyTable !== undefined;
|
|
5483
5692
|
if (extra)
|
|
5484
|
-
throw
|
|
5693
|
+
throw internalError5({ message: "an exists select takes only a FROM and a WHERE" });
|
|
5485
5694
|
const inner = renderSelect({ dialect, statement: { ...statement, selectExpr: { kind: "one" } }, collector });
|
|
5486
5695
|
return { ...inner, text: `SELECT EXISTS (${inner.text}) AS ${dialect.quoteIdent({ name: alias })}` };
|
|
5487
5696
|
}
|
|
5488
5697
|
function renderSelect(params) {
|
|
5489
5698
|
const { dialect, statement, collector } = params;
|
|
5699
|
+
if (statement.with !== undefined) {
|
|
5700
|
+
if (params.outermost !== true)
|
|
5701
|
+
throw internalError5({ message: "common tables render on the outermost select only" });
|
|
5702
|
+
const { with: tables, ...main } = statement;
|
|
5703
|
+
const prefix = renderCommonTables({ dialect, tables, collector });
|
|
5704
|
+
const names = new Set(tables.map((table) => table.name));
|
|
5705
|
+
const rendered = renderSelect({ dialect, statement: main, collector, ...main.allColumns === true && names.has(main.table.name) ? { overCommonTable: true } : {} });
|
|
5706
|
+
return { ...rendered, text: `${prefix}${rendered.text}` };
|
|
5707
|
+
}
|
|
5490
5708
|
if (statement.selectExpr?.kind === "exists")
|
|
5491
5709
|
return renderExistsSelect({ dialect, statement, alias: statement.selectExpr.alias, collector });
|
|
5492
5710
|
if (statement.selectExpr !== undefined && statement.columns.length > 0 && statement.selectExpr.kind !== "countAll") {
|
|
5493
|
-
throw
|
|
5711
|
+
throw internalError5({ message: "only countAll can combine with a column list" });
|
|
5494
5712
|
}
|
|
5495
5713
|
if (statement.selectExpr !== undefined && statement.aggregates !== undefined && statement.aggregates.length > 0) {
|
|
5496
|
-
throw
|
|
5714
|
+
throw internalError5({ message: "selectExpr and aggregates cannot combine" });
|
|
5497
5715
|
}
|
|
5498
5716
|
const columnList = statement.columns.map((column) => quoteProjection(dialect, column));
|
|
5499
5717
|
if (statement.selectExpr !== undefined) {
|
|
@@ -5523,15 +5741,45 @@ function renderSelect(params) {
|
|
|
5523
5741
|
if (statement.rowNumber !== undefined) {
|
|
5524
5742
|
const rowNumber = statement.rowNumber;
|
|
5525
5743
|
if (rowNumber.partitionBy.length === 0 && rowNumber.orderBy.length === 0) {
|
|
5526
|
-
throw
|
|
5744
|
+
throw internalError5({ message: "rowNumber needs a partition column or an order" });
|
|
5527
5745
|
}
|
|
5528
5746
|
const partition = rowNumber.partitionBy.length === 0 ? "" : `PARTITION BY ${rowNumber.partitionBy.map((column) => quoteCol(dialect, column)).join(", ")}`;
|
|
5529
5747
|
const order = rowNumber.orderBy.length === 0 ? "" : `${partition === "" ? "" : " "}ORDER BY ${rowNumber.orderBy.map((term) => renderOrderTerm({ dialect, term, collector })).join(", ")}`;
|
|
5530
5748
|
columnList.push(`ROW_NUMBER() OVER (${partition}${order}) AS ${dialect.quoteIdent({ name: rowNumber.alias })}`);
|
|
5531
5749
|
}
|
|
5750
|
+
if (statement.windows !== undefined) {
|
|
5751
|
+
for (const window of statement.windows) {
|
|
5752
|
+
const alias = dialect.quoteIdent({ name: window.alias });
|
|
5753
|
+
if (window.kind === "ordinal") {
|
|
5754
|
+
const order = window.orderBy.length === 0 ? "" : `ORDER BY ${window.orderBy.map((term) => renderOrderTerm({ dialect, term, collector })).join(", ")}`;
|
|
5755
|
+
columnList.push(`ROW_NUMBER() OVER (${order}) AS ${alias}`);
|
|
5756
|
+
continue;
|
|
5757
|
+
}
|
|
5758
|
+
if (window.partitionBy.length === 0)
|
|
5759
|
+
throw internalError5({ message: "a firstOrdinal window needs a partition column" });
|
|
5760
|
+
columnList.push(`MIN(${quoteCol(dialect, window.ordinal)}) OVER (PARTITION BY ${window.partitionBy.map((column) => quoteCol(dialect, column)).join(", ")}) AS ${alias}`);
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
if (statement.firstRowLists !== undefined) {
|
|
5764
|
+
for (const entry of statement.firstRowLists)
|
|
5765
|
+
columnList.push(renderFirstRowList({ dialect, entry, collector }));
|
|
5766
|
+
}
|
|
5767
|
+
if (statement.allColumns === true) {
|
|
5768
|
+
if (params.overCommonTable === true) {
|
|
5769
|
+
if (statement.fromSelect !== undefined || statement.selectExpr !== undefined || statement.table.namespace !== undefined) {
|
|
5770
|
+
throw internalError5({ message: "allColumns over a common table reads that table alone" });
|
|
5771
|
+
}
|
|
5772
|
+
columnList.unshift(`${dialect.quoteIdent({ name: statement.table.alias ?? statement.table.name })}.*`);
|
|
5773
|
+
} else {
|
|
5774
|
+
if (statement.fromSelect === undefined || statement.columns.length > 0 || statement.selectExpr !== undefined) {
|
|
5775
|
+
throw internalError5({ message: "allColumns re-exposes a derived table: it needs fromSelect and no column list" });
|
|
5776
|
+
}
|
|
5777
|
+
columnList.unshift(`${dialect.quoteIdent({ name: statement.table.name })}.*`);
|
|
5778
|
+
}
|
|
5779
|
+
}
|
|
5532
5780
|
let distinctPrefix = statement.distinct === true ? "DISTINCT " : "";
|
|
5533
5781
|
if (statement.distinct === true && statement.distinctOn !== undefined) {
|
|
5534
|
-
throw
|
|
5782
|
+
throw internalError5({ message: "distinct and distinctOn cannot combine" });
|
|
5535
5783
|
}
|
|
5536
5784
|
if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
|
|
5537
5785
|
if (!dialect.capabilities.distinctOn) {
|
|
@@ -5547,16 +5795,22 @@ function renderSelect(params) {
|
|
|
5547
5795
|
let from;
|
|
5548
5796
|
if (statement.fromKeys !== undefined) {
|
|
5549
5797
|
from = renderKeyRows({ dialect, statement, keys: statement.fromKeys, collector });
|
|
5798
|
+
} else if (statement.fromRow !== undefined) {
|
|
5799
|
+
from = renderOuterRow({ dialect, statement, row: statement.fromRow });
|
|
5800
|
+
} else if (statement.fromJsonKeys !== undefined) {
|
|
5801
|
+
from = renderJsonKeys({ dialect, statement, keys: statement.fromJsonKeys });
|
|
5802
|
+
} else if (statement.fromTableArrays !== undefined) {
|
|
5803
|
+
from = renderTableArrays({ dialect, statement, rows: statement.fromTableArrays });
|
|
5550
5804
|
} else if (statement.fromUnion !== undefined) {
|
|
5551
5805
|
from = `(${renderUnionPage({ dialect, statement, collector })}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
5552
5806
|
} else if (statement.keyPage !== undefined) {
|
|
5553
5807
|
if (statement.fromSelect !== undefined || statement.table.alias !== undefined) {
|
|
5554
|
-
throw
|
|
5808
|
+
throw internalError5({ message: "keyPage uses table.name as the derived-table alias \u2014 fromSelect and table.alias must be unset" });
|
|
5555
5809
|
}
|
|
5556
5810
|
from = `(${renderKeyPage({ dialect, page: statement.keyPage, collector })}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
5557
5811
|
} else if (statement.fromSelect !== undefined) {
|
|
5558
5812
|
if (statement.table.alias !== undefined) {
|
|
5559
|
-
throw
|
|
5813
|
+
throw internalError5({ message: "fromSelect uses table.name as the subquery alias \u2014 table.alias must be unset" });
|
|
5560
5814
|
}
|
|
5561
5815
|
const inner = renderSelect({ dialect, statement: statement.fromSelect, collector });
|
|
5562
5816
|
from = `(${inner.text}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
@@ -5567,7 +5821,7 @@ function renderSelect(params) {
|
|
|
5567
5821
|
if (statement.innerJoins !== undefined && statement.innerJoins.length > 0) {
|
|
5568
5822
|
for (const join of statement.innerJoins) {
|
|
5569
5823
|
if (join.on.length === 0) {
|
|
5570
|
-
throw
|
|
5824
|
+
throw internalError5({ message: "inner join needs at least one ON pair" });
|
|
5571
5825
|
}
|
|
5572
5826
|
const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
|
|
5573
5827
|
text += ` INNER JOIN ${renderTable(dialect, join.table)} ON ${on}`;
|
|
@@ -5578,7 +5832,7 @@ function renderSelect(params) {
|
|
|
5578
5832
|
}
|
|
5579
5833
|
if (statement.leftJoins !== undefined && statement.leftJoins.length > 0) {
|
|
5580
5834
|
if (statement.rowLock !== undefined) {
|
|
5581
|
-
throw
|
|
5835
|
+
throw internalError5({ message: "a locking read cannot carry a left join \u2014 postgres refuses FOR UPDATE on the nullable side of an outer join" });
|
|
5582
5836
|
}
|
|
5583
5837
|
for (const join of statement.leftJoins) {
|
|
5584
5838
|
text += ` ${renderLeftJoin({ dialect, join, collector })}`;
|
|
@@ -5602,7 +5856,7 @@ function renderSelect(params) {
|
|
|
5602
5856
|
if (statement.groupBy !== undefined && statement.groupBy.length > 0) {
|
|
5603
5857
|
text += ` GROUP BY ${statement.groupingSets === undefined ? groupKeys.join(", ") : renderGroupingSets({ dialect, keys: groupKeys, grouping: statement.groupingSets })}`;
|
|
5604
5858
|
} else if (statement.groupingSets !== undefined) {
|
|
5605
|
-
throw
|
|
5859
|
+
throw internalError5({ message: "grouping sets need groupBy columns" });
|
|
5606
5860
|
}
|
|
5607
5861
|
if (statement.having !== undefined) {
|
|
5608
5862
|
text += ` HAVING ${renderExpr({ dialect, expr: statement.having, collector })}`;
|
|
@@ -5635,10 +5889,10 @@ function renderSelect(params) {
|
|
|
5635
5889
|
if (statement.rowLock !== undefined) {
|
|
5636
5890
|
const target = statement.table.alias ?? statement.table.name;
|
|
5637
5891
|
if (statement.rowLock.of !== undefined && statement.rowLock.of.some((name) => name !== target)) {
|
|
5638
|
-
throw
|
|
5892
|
+
throw internalError5({ message: `a row lock's \`of\` list may name only the statement's own table "${target}"` });
|
|
5639
5893
|
}
|
|
5640
5894
|
if (statement.rowLock.of !== undefined && (statement.fromSelect !== undefined || statement.keyPage !== undefined)) {
|
|
5641
|
-
throw
|
|
5895
|
+
throw internalError5({ message: "a row lock's `of` list cannot name a derived table" });
|
|
5642
5896
|
}
|
|
5643
5897
|
const clause = renderRowLockClause({ dialect, lock: statement.rowLock });
|
|
5644
5898
|
if (clause !== null)
|
|
@@ -5654,7 +5908,7 @@ function unnestElement(params) {
|
|
|
5654
5908
|
return null;
|
|
5655
5909
|
if (isJsonParamCell(value)) {
|
|
5656
5910
|
if (jsonStorage === undefined || (value.storage ?? "jsonb") !== jsonStorage) {
|
|
5657
|
-
throw
|
|
5911
|
+
throw internalError5({ message: `unnest insert column "${column}" got a Json cell its declared type does not take` });
|
|
5658
5912
|
}
|
|
5659
5913
|
return value.text;
|
|
5660
5914
|
}
|
|
@@ -5670,7 +5924,7 @@ function unnestElement(params) {
|
|
|
5670
5924
|
return value;
|
|
5671
5925
|
}
|
|
5672
5926
|
}
|
|
5673
|
-
throw
|
|
5927
|
+
throw internalError5({ message: `unnest insert column "${column}" got a cell that is not a plain value (DEFAULT, the database clock, a document) \u2014 such rows keep VALUES` });
|
|
5674
5928
|
}
|
|
5675
5929
|
function renderUnnestRows(params) {
|
|
5676
5930
|
const { dialect, statement, members, collector } = params;
|
|
@@ -5682,12 +5936,12 @@ function renderUnnestRows(params) {
|
|
|
5682
5936
|
});
|
|
5683
5937
|
}
|
|
5684
5938
|
if (statement.columns.length === 0)
|
|
5685
|
-
throw
|
|
5939
|
+
throw internalError5({ message: "an unnest insert needs at least one column" });
|
|
5686
5940
|
if (members.length !== statement.columns.length) {
|
|
5687
|
-
throw
|
|
5941
|
+
throw internalError5({ message: `unnest insert has ${members.length} column types for ${statement.columns.length} columns` });
|
|
5688
5942
|
}
|
|
5689
5943
|
if (statement.onConflict !== undefined && !("doNothing" in statement.onConflict)) {
|
|
5690
|
-
throw
|
|
5944
|
+
throw internalError5({ message: "an unnest insert takes no DO UPDATE arm \u2014 upserts bind VALUES" });
|
|
5691
5945
|
}
|
|
5692
5946
|
const aliases = statement.columns.map((_column, index) => dialect.quoteIdent({ name: `c${index + 1}` }));
|
|
5693
5947
|
const arrays = members.map((member, index) => {
|
|
@@ -5703,11 +5957,11 @@ function renderUnnestRows(params) {
|
|
|
5703
5957
|
function renderInsert(params) {
|
|
5704
5958
|
const { dialect, statement, collector } = params;
|
|
5705
5959
|
if (statement.rows.length === 0) {
|
|
5706
|
-
throw
|
|
5960
|
+
throw internalError5({ message: "insert requires at least one row" });
|
|
5707
5961
|
}
|
|
5708
5962
|
const defaultValues = statement.columns.length === 0 && dialect.emptyInsert.form === "defaultValues";
|
|
5709
5963
|
if (defaultValues && statement.rows.length !== 1) {
|
|
5710
|
-
throw
|
|
5964
|
+
throw internalError5({ message: `${dialect.name} inserts an all-default row as DEFAULT VALUES, one row per statement` });
|
|
5711
5965
|
}
|
|
5712
5966
|
if (defaultValues && statement.onConflict !== undefined && !(dialect.emptyInsert.form === "defaultValues" && dialect.emptyInsert.conflictClause)) {
|
|
5713
5967
|
throw unsupportedCapability23({
|
|
@@ -5718,7 +5972,7 @@ function renderInsert(params) {
|
|
|
5718
5972
|
}
|
|
5719
5973
|
for (const row of statement.rows) {
|
|
5720
5974
|
if (row.length !== statement.columns.length) {
|
|
5721
|
-
throw
|
|
5975
|
+
throw internalError5({
|
|
5722
5976
|
message: `insert row has ${row.length} values but ${statement.columns.length} columns`
|
|
5723
5977
|
});
|
|
5724
5978
|
}
|
|
@@ -5736,7 +5990,7 @@ function renderInsert(params) {
|
|
|
5736
5990
|
if (!isSqlDefaultCell(value))
|
|
5737
5991
|
return collector.add(value);
|
|
5738
5992
|
if (!dialect.rowDefaultKeyword) {
|
|
5739
|
-
throw
|
|
5993
|
+
throw internalError5({
|
|
5740
5994
|
message: `${dialect.name} cannot render a per-row DEFAULT cell \u2014 rows must be grouped by column set`
|
|
5741
5995
|
});
|
|
5742
5996
|
}
|
|
@@ -5776,20 +6030,20 @@ function renderInsert(params) {
|
|
|
5776
6030
|
}
|
|
5777
6031
|
const keepName = onConflict.keepColumn ?? statement.columns[0];
|
|
5778
6032
|
if (keepName === undefined) {
|
|
5779
|
-
throw
|
|
6033
|
+
throw internalError5({ message: "a skip-duplicates insert of an all-default row must name its keepColumn" });
|
|
5780
6034
|
}
|
|
5781
6035
|
const keep = dialect.quoteIdent({ name: keepName });
|
|
5782
6036
|
const current = `${dialect.quoteIdent({ name: statement.table.name })}.${keep}`;
|
|
5783
6037
|
const flag = onConflict.duplicateFlag;
|
|
5784
6038
|
if (flag !== undefined && !/^\d{1,20}$/.test(flag)) {
|
|
5785
|
-
throw
|
|
6039
|
+
throw internalError5({ message: `duplicateFlag must be a decimal literal, got ${JSON.stringify(flag)}` });
|
|
5786
6040
|
}
|
|
5787
6041
|
text += flag === undefined ? ` ON DUPLICATE KEY UPDATE ${keep} = ${current}` : ` ON DUPLICATE KEY UPDATE ${keep} = IF(LAST_INSERT_ID(${flag}), ${current}, ${current})`;
|
|
5788
6042
|
}
|
|
5789
6043
|
} else {
|
|
5790
6044
|
const updates = Object.entries(onConflict.update);
|
|
5791
6045
|
if (updates.length === 0) {
|
|
5792
|
-
throw
|
|
6046
|
+
throw internalError5({ message: "upsert update must set at least one column" });
|
|
5793
6047
|
}
|
|
5794
6048
|
const selfRef = dialect.quoteIdent({ name: statement.table.name });
|
|
5795
6049
|
if (onConflict.skipUnchanged !== undefined && !dialect.capabilities.upsertSkipUnchanged) {
|
|
@@ -5801,7 +6055,7 @@ function renderInsert(params) {
|
|
|
5801
6055
|
const assignments = dialect.upsertForm !== "onConflict" && onConflict.skipUnchanged !== undefined ? renderStampFirstAssignments({ dialect, columns: onConflict.skipUnchanged, update: onConflict.update, collector, selfRef, tableName: statement.table.name }) : renderAssignments({ dialect, entries: updates, collector, selfRef, allowExcluded: true, tableName: statement.table.name });
|
|
5802
6056
|
if (dialect.upsertForm === "onConflict") {
|
|
5803
6057
|
if (onConflict.lastInsertIdColumn !== undefined) {
|
|
5804
|
-
throw
|
|
6058
|
+
throw internalError5({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
|
|
5805
6059
|
}
|
|
5806
6060
|
if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
|
|
5807
6061
|
throw unsupportedCapability23({
|
|
@@ -5811,7 +6065,7 @@ function renderInsert(params) {
|
|
|
5811
6065
|
});
|
|
5812
6066
|
}
|
|
5813
6067
|
if (onConflict.constraint !== undefined && onConflict.conflictWhere !== undefined) {
|
|
5814
|
-
throw
|
|
6068
|
+
throw internalError5({
|
|
5815
6069
|
message: "a named-constraint conflict target already identifies the index \u2014 it cannot carry a partial predicate too"
|
|
5816
6070
|
});
|
|
5817
6071
|
}
|
|
@@ -5856,7 +6110,7 @@ function renderInsert(params) {
|
|
|
5856
6110
|
const trick = onConflict.lastInsertIdColumn === undefined ? "" : `, ${dialect.quoteIdent({ name: onConflict.lastInsertIdColumn })} = LAST_INSERT_ID(${dialect.quoteIdent({ name: onConflict.lastInsertIdColumn })})`;
|
|
5857
6111
|
const flagged = onConflict.duplicateFlag;
|
|
5858
6112
|
if (flagged !== undefined && (onConflict.lastInsertIdColumn !== undefined || !/^\d{1,20}$/.test(flagged.flag))) {
|
|
5859
|
-
throw
|
|
6113
|
+
throw internalError5({ message: "an update arm's duplicate flag must be a decimal literal, and cannot share LAST_INSERT_ID with lastInsertIdColumn" });
|
|
5860
6114
|
}
|
|
5861
6115
|
const flag = flagged === undefined ? "" : `, ${dialect.quoteIdent({ name: flagged.column })} = IF(LAST_INSERT_ID(${flagged.flag}), ${selfRef}.${dialect.quoteIdent({ name: flagged.column })}, ${selfRef}.${dialect.quoteIdent({ name: flagged.column })})`;
|
|
5862
6116
|
text += ` ON DUPLICATE KEY UPDATE ${assignments}${trick}${flag}`;
|
|
@@ -5870,12 +6124,12 @@ function renderUpdate(params) {
|
|
|
5870
6124
|
const { dialect, statement, collector } = params;
|
|
5871
6125
|
const assignments = Object.entries(statement.set);
|
|
5872
6126
|
if (assignments.length === 0) {
|
|
5873
|
-
throw
|
|
6127
|
+
throw internalError5({ message: "update must set at least one column" });
|
|
5874
6128
|
}
|
|
5875
6129
|
const source = statement.from;
|
|
5876
6130
|
if (source !== undefined) {
|
|
5877
6131
|
if (statement.alias === undefined || statement.alias === source.alias)
|
|
5878
|
-
throw
|
|
6132
|
+
throw internalError5({ message: "UPDATE \u2026 FROM needs distinct target and source aliases" });
|
|
5879
6133
|
const target = dialect.quoteIdent({ name: statement.alias });
|
|
5880
6134
|
const joined = dialect.quoteIdent({ name: source.alias });
|
|
5881
6135
|
const assign = () => renderAssignments({ dialect, entries: assignments, collector, selfRef: target, source: joined, ...dialect.name === "mysql" ? { qualifyWrite: target } : {}, ...params.earlier === undefined ? {} : { steps: params.earlier } });
|
|
@@ -5915,12 +6169,12 @@ function renderAssignments(params) {
|
|
|
5915
6169
|
function renderUnchangedGuard(params) {
|
|
5916
6170
|
const { dialect, columns, update, collector, selfRef, tableName } = params;
|
|
5917
6171
|
if (columns.length === 0)
|
|
5918
|
-
throw
|
|
6172
|
+
throw internalError5({ message: "a skip-unchanged guard needs at least one compared column" });
|
|
5919
6173
|
const left = [];
|
|
5920
6174
|
const right = [];
|
|
5921
6175
|
for (const entry of columns) {
|
|
5922
6176
|
if (!Object.hasOwn(update, entry.column)) {
|
|
5923
|
-
throw
|
|
6177
|
+
throw internalError5({ message: `the skip-unchanged column ${entry.column} has no assignment in the update arm` });
|
|
5924
6178
|
}
|
|
5925
6179
|
const assigned = update[entry.column];
|
|
5926
6180
|
const value = renderAssignmentValue({ dialect, collector, selfRef, allowExcluded: true, tableName, name: entry.column, value: assigned });
|
|
@@ -5966,23 +6220,23 @@ function renderAssignmentValue(params) {
|
|
|
5966
6220
|
const read = selfRef === undefined ? column : `${selfRef}.${column}`;
|
|
5967
6221
|
if (isSourceColumnRef(value)) {
|
|
5968
6222
|
if (params.source === undefined)
|
|
5969
|
-
throw
|
|
6223
|
+
throw internalError5({ message: `an UPDATE set value reads the joined source's column ${value.column}, but the statement joins no source` });
|
|
5970
6224
|
return `${params.source}.${dialect.quoteIdent({ name: value.column })}`;
|
|
5971
6225
|
}
|
|
5972
6226
|
if (isStepColumnRef(value)) {
|
|
5973
6227
|
if (params.steps === undefined || !params.steps.has(value.step)) {
|
|
5974
|
-
throw
|
|
6228
|
+
throw internalError5({ message: `an UPDATE set value reads step "${value.step}", which is not an earlier chain step` });
|
|
5975
6229
|
}
|
|
5976
6230
|
return `(SELECT ${dialect.quoteIdent({ name: value.column })} FROM ${dialect.quoteIdent({ name: value.step })})`;
|
|
5977
6231
|
}
|
|
5978
6232
|
if (isSqlFragment(value)) {
|
|
5979
6233
|
if (params.allowExcluded !== true || selfRef === undefined)
|
|
5980
|
-
throw
|
|
6234
|
+
throw internalError5({ message: "SQL assignment fragments require a conflict update" });
|
|
5981
6235
|
return renderSqlFragment({ dialect, fragment: value, collector, conflict: { table: params.tableName } });
|
|
5982
6236
|
}
|
|
5983
6237
|
if (isExcludedRef(value)) {
|
|
5984
6238
|
if (params.allowExcluded !== true) {
|
|
5985
|
-
throw
|
|
6239
|
+
throw internalError5({
|
|
5986
6240
|
message: `an incoming-row reference (${value.column}) is only meaningful inside an upsert's update arm`
|
|
5987
6241
|
});
|
|
5988
6242
|
}
|
|
@@ -5990,7 +6244,7 @@ function renderAssignmentValue(params) {
|
|
|
5990
6244
|
}
|
|
5991
6245
|
if (isSqlKeepCell(value)) {
|
|
5992
6246
|
if (params.allowExcluded !== true || selfRef === undefined)
|
|
5993
|
-
throw
|
|
6247
|
+
throw internalError5({ message: "a keep-column cell is only meaningful inside an upsert's update arm" });
|
|
5994
6248
|
return read;
|
|
5995
6249
|
}
|
|
5996
6250
|
if (isJsonPatchOperation(value)) {
|
|
@@ -6037,12 +6291,12 @@ function renderChain(params) {
|
|
|
6037
6291
|
});
|
|
6038
6292
|
}
|
|
6039
6293
|
if (statement.steps.length === 0)
|
|
6040
|
-
throw
|
|
6294
|
+
throw internalError5({ message: "a statement chain needs at least one step" });
|
|
6041
6295
|
const defined = new Set;
|
|
6042
6296
|
const steps = [];
|
|
6043
6297
|
for (const step of statement.steps) {
|
|
6044
6298
|
if (step.name === CHAIN_ROWS_ALIAS || step.name === CHAIN_ONE_ALIAS || defined.has(step.name)) {
|
|
6045
|
-
throw
|
|
6299
|
+
throw internalError5({ message: `chain step name "${step.name}" is reserved or repeated` });
|
|
6046
6300
|
}
|
|
6047
6301
|
const body = renderChainStep({ dialect, statement: step.statement, collector, earlier: defined });
|
|
6048
6302
|
steps.push(`${dialect.quoteIdent({ name: step.name })} AS (${body})`);
|
|
@@ -6055,7 +6309,7 @@ function renderChain(params) {
|
|
|
6055
6309
|
}
|
|
6056
6310
|
for (const alias of captureAliases({ captures })) {
|
|
6057
6311
|
if (result.columns.includes(alias) || alias === result.verdict?.alias || alias === result.tally?.alias) {
|
|
6058
|
-
throw
|
|
6312
|
+
throw internalError5({ message: `chain capture alias "${alias}" collides with a result column` });
|
|
6059
6313
|
}
|
|
6060
6314
|
}
|
|
6061
6315
|
if (result.rowsFrom.length === 0 && result.columns.length === 0 && result.tally !== undefined && result.verdict === undefined) {
|
|
@@ -6063,11 +6317,11 @@ function renderChain(params) {
|
|
|
6063
6317
|
return { text: `WITH ${steps.join(", ")} SELECT ${[tally, ...renderChainCaptures({ dialect, captures, defined, collector })].join(", ")}`, values: collector.values, returning: "none" };
|
|
6064
6318
|
}
|
|
6065
6319
|
if (result.rowsFrom.length === 0 || result.columns.length === 0) {
|
|
6066
|
-
throw
|
|
6320
|
+
throw internalError5({ message: "a chain result needs at least one source step and one column" });
|
|
6067
6321
|
}
|
|
6068
6322
|
for (const name of result.rowsFrom) {
|
|
6069
6323
|
if (!defined.has(name))
|
|
6070
|
-
throw
|
|
6324
|
+
throw internalError5({ message: `chain result reads undefined step "${name}"` });
|
|
6071
6325
|
}
|
|
6072
6326
|
const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
6073
6327
|
const guarded = result.verdict !== undefined || result.tally !== undefined || result.veto !== undefined && result.veto.length > 0;
|
|
@@ -6078,7 +6332,7 @@ function renderChain(params) {
|
|
|
6078
6332
|
const extra = [];
|
|
6079
6333
|
for (const alias of [result.verdict?.alias, result.tally?.alias]) {
|
|
6080
6334
|
if (alias !== undefined && result.columns.includes(alias)) {
|
|
6081
|
-
throw
|
|
6335
|
+
throw internalError5({ message: `chain result alias "${alias}" collides with a result column` });
|
|
6082
6336
|
}
|
|
6083
6337
|
}
|
|
6084
6338
|
const rows = dialect.quoteIdent({ name: CHAIN_ROWS_ALIAS });
|
|
@@ -6134,7 +6388,7 @@ function renderChainCaptures(params) {
|
|
|
6134
6388
|
const out = [];
|
|
6135
6389
|
for (const entry of captures) {
|
|
6136
6390
|
if (!defined.has(entry.step))
|
|
6137
|
-
throw
|
|
6391
|
+
throw internalError5({ message: `chain capture reads undefined step "${entry.step}"` });
|
|
6138
6392
|
const step = quote(entry.step);
|
|
6139
6393
|
const marker = entry.inserted === undefined ? undefined : quote(entry.inserted);
|
|
6140
6394
|
out.push(`(SELECT count(*) FROM ${step}) AS ${quote(`${entry.alias}_n`)}`);
|
|
@@ -6159,7 +6413,7 @@ function renderImageAggregates(params) {
|
|
|
6159
6413
|
if (before.length === 0 && after.length === 0)
|
|
6160
6414
|
return out;
|
|
6161
6415
|
if (!Number.isSafeInteger(entry.limit) || entry.limit < 1)
|
|
6162
|
-
throw
|
|
6416
|
+
throw internalError5({ message: `chain capture "${entry.alias}" needs a positive integer limit` });
|
|
6163
6417
|
const limit = collector.add(entry.limit);
|
|
6164
6418
|
const item = quote("__vibe_i");
|
|
6165
6419
|
const image = (columns, filter) => `(SELECT array_agg(${item})::text FROM (SELECT DISTINCT ARRAY[${columns.map(quote).join(", ")}] AS ${item} FROM ${step}${filter} LIMIT ${limit}) AS ${quote("__vibe_d")})`;
|
|
@@ -6179,21 +6433,21 @@ function renderSelectCell(params) {
|
|
|
6179
6433
|
if (isDbNowMarker(value))
|
|
6180
6434
|
return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
|
|
6181
6435
|
if (isSqlDefaultCell(value)) {
|
|
6182
|
-
throw
|
|
6436
|
+
throw internalError5({ message: "DEFAULT is not allowed in an INSERT \u2026 SELECT list \u2014 omit the column instead" });
|
|
6183
6437
|
}
|
|
6184
6438
|
return collector.add(value);
|
|
6185
6439
|
}
|
|
6186
6440
|
function renderRowSource(params) {
|
|
6187
6441
|
const { dialect, source, collector } = params;
|
|
6188
6442
|
if (source.columns.length === 0 || source.rows.length === 0) {
|
|
6189
|
-
throw
|
|
6443
|
+
throw internalError5({ message: "a row source needs at least one column and one row" });
|
|
6190
6444
|
}
|
|
6191
6445
|
const typedBy = renderTable(dialect, source.typedBy);
|
|
6192
6446
|
const columns = source.columns.map((name) => dialect.quoteIdent({ name }));
|
|
6193
6447
|
const rows = [`(${columns.map((column) => `(SELECT ${column} FROM ${typedBy} WHERE FALSE)`).join(", ")})`];
|
|
6194
6448
|
for (const row of source.rows) {
|
|
6195
6449
|
if (row.length !== source.columns.length) {
|
|
6196
|
-
throw
|
|
6450
|
+
throw internalError5({ message: `row source row has ${row.length} values but ${source.columns.length} columns` });
|
|
6197
6451
|
}
|
|
6198
6452
|
rows.push(`(${row.map((value) => renderSelectCell({ dialect, value, collector })).join(", ")})`);
|
|
6199
6453
|
}
|
|
@@ -6205,12 +6459,12 @@ function renderTallyColumn(params) {
|
|
|
6205
6459
|
const { dialect, result, defined, collector } = params;
|
|
6206
6460
|
const tally = result.tally;
|
|
6207
6461
|
if (tally === undefined)
|
|
6208
|
-
throw
|
|
6462
|
+
throw internalError5({ message: "a chain veto needs a tally column to guard" });
|
|
6209
6463
|
if (tally.steps.length === 0)
|
|
6210
|
-
throw
|
|
6464
|
+
throw internalError5({ message: "a chain tally needs at least one step" });
|
|
6211
6465
|
const step = (name) => {
|
|
6212
6466
|
if (!defined.has(name))
|
|
6213
|
-
throw
|
|
6467
|
+
throw internalError5({ message: `chain tally or veto reads undefined step "${name}"` });
|
|
6214
6468
|
return dialect.quoteIdent({ name });
|
|
6215
6469
|
};
|
|
6216
6470
|
const count = (name) => `(SELECT count(*) FROM ${step(name)})`;
|
|
@@ -6218,20 +6472,20 @@ function renderTallyColumn(params) {
|
|
|
6218
6472
|
const vetoes = result.veto ?? [];
|
|
6219
6473
|
if (vetoes.length === 0) {
|
|
6220
6474
|
if (result.vetoGate !== undefined)
|
|
6221
|
-
throw
|
|
6475
|
+
throw internalError5({ message: "a chain veto gate needs a veto to gate" });
|
|
6222
6476
|
return `${sum} AS ${dialect.quoteIdent({ name: tally.alias })}`;
|
|
6223
6477
|
}
|
|
6224
6478
|
const gate = result.vetoGate === undefined ? [] : [`WHEN NOT EXISTS (SELECT 1 FROM ${step(result.vetoGate)}) THEN ${sum}`];
|
|
6225
6479
|
const arms = vetoes.map((veto) => {
|
|
6226
6480
|
if (!CHAIN_VETO_TOKEN_PATTERN.test(veto.token))
|
|
6227
|
-
throw
|
|
6481
|
+
throw internalError5({ message: `chain veto token "${veto.token}" is not a lower-case word` });
|
|
6228
6482
|
if (typeof veto.nonce !== "string" || veto.nonce === "")
|
|
6229
|
-
throw
|
|
6483
|
+
throw internalError5({ message: `chain veto "${veto.token}" needs a non-empty nonce` });
|
|
6230
6484
|
const minRows = veto.minRows ?? 1;
|
|
6231
6485
|
if (!Number.isSafeInteger(minRows) || minRows < 1)
|
|
6232
|
-
throw
|
|
6486
|
+
throw internalError5({ message: `chain veto "${veto.token}" needs a positive integer minRows` });
|
|
6233
6487
|
if (veto.exactRows !== undefined && (!Number.isSafeInteger(veto.exactRows) || veto.exactRows < 0 || veto.minRows !== undefined)) {
|
|
6234
|
-
throw
|
|
6488
|
+
throw internalError5({ message: `chain veto "${veto.token}" needs a nonnegative safe integer exactRows without minRows` });
|
|
6235
6489
|
}
|
|
6236
6490
|
const when = veto.exactRows !== undefined ? `WHEN ${count(veto.step)} <> ${veto.exactRows}` : minRows === 1 ? `WHEN NOT EXISTS (SELECT 1 FROM ${step(veto.step)})` : `WHEN ${count(veto.step)} < ${minRows}`;
|
|
6237
6491
|
return `${when} THEN CAST('${CHAIN_VETO_PREFIX}${veto.token}:' || ${collector.add(veto.nonce)} || ':' || ${count(veto.step)} AS integer)`;
|
|
@@ -6241,25 +6495,25 @@ function renderTallyColumn(params) {
|
|
|
6241
6495
|
function renderInsertSelect(params) {
|
|
6242
6496
|
const { dialect, statement, collector } = params;
|
|
6243
6497
|
if (statement.columns.length === 0)
|
|
6244
|
-
throw
|
|
6498
|
+
throw internalError5({ message: "insert requires at least one column" });
|
|
6245
6499
|
if (statement.cells.length !== statement.columns.length) {
|
|
6246
|
-
throw
|
|
6500
|
+
throw internalError5({
|
|
6247
6501
|
message: `insert row has ${statement.cells.length} values but ${statement.columns.length} columns`
|
|
6248
6502
|
});
|
|
6249
6503
|
}
|
|
6250
6504
|
const from = statement.from ?? [];
|
|
6251
6505
|
for (const name of from) {
|
|
6252
6506
|
if (!params.earlier.has(name))
|
|
6253
|
-
throw
|
|
6507
|
+
throw internalError5({ message: `INSERT \u2026 SELECT reads step "${name}" before it is defined` });
|
|
6254
6508
|
}
|
|
6255
6509
|
const source = statement.source;
|
|
6256
6510
|
if (source !== undefined && (params.earlier.has(source.alias) || from.includes(source.alias))) {
|
|
6257
|
-
throw
|
|
6511
|
+
throw internalError5({ message: `row source alias "${source.alias}" collides with a chain step` });
|
|
6258
6512
|
}
|
|
6259
6513
|
const cells = statement.cells.map((value) => {
|
|
6260
6514
|
if (isStepColumnRef(value)) {
|
|
6261
6515
|
if (!from.includes(value.step) && value.step !== source?.alias) {
|
|
6262
|
-
throw
|
|
6516
|
+
throw internalError5({ message: `cell reads step "${value.step}", which the INSERT \u2026 SELECT does not name in FROM` });
|
|
6263
6517
|
}
|
|
6264
6518
|
return `${dialect.quoteIdent({ name: value.step })}.${dialect.quoteIdent({ name: value.column })}`;
|
|
6265
6519
|
}
|
|
@@ -6278,7 +6532,7 @@ function renderInsertSelect(params) {
|
|
|
6278
6532
|
const conflict = statement.conflictDoNothing;
|
|
6279
6533
|
if (conflict !== undefined) {
|
|
6280
6534
|
if (conflict.columns.length === 0)
|
|
6281
|
-
throw
|
|
6535
|
+
throw internalError5({ message: "an INSERT \u2026 SELECT conflict clause needs at least one column" });
|
|
6282
6536
|
if (dialect.doNothingForm !== "onConflictDoNothing") {
|
|
6283
6537
|
throw unsupportedCapability23({
|
|
6284
6538
|
dialect: dialect.name,
|
|
@@ -6538,7 +6792,7 @@ var postgresDialect = {
|
|
|
6538
6792
|
keyListStrategy: "unnest",
|
|
6539
6793
|
keysetSeekStrategy: "rowValue",
|
|
6540
6794
|
keyPageStrategy: "lateral",
|
|
6541
|
-
lateralJson: { rowArray: "json_build_array", arrayAgg: "json_agg", emptyArray: "'[]'::json", order: "inAggregate", timeOfDayAsText: false, requiresUtcDateTimeWire: false },
|
|
6795
|
+
lateralJson: { rowArray: "json_build_array", arrayAgg: "json_agg", emptyArray: "'[]'::json", order: "inAggregate", timeOfDayAsText: false, requiresUtcDateTimeWire: false, keysFromJson: "elements", pinnedCommonTable: "keyword", carriedLists: "firstRow", silentSizeLimit: null },
|
|
6542
6796
|
relationStrategyDefault: "join",
|
|
6543
6797
|
relationFilterStrategy: "eqSelect",
|
|
6544
6798
|
deferredJoinPage: { minSkip: 1000, requiresCoveringIndex: true },
|
|
@@ -6636,7 +6890,7 @@ var mysqlDialect = {
|
|
|
6636
6890
|
keyListStrategy: "rowValue",
|
|
6637
6891
|
keysetSeekStrategy: "or",
|
|
6638
6892
|
keyPageStrategy: "window",
|
|
6639
|
-
lateralJson: { rowArray: "JSON_ARRAY", arrayAgg: "JSON_ARRAYAGG", emptyArray: "JSON_ARRAY()", order: "positionSlot", timeOfDayAsText: true, requiresUtcDateTimeWire: true },
|
|
6893
|
+
lateralJson: { rowArray: "JSON_ARRAY", arrayAgg: "JSON_ARRAYAGG", emptyArray: "JSON_ARRAY()", order: "positionSlot", timeOfDayAsText: true, requiresUtcDateTimeWire: true, keysFromJson: "jsonTable", pinnedCommonTable: "limit", carriedLists: "carrierRow", silentSizeLimit: "max_allowed_packet" },
|
|
6640
6894
|
relationStrategyDefault: "join",
|
|
6641
6895
|
relationFilterStrategy: "exists",
|
|
6642
6896
|
deferredJoinPage: { minSkip: 1000, requiresCoveringIndex: false },
|
|
@@ -6903,7 +7157,7 @@ function renderGeneratedColumnClause({ dialect, generated }) {
|
|
|
6903
7157
|
}
|
|
6904
7158
|
}
|
|
6905
7159
|
// packages/sql/src/row-rules.ts
|
|
6906
|
-
import { VibeError as VibeError25, internalError as
|
|
7160
|
+
import { VibeError as VibeError25, internalError as internalError6, normalizeRowRules, rowRuleGateField, rowRuleIdentities, rowRuleProtectedFields, rowRuleShapeIssue, rowRuleTriggerName, unsupportedCapability as unsupportedCapability31 } from "@vibeorm/schema";
|
|
6907
7161
|
|
|
6908
7162
|
// packages/sql/src/dialects/postgres-row-rules.ts
|
|
6909
7163
|
import { VibeError as VibeError22, unsupportedCapability as unsupportedCapability28 } from "@vibeorm/schema";
|
|
@@ -7073,7 +7327,7 @@ function renderRowRule({ schema, model, rule, dialect, placement }) {
|
|
|
7073
7327
|
const functionIdentity = objects.find((object) => object.role === "function")?.identity;
|
|
7074
7328
|
const triggerIdentity = objects.find((object) => object.role === "trigger")?.identity;
|
|
7075
7329
|
if (functionIdentity?.kind !== "function" || triggerIdentity?.kind !== "trigger")
|
|
7076
|
-
throw
|
|
7330
|
+
throw internalError6({ message: "PostgreSQL row rules own one function and one trigger", meta: { model: model.name, rule: normalized.name } });
|
|
7077
7331
|
const gate = rowRuleGateField({ rule: normalized });
|
|
7078
7332
|
const gateField = gate === undefined ? undefined : model.fields.find((field) => field.name === gate);
|
|
7079
7333
|
return renderPostgresRowRule({
|
|
@@ -7094,15 +7348,15 @@ function renderRowRule({ schema, model, rule, dialect, placement }) {
|
|
|
7094
7348
|
const objects = rowRuleIdentities({ schema, model, rule: normalized, dialect: dialect.name });
|
|
7095
7349
|
const triggers = objects.map(({ role, identity }) => {
|
|
7096
7350
|
if (role !== "trigger" || identity.kind !== "trigger")
|
|
7097
|
-
throw
|
|
7351
|
+
throw internalError6({ message: "Event row rules own only triggers", meta: { model: model.name, rule: normalized.name } });
|
|
7098
7352
|
const event = ["update", "delete"].find((event) => identity.name === rowRuleTriggerName({ rule: normalized, event }));
|
|
7099
7353
|
if (event === undefined)
|
|
7100
|
-
throw
|
|
7354
|
+
throw internalError6({ message: "Row-rule identity has no declared event", meta: { model: model.name, rule: normalized.name, trigger: identity.name } });
|
|
7101
7355
|
return { event, identity: { kind: "trigger", table: identity.table, name: identity.name } };
|
|
7102
7356
|
});
|
|
7103
7357
|
const table = triggers[0]?.identity.table;
|
|
7104
7358
|
if (table === undefined)
|
|
7105
|
-
throw
|
|
7359
|
+
throw internalError6({ message: "Event row rules require at least one trigger", meta: { model: model.name, rule: normalized.name } });
|
|
7106
7360
|
const gate = rowRuleGateField({ rule: normalized });
|
|
7107
7361
|
const gateField = gate === undefined ? undefined : model.fields.find((field) => field.name === gate);
|
|
7108
7362
|
const context = {
|
|
@@ -7355,7 +7609,7 @@ function renderMigrationVisibility(params) {
|
|
|
7355
7609
|
return { text: `SELECT PRIVILEGE_TYPE AS privilege FROM information_schema.USER_PRIVILEGES WHERE GRANTEE = ${grantee} UNION SELECT PRIVILEGE_TYPE AS privilege FROM information_schema.SCHEMA_PRIVILEGES WHERE GRANTEE = ${grantee} AND ${collector.add(params.namespace)} LIKE TABLE_SCHEMA${MYSQL_NO_ROW_LIMIT}`, values: collector.values, returning: "none" };
|
|
7356
7610
|
}
|
|
7357
7611
|
// packages/sql/src/dialects/migration-objects.ts
|
|
7358
|
-
import { internalError as
|
|
7612
|
+
import { internalError as internalError7, unsupportedCapability as unsupportedCapability38 } from "@vibeorm/schema";
|
|
7359
7613
|
function renderMigrationObjectQuery(params) {
|
|
7360
7614
|
return objectQuery({ ...params, collector: new ParamCollector(params.dialect) });
|
|
7361
7615
|
}
|
|
@@ -7607,7 +7861,7 @@ function renderMigrationObjectBatch(params) {
|
|
|
7607
7861
|
const facts = objectQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
|
|
7608
7862
|
const dependencies = dependencyQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
|
|
7609
7863
|
if (JSON.stringify(dependencies.values) !== JSON.stringify(facts.values))
|
|
7610
|
-
throw
|
|
7864
|
+
throw internalError7({ message: "Batched catalog queries bind their identity in different orders", meta: { kind: identity.kind } });
|
|
7611
7865
|
const columns = facts.values.map((value, index) => `${dialect.quoteIdent({ name: `p${index + 1}` })} ${Array.isArray(value) ? "text[]" : "text"}`);
|
|
7612
7866
|
const requests = group.map(({ identity, position }) => ({ position, ...Object.fromEntries(renderMigrationObjectQuery({ dialect, identity }).values.map((value, index) => [`p${index + 1}`, value])) }));
|
|
7613
7867
|
const collector = new ParamCollector(dialect);
|
|
@@ -7849,6 +8103,8 @@ export {
|
|
|
7849
8103
|
isSqlQuery,
|
|
7850
8104
|
isStepColumnRef,
|
|
7851
8105
|
isTimeZoneName,
|
|
8106
|
+
jsonKeyTokenCast,
|
|
8107
|
+
jsonKeysTyped,
|
|
7852
8108
|
jsonListParameter,
|
|
7853
8109
|
jsonParamCell,
|
|
7854
8110
|
jsonPatchVetoOf,
|
|
@@ -7857,6 +8113,7 @@ export {
|
|
|
7857
8113
|
mapConflictColumns,
|
|
7858
8114
|
mysqlBoundTimestampText,
|
|
7859
8115
|
mysqlDialect,
|
|
8116
|
+
mysqlJsonKeyTypeOf,
|
|
7860
8117
|
nativeEnumArrayParameter,
|
|
7861
8118
|
normalizeMysqlNativeType,
|
|
7862
8119
|
normalizePostgresNativeType,
|
|
@@ -7995,4 +8252,4 @@ export {
|
|
|
7995
8252
|
zonedCodec
|
|
7996
8253
|
};
|
|
7997
8254
|
|
|
7998
|
-
//# debugId=
|
|
8255
|
+
//# debugId=07BC696B142BA5A864756E2164756E21
|