@sequenceholdings/orm 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/authoring.cjs +53 -7
- package/dist/authoring.cjs.map +1 -1
- package/dist/authoring.d.cts +10 -6
- package/dist/authoring.d.ts +10 -6
- package/dist/authoring.js +53 -7
- package/dist/authoring.js.map +1 -1
- package/dist/canonical.cjs +48 -2
- package/dist/canonical.cjs.map +1 -1
- package/dist/canonical.d.cts +1 -1
- package/dist/canonical.d.ts +1 -1
- package/dist/canonical.js +48 -2
- package/dist/canonical.js.map +1 -1
- package/dist/cli.cjs +260 -31
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +260 -31
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs +11 -6
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +38 -31
- package/dist/client.d.ts +38 -31
- package/dist/client.js +11 -7
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +513 -23
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +39 -3
- package/dist/compiler.d.ts +39 -3
- package/dist/compiler.js +510 -24
- package/dist/compiler.js.map +1 -1
- package/dist/{constants-C6EG6Wq7.d.cts → constants-Dou1RkTo.d.cts} +7 -2
- package/dist/{constants-C6EG6Wq7.d.ts → constants-Dou1RkTo.d.ts} +7 -2
- package/dist/dynamic-input.d.cts +1 -1
- package/dist/dynamic-input.d.ts +1 -1
- package/dist/index.cjs +461 -140
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -4
- package/dist/index.d.ts +34 -4
- package/dist/index.js +460 -141
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +55 -4
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +1 -1
- package/dist/ir.d.ts +1 -1
- package/dist/ir.js +55 -4
- package/dist/ir.js.map +1 -1
- package/dist/predicate-v2.cjs +55 -4
- package/dist/predicate-v2.cjs.map +1 -1
- package/dist/predicate-v2.d.cts +1 -1
- package/dist/predicate-v2.d.ts +1 -1
- package/dist/predicate-v2.js +55 -4
- package/dist/predicate-v2.js.map +1 -1
- package/dist/sdl.cjs +60 -4
- package/dist/sdl.cjs.map +1 -1
- package/dist/sdl.d.cts +3 -1
- package/dist/sdl.d.ts +3 -1
- package/dist/sdl.js +60 -4
- package/dist/sdl.js.map +1 -1
- package/package.json +1 -1
package/dist/compiler.cjs
CHANGED
|
@@ -8,12 +8,17 @@ var pgParser = require('@supabase/pg-parser');
|
|
|
8
8
|
// src/compiler-gql/artifact.ts
|
|
9
9
|
|
|
10
10
|
// src/constants.ts
|
|
11
|
+
var NAMESPACE_PATTERN = /^(?!(?:events|native_sources)$)[a-z][a-z0-9_]{0,40}$/;
|
|
11
12
|
var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
|
|
12
13
|
var MAX_IDENTIFIER_LENGTH = 63;
|
|
13
14
|
var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
|
|
15
|
+
var MAX_PERSISTED_READ_ROWS = 50001;
|
|
14
16
|
var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
|
|
15
17
|
var ORM_SCHEMA_PREFIX = "orm_";
|
|
16
|
-
var ormSchemaName = (namespace) =>
|
|
18
|
+
var ormSchemaName = (namespace) => {
|
|
19
|
+
if (!NAMESPACE_PATTERN.test(namespace)) throw new Error(`invalid or reserved ORM namespace '${namespace}'`);
|
|
20
|
+
return `${ORM_SCHEMA_PREFIX}${namespace}`;
|
|
21
|
+
};
|
|
17
22
|
var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
|
|
18
23
|
var CELL_PART_SUFFIX = {
|
|
19
24
|
at: "__at",
|
|
@@ -389,6 +394,7 @@ function validateResolvedView({ view, tables, views = [] }) {
|
|
|
389
394
|
function validateSource({ scope, source, table }) {
|
|
390
395
|
const { fail: fail4 } = scope;
|
|
391
396
|
if (source.kind === "etl" && table.source === void 0) fail4(`etl source '${source.name}' must reference a synced table`);
|
|
397
|
+
if (source.kind === "etl" && table.primaryKey?.length === 0) fail4(`etl source '${source.name}' cannot use an unkeyed synced table for resolved row identity`);
|
|
392
398
|
if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
|
|
393
399
|
fail4(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
|
|
394
400
|
}
|
|
@@ -620,6 +626,163 @@ function isSingleViewQuerySql(value) {
|
|
|
620
626
|
}
|
|
621
627
|
return state === "normal" && !/\$[a-z_0-9]*\$/i.test(query);
|
|
622
628
|
}
|
|
629
|
+
var VIEW_QUERY_FUNCTION_ALLOWLIST = [
|
|
630
|
+
"abs",
|
|
631
|
+
"age",
|
|
632
|
+
"array_agg",
|
|
633
|
+
"array_append",
|
|
634
|
+
"array_cat",
|
|
635
|
+
"array_dims",
|
|
636
|
+
"array_length",
|
|
637
|
+
"array_lower",
|
|
638
|
+
"array_ndims",
|
|
639
|
+
"array_position",
|
|
640
|
+
"array_positions",
|
|
641
|
+
"array_prepend",
|
|
642
|
+
"array_remove",
|
|
643
|
+
"array_replace",
|
|
644
|
+
"array_to_string",
|
|
645
|
+
"array_trim",
|
|
646
|
+
"array_upper",
|
|
647
|
+
"ascii",
|
|
648
|
+
"avg",
|
|
649
|
+
"bit_and",
|
|
650
|
+
"bit_or",
|
|
651
|
+
"bit_xor",
|
|
652
|
+
"bool_and",
|
|
653
|
+
"bool_or",
|
|
654
|
+
"btrim",
|
|
655
|
+
"cardinality",
|
|
656
|
+
"cbrt",
|
|
657
|
+
"ceil",
|
|
658
|
+
"ceiling",
|
|
659
|
+
"char_length",
|
|
660
|
+
"character_length",
|
|
661
|
+
"chr",
|
|
662
|
+
"concat",
|
|
663
|
+
"concat_ws",
|
|
664
|
+
"count",
|
|
665
|
+
"cume_dist",
|
|
666
|
+
"date_bin",
|
|
667
|
+
"date_part",
|
|
668
|
+
"date_trunc",
|
|
669
|
+
"decode",
|
|
670
|
+
"degrees",
|
|
671
|
+
"dense_rank",
|
|
672
|
+
"encode",
|
|
673
|
+
"enum_first",
|
|
674
|
+
"enum_last",
|
|
675
|
+
"enum_range",
|
|
676
|
+
"every",
|
|
677
|
+
"exp",
|
|
678
|
+
"first_value",
|
|
679
|
+
"floor",
|
|
680
|
+
"format",
|
|
681
|
+
"grouping",
|
|
682
|
+
"initcap",
|
|
683
|
+
"json_agg",
|
|
684
|
+
"json_array_length",
|
|
685
|
+
"json_build_array",
|
|
686
|
+
"json_build_object",
|
|
687
|
+
"json_extract_path",
|
|
688
|
+
"json_extract_path_text",
|
|
689
|
+
"json_object_agg",
|
|
690
|
+
"json_object_keys",
|
|
691
|
+
"json_strip_nulls",
|
|
692
|
+
"json_typeof",
|
|
693
|
+
"jsonb_agg",
|
|
694
|
+
"jsonb_array_length",
|
|
695
|
+
"jsonb_build_array",
|
|
696
|
+
"jsonb_build_object",
|
|
697
|
+
"jsonb_extract_path",
|
|
698
|
+
"jsonb_extract_path_text",
|
|
699
|
+
"jsonb_insert",
|
|
700
|
+
"jsonb_object_agg",
|
|
701
|
+
"jsonb_object_keys",
|
|
702
|
+
"jsonb_path_exists",
|
|
703
|
+
"jsonb_path_match",
|
|
704
|
+
"jsonb_path_query",
|
|
705
|
+
"jsonb_path_query_array",
|
|
706
|
+
"jsonb_path_query_first",
|
|
707
|
+
"jsonb_pretty",
|
|
708
|
+
"jsonb_set",
|
|
709
|
+
"jsonb_set_lax",
|
|
710
|
+
"jsonb_strip_nulls",
|
|
711
|
+
"jsonb_typeof",
|
|
712
|
+
"justify_days",
|
|
713
|
+
"justify_hours",
|
|
714
|
+
"justify_interval",
|
|
715
|
+
"lag",
|
|
716
|
+
"last_value",
|
|
717
|
+
"lead",
|
|
718
|
+
"left",
|
|
719
|
+
"length",
|
|
720
|
+
"ln",
|
|
721
|
+
"log",
|
|
722
|
+
"lower",
|
|
723
|
+
"lpad",
|
|
724
|
+
"ltrim",
|
|
725
|
+
"make_date",
|
|
726
|
+
"make_interval",
|
|
727
|
+
"make_time",
|
|
728
|
+
"make_timestamp",
|
|
729
|
+
"make_timestamptz",
|
|
730
|
+
"max",
|
|
731
|
+
"md5",
|
|
732
|
+
"min",
|
|
733
|
+
"mod",
|
|
734
|
+
"nth_value",
|
|
735
|
+
"ntile",
|
|
736
|
+
"octet_length",
|
|
737
|
+
"percent_rank",
|
|
738
|
+
"pi",
|
|
739
|
+
"power",
|
|
740
|
+
"quote_ident",
|
|
741
|
+
"quote_literal",
|
|
742
|
+
"quote_nullable",
|
|
743
|
+
"radians",
|
|
744
|
+
"rank",
|
|
745
|
+
"regexp_count",
|
|
746
|
+
"regexp_instr",
|
|
747
|
+
"regexp_like",
|
|
748
|
+
"regexp_match",
|
|
749
|
+
"regexp_matches",
|
|
750
|
+
"regexp_replace",
|
|
751
|
+
"regexp_split_to_array",
|
|
752
|
+
"regexp_split_to_table",
|
|
753
|
+
"repeat",
|
|
754
|
+
"replace",
|
|
755
|
+
"reverse",
|
|
756
|
+
"right",
|
|
757
|
+
"round",
|
|
758
|
+
"row_number",
|
|
759
|
+
"rpad",
|
|
760
|
+
"rtrim",
|
|
761
|
+
"sign",
|
|
762
|
+
"split_part",
|
|
763
|
+
"sqrt",
|
|
764
|
+
"starts_with",
|
|
765
|
+
"string_agg",
|
|
766
|
+
"string_to_array",
|
|
767
|
+
"strpos",
|
|
768
|
+
"substr",
|
|
769
|
+
"substring",
|
|
770
|
+
"sum",
|
|
771
|
+
"timezone",
|
|
772
|
+
"to_ascii",
|
|
773
|
+
"to_char",
|
|
774
|
+
"to_date",
|
|
775
|
+
"to_hex",
|
|
776
|
+
"to_json",
|
|
777
|
+
"to_jsonb",
|
|
778
|
+
"to_number",
|
|
779
|
+
"to_timestamp",
|
|
780
|
+
"translate",
|
|
781
|
+
"trunc",
|
|
782
|
+
"unnest",
|
|
783
|
+
"upper",
|
|
784
|
+
"width_bucket"
|
|
785
|
+
];
|
|
623
786
|
var canonicalActionSchema = zod.z.object({
|
|
624
787
|
name: identifier2,
|
|
625
788
|
requires: zod.z.string().max(1e3).optional(),
|
|
@@ -680,7 +843,7 @@ var canonicalTableSchema = zod.z.object({
|
|
|
680
843
|
/** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
|
|
681
844
|
source: zod.z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
|
|
682
845
|
columns: zod.z.array(canonicalColumnSchema).default([]),
|
|
683
|
-
primaryKey: zod.z.array(columnIdentifier).
|
|
846
|
+
primaryKey: zod.z.array(columnIdentifier).max(8).optional(),
|
|
684
847
|
links: zod.z.array(canonicalLinkSchema).default([]),
|
|
685
848
|
indexes: zod.z.array(canonicalIndexSchema).default([]),
|
|
686
849
|
/** Omitted by old definitions, preserving their existing content hashes. */
|
|
@@ -716,6 +879,29 @@ var canonicalTableSchema = zod.z.object({
|
|
|
716
879
|
* data plane. Omitted otherwise.
|
|
717
880
|
*/
|
|
718
881
|
materializes: identifier2.optional()
|
|
882
|
+
}).superRefine((table, ctx) => {
|
|
883
|
+
if (table.primaryKey?.length !== 0) return;
|
|
884
|
+
const fail4 = (message, path = ["primaryKey"]) => ctx.addIssue({ code: "custom", path, message: `unkeyed table '${table.table}' ${message}` });
|
|
885
|
+
if (table.source === void 0 || table.columns.length === 0) {
|
|
886
|
+
fail4("requires a typed synced source");
|
|
887
|
+
}
|
|
888
|
+
if ([table.policies.write, table.policies.insert, table.policies.update].some((policy) => policy !== void 0)) {
|
|
889
|
+
fail4("is read-only and cannot declare write policies", ["policies"]);
|
|
890
|
+
}
|
|
891
|
+
const ownedCollections = [table.indexes, table.actions, table.checks, table.foreignKeys, table.cowMutations];
|
|
892
|
+
const ownedMetadata = [table.cellMetadata, table.internal, table.materializes, table.renamedFrom];
|
|
893
|
+
if (ownedCollections.some((items) => items !== void 0 && items.length > 0) || ownedMetadata.some((value) => value !== void 0)) {
|
|
894
|
+
fail4("cannot declare owned storage, constraints or identity operations");
|
|
895
|
+
}
|
|
896
|
+
for (const [index, column] of table.columns.entries()) {
|
|
897
|
+
const ownedColumnMetadata = [column.default, column.defaultFn, column.onUpdateFn, column.renamedFrom, column.cast, column.cell];
|
|
898
|
+
if (column.unique || ownedColumnMetadata.some((value) => value !== void 0)) {
|
|
899
|
+
fail4("cannot declare column uniqueness, mutation defaults or migration metadata", ["columns", index]);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
for (const [index, link] of table.links.entries()) {
|
|
903
|
+
if (link.enforce || link.kind === "toMany") fail4("cannot provide an identity for this link", ["links", index]);
|
|
904
|
+
}
|
|
719
905
|
});
|
|
720
906
|
var MAX_VIEW_QUERY = 1e5;
|
|
721
907
|
var MAX_RESOLVED_VIEW_QUERY = 5e6;
|
|
@@ -772,6 +958,26 @@ var canonicalViewSchema = zod.z.object({
|
|
|
772
958
|
}
|
|
773
959
|
}
|
|
774
960
|
});
|
|
961
|
+
function validateUnkeyedLinkIdentities({ definition, ctx }) {
|
|
962
|
+
const tables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
963
|
+
const relations = [
|
|
964
|
+
...definition.tables.map((table, index) => ({ kind: "tables", index, links: table.links })),
|
|
965
|
+
...(definition.views ?? []).map((view, index) => ({ kind: "views", index, links: view.links }))
|
|
966
|
+
];
|
|
967
|
+
for (const relation of relations) {
|
|
968
|
+
for (const [linkIndex, link] of relation.links.entries()) {
|
|
969
|
+
if (link.kind !== "toOne" || link.namespace !== void 0 && link.namespace !== definition.namespace) continue;
|
|
970
|
+
const target = link.target === void 0 ? void 0 : tables.get(link.target);
|
|
971
|
+
if (target?.source !== void 0 && target.primaryKey?.length === 0) {
|
|
972
|
+
ctx.addIssue({
|
|
973
|
+
code: "custom",
|
|
974
|
+
path: [relation.kind, relation.index, "links", linkIndex],
|
|
975
|
+
message: `link '${link.name}' cannot use unkeyed table '${target.table}' as a to-one identity`
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
775
981
|
var CELL_PART_TYPES = {
|
|
776
982
|
at: "timestamptz",
|
|
777
983
|
by: "text",
|
|
@@ -835,7 +1041,7 @@ var exportGateSchema = zod.z.union([zod.z.literal(true), zod.z.object({ capabili
|
|
|
835
1041
|
var relationExportSchema = zod.z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
|
|
836
1042
|
var localDefinitionSchema = zod.z.object({
|
|
837
1043
|
definition_format: zod.z.literal(CANONICAL_FORMAT),
|
|
838
|
-
namespace: identifier2.max(41),
|
|
1044
|
+
namespace: identifier2.max(41).regex(NAMESPACE_PATTERN, "invalid or reserved ORM namespace"),
|
|
839
1045
|
description: zod.z.string().max(2e3).optional(),
|
|
840
1046
|
enums: zod.z.array(canonicalEnumSchema).default([]),
|
|
841
1047
|
tables: zod.z.array(canonicalTableSchema).min(0).max(500),
|
|
@@ -869,6 +1075,7 @@ var localDefinitionSchema = zod.z.object({
|
|
|
869
1075
|
}
|
|
870
1076
|
}
|
|
871
1077
|
const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
1078
|
+
validateUnkeyedLinkIdentities({ definition, ctx });
|
|
872
1079
|
const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
|
|
873
1080
|
for (const name2 of Object.keys(definition.exports ?? {})) {
|
|
874
1081
|
if (!relations.has(name2)) ctx.addIssue({ code: "custom", path: ["exports", name2], message: `export '${name2}' is not a local object` });
|
|
@@ -1103,6 +1310,9 @@ function normalizeCanonical(definition) {
|
|
|
1103
1310
|
return normalized;
|
|
1104
1311
|
}
|
|
1105
1312
|
|
|
1313
|
+
// src/client/sql.ts
|
|
1314
|
+
var BUILTIN_SQL_READ_HASH = "sha256:a50c477c952a5e0fe68ed158a4530e4aab573c09bcbb1a8ac454dc88a39a6258";
|
|
1315
|
+
|
|
1106
1316
|
// src/ir/index.ts
|
|
1107
1317
|
var IR_VERSION = 5;
|
|
1108
1318
|
var LEGACY_IR_VERSION = 1;
|
|
@@ -1191,7 +1401,7 @@ var limitIrSchema = zod.z.object({
|
|
|
1191
1401
|
variable: zod.z.string().optional(),
|
|
1192
1402
|
value: zod.z.number().int().positive().optional(),
|
|
1193
1403
|
default: zod.z.number().int().positive(),
|
|
1194
|
-
max: zod.z.number().int().positive()
|
|
1404
|
+
max: zod.z.number().int().positive().max(MAX_PERSISTED_READ_ROWS)
|
|
1195
1405
|
});
|
|
1196
1406
|
var selectionIrSchema = zod.z.lazy(
|
|
1197
1407
|
() => zod.z.object({
|
|
@@ -1436,7 +1646,8 @@ var compiledOperationSchema = zod.z.object({
|
|
|
1436
1646
|
}).superRefine((operation, ctx) => {
|
|
1437
1647
|
if (operation.language === "sql") {
|
|
1438
1648
|
const validPlan = operation.sqlQuery ? !operation.sql && operation.irVersion === RUNTIME_SQL_IR_VERSION : !!operation.sql && operation.irVersion === SQL_IR_VERSION;
|
|
1439
|
-
|
|
1649
|
+
const builtin = operation.name === "BuiltinSqlRead" && operation.opHash === BUILTIN_SQL_READ_HASH && operation.requires === null;
|
|
1650
|
+
if (!validPlan || operation.kind !== "query" || operation.roots.length !== 0 || !builtin && !operation.requires?.capability) {
|
|
1440
1651
|
ctx.addIssue({ code: "custom", message: "SQL operations require a query-only v4 plan or v5 runtime contract, capability, and no GraphQL roots" });
|
|
1441
1652
|
}
|
|
1442
1653
|
} else if (operation.sql || operation.sqlQuery || operation.roots.length === 0) {
|
|
@@ -2828,6 +3039,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
2828
3039
|
const platformMeta = usesPlatformUsers ? platformUserMeta() : void 0;
|
|
2829
3040
|
if (platformMeta) tables[PLATFORM_USER_TABLE] = platformMeta;
|
|
2830
3041
|
const syncedTableNames = new Set(canonical.tables.filter((t) => t.source !== void 0).map((t) => t.table));
|
|
3042
|
+
const unkeyedSyncedTableNames = new Set(relations.filter((table) => table.source !== void 0 && table.primaryKey?.length === 0).map((table) => table.table));
|
|
2831
3043
|
const viewDependencies = new Map((canonical.views ?? []).map((view) => [view.view, view.dependencies]));
|
|
2832
3044
|
const claimsGatedMemo = /* @__PURE__ */ new Map();
|
|
2833
3045
|
const relationIsClaimsGated = (name2) => {
|
|
@@ -2841,6 +3053,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
2841
3053
|
};
|
|
2842
3054
|
for (const table of relations) {
|
|
2843
3055
|
const isView = viewNames.has(table.table);
|
|
3056
|
+
const readOnly = isView || unkeyedSyncedTableNames.has(table.table);
|
|
2844
3057
|
const owner = owners.get(table.table);
|
|
2845
3058
|
const entries = resolveColumnEntries(table, enums, diagnostics);
|
|
2846
3059
|
columnEntries.set(table.table, entries);
|
|
@@ -2888,13 +3101,13 @@ function buildCompileContext(rawCanonical) {
|
|
|
2888
3101
|
// are claims-gated — see tableMetaIrSchema.claimsGated.
|
|
2889
3102
|
claimsGated: relationIsClaimsGated(table.table),
|
|
2890
3103
|
columns: entries.map(({ deprecated: _deprecated, onUpdateFn: _onUpdateFn, ...column }) => column),
|
|
2891
|
-
primaryKey: isView ? primaryKey : primaryKey.length > 0 ? primaryKey : ["id"],
|
|
3104
|
+
primaryKey: isView || unkeyedSyncedTableNames.has(table.table) ? primaryKey : primaryKey.length > 0 ? primaryKey : ["id"],
|
|
2892
3105
|
links: [],
|
|
2893
3106
|
filterable: data.map((c) => c.name),
|
|
2894
3107
|
orderable: data.filter((c) => c.type !== "jsonb").map((c) => c.name),
|
|
2895
|
-
insertable: resolved ? resolvedWritable(data) :
|
|
2896
|
-
settable: resolved ? resolvedWritable(data).filter((name2) => !primaryKey.includes(name2)) :
|
|
2897
|
-
appendable:
|
|
3108
|
+
insertable: resolved ? resolvedWritable(data) : readOnly ? [] : active.map((c) => c.name),
|
|
3109
|
+
settable: resolved ? resolvedWritable(data).filter((name2) => !primaryKey.includes(name2)) : readOnly ? [] : active.filter((c) => !c.onUpdateFn).map((c) => c.name),
|
|
3110
|
+
appendable: readOnly ? [] : active.filter((c) => c.type === "jsonb").map((c) => c.name),
|
|
2898
3111
|
...touchOnUpdate.length > 0 ? { touchOnUpdate } : {},
|
|
2899
3112
|
...cells ? { cells } : {},
|
|
2900
3113
|
...resolved ? { resolved: resolvedMeta({ resolved, pk: primaryKey[0] }) } : {},
|
|
@@ -2918,6 +3131,16 @@ function buildCompileContext(rawCanonical) {
|
|
|
2918
3131
|
});
|
|
2919
3132
|
continue;
|
|
2920
3133
|
}
|
|
3134
|
+
const identityTable = link.kind === "toOne" ? targetName : link.kind === "toMany" ? table.table : void 0;
|
|
3135
|
+
if (identityTable !== void 0 && unkeyedSyncedTableNames.has(identityTable)) {
|
|
3136
|
+
diagnostics.push({
|
|
3137
|
+
severity: "error",
|
|
3138
|
+
code: "unkeyed-link-identity",
|
|
3139
|
+
table: table.table,
|
|
3140
|
+
message: `link '${link.name}' cannot use unkeyed table '${identityTable}' as a row identity`
|
|
3141
|
+
});
|
|
3142
|
+
continue;
|
|
3143
|
+
}
|
|
2921
3144
|
const targetEntries = targetName === PLATFORM_USER_TABLE ? target.columns.map((c) => ({ ...c, deprecated: false, onUpdateFn: false })) : columnEntries.get(targetName);
|
|
2922
3145
|
const onEntries = link.kind === "toMany" ? targetEntries : entries;
|
|
2923
3146
|
const referencesEntries = link.kind === "toMany" ? entries : targetEntries;
|
|
@@ -2983,7 +3206,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
2983
3206
|
compilePolicyFor("write", writePolicies);
|
|
2984
3207
|
compilePolicyFor("insert", insertPolicies);
|
|
2985
3208
|
compilePolicyFor("update", updatePolicies);
|
|
2986
|
-
if (isView && table.resolved === void 0) {
|
|
3209
|
+
if (isView && table.resolved === void 0 || unkeyedSyncedTableNames.has(table.table)) {
|
|
2987
3210
|
writePolicies.set(table.table, CONST_FALSE);
|
|
2988
3211
|
insertPolicies.set(table.table, CONST_FALSE);
|
|
2989
3212
|
updatePolicies.set(table.table, CONST_FALSE);
|
|
@@ -3058,7 +3281,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
3058
3281
|
const queryRoots = /* @__PURE__ */ new Map();
|
|
3059
3282
|
const mutationRoots = /* @__PURE__ */ new Map();
|
|
3060
3283
|
for (const table of relations) {
|
|
3061
|
-
if (table.internal === true) continue;
|
|
3284
|
+
if (table.internal === true || unkeyedSyncedTableNames.has(table.table)) continue;
|
|
3062
3285
|
const roots = queryRootNames(table.table);
|
|
3063
3286
|
queryRoots.set(roots.select, { kind: "select", table: table.table });
|
|
3064
3287
|
if ((tables[table.table]?.primaryKey.length ?? 0) > 0) {
|
|
@@ -3438,8 +3661,10 @@ var OperationLowerer = class {
|
|
|
3438
3661
|
constructor(ctx, restricted) {
|
|
3439
3662
|
this.ctx = ctx;
|
|
3440
3663
|
this.variables = new Map(restricted.variables.map((v) => [v.name, v]));
|
|
3664
|
+
this.maxRows = restricted.readBudget ?? MAX_LIMIT;
|
|
3441
3665
|
}
|
|
3442
3666
|
variables;
|
|
3667
|
+
maxRows;
|
|
3443
3668
|
meta(table) {
|
|
3444
3669
|
return this.ctx.tables[table];
|
|
3445
3670
|
}
|
|
@@ -3643,13 +3868,13 @@ var OperationLowerer = class {
|
|
|
3643
3868
|
});
|
|
3644
3869
|
}
|
|
3645
3870
|
lowerLimit(node) {
|
|
3646
|
-
const base2 = { default: DEFAULT_LIMIT, max:
|
|
3871
|
+
const base2 = { default: DEFAULT_LIMIT, max: this.maxRows };
|
|
3647
3872
|
if (!node) return base2;
|
|
3648
3873
|
if (node.kind === graphql.Kind.VARIABLE) return { ...base2, variable: node.name.value };
|
|
3649
3874
|
if (node.kind === graphql.Kind.INT) {
|
|
3650
3875
|
const value = Number(node.value);
|
|
3651
3876
|
if (value < 1) fail2("limit must be a positive integer", "invalid-limit");
|
|
3652
|
-
return { ...base2, value: Math.min(value,
|
|
3877
|
+
return { ...base2, value: Math.min(value, this.maxRows) };
|
|
3653
3878
|
}
|
|
3654
3879
|
fail2("limit must be an integer literal or a variable", "invalid-limit");
|
|
3655
3880
|
}
|
|
@@ -4448,7 +4673,13 @@ var OperationLowerer = class {
|
|
|
4448
4673
|
};
|
|
4449
4674
|
function lowerOperation({ restricted, ctx }) {
|
|
4450
4675
|
const lowerer = new OperationLowerer(ctx, restricted);
|
|
4451
|
-
|
|
4676
|
+
const roots = lowerer.lowerRoots(restricted.definition, restricted.kind);
|
|
4677
|
+
if (restricted.readBudget === void 0) return roots;
|
|
4678
|
+
const root = roots[0];
|
|
4679
|
+
if (restricted.kind !== "query" || restricted.definition.selectionSet.selections.length !== 1 || roots.length !== 1 || root?.kind !== "select" || root.selection.relations.length > 0 || root.selection.aggregates.length > 0 || root.selection.cells !== void 0 || root.prefetch?.length) {
|
|
4680
|
+
fail2("@readBudget requires one flat select root without related selections, aggregates, cells, or prefetch", "invalid-read-budget");
|
|
4681
|
+
}
|
|
4682
|
+
return roots;
|
|
4452
4683
|
}
|
|
4453
4684
|
function isCellColumn(meta, name2) {
|
|
4454
4685
|
if (!meta.cells) return false;
|
|
@@ -4507,6 +4738,19 @@ function parseDocuments({
|
|
|
4507
4738
|
}
|
|
4508
4739
|
return { documents: parsed, diagnostics };
|
|
4509
4740
|
}
|
|
4741
|
+
function parseReadBudget(operation) {
|
|
4742
|
+
const directives = operation.directives?.filter((directive) => directive.name.value === "readBudget") ?? [];
|
|
4743
|
+
if (directives.length === 0) return void 0;
|
|
4744
|
+
const value = directives[0]?.arguments?.find((argument) => argument.name.value === "maxRows")?.value;
|
|
4745
|
+
if (operation.operation !== "query" || directives.length !== 1 || value?.kind !== graphql.Kind.INT) {
|
|
4746
|
+
throw new DocumentCompileError("@readBudget requires a literal maxRows on a query", "invalid-read-budget");
|
|
4747
|
+
}
|
|
4748
|
+
const maxRows = Number(value.value);
|
|
4749
|
+
if (!Number.isSafeInteger(maxRows) || maxRows < 1 || maxRows > MAX_PERSISTED_READ_ROWS) {
|
|
4750
|
+
throw new DocumentCompileError(`@readBudget(maxRows:) must be between 1 and ${MAX_PERSISTED_READ_ROWS}`, "invalid-read-budget");
|
|
4751
|
+
}
|
|
4752
|
+
return maxRows;
|
|
4753
|
+
}
|
|
4510
4754
|
function unwrapType(node, variable) {
|
|
4511
4755
|
let nullable = true;
|
|
4512
4756
|
let list2 = false;
|
|
@@ -4667,6 +4911,7 @@ function restrictOperation({
|
|
|
4667
4911
|
name: name2,
|
|
4668
4912
|
kind: operation.operation === "mutation" ? "mutation" : "query",
|
|
4669
4913
|
requires: parseRequires(operation),
|
|
4914
|
+
readBudget: parseReadBudget(operation),
|
|
4670
4915
|
variables,
|
|
4671
4916
|
definition: operation
|
|
4672
4917
|
};
|
|
@@ -5180,9 +5425,9 @@ function compilePredIrToSql(pred, options = {}) {
|
|
|
5180
5425
|
case "cmp":
|
|
5181
5426
|
return compileCmp(pred.op, pred.column, pred.value, options);
|
|
5182
5427
|
case "capability":
|
|
5183
|
-
return `(${capabilityExpression(pred.key, pred.namespace ?? options.namespace)})`;
|
|
5428
|
+
return `(${options.statementClaimChecks ? "SELECT " : ""}${capabilityExpression(pred.key, pred.namespace ?? options.namespace)})`;
|
|
5184
5429
|
case "role":
|
|
5185
|
-
return `(${roleExpression(pred.key, pred.namespace ?? options.namespace)})`;
|
|
5430
|
+
return `(${options.statementClaimChecks ? "SELECT " : ""}${roleExpression(pred.key, pred.namespace ?? options.namespace)})`;
|
|
5186
5431
|
case "exists":
|
|
5187
5432
|
if (options.exists) return options.exists(pred);
|
|
5188
5433
|
throw new OrmDefinitionError("synced view read policy cannot use 'exists' without a relationship resolver");
|
|
@@ -5290,7 +5535,8 @@ function createSqlCatalog({ canonical, publishedObjects, executionTarget }) {
|
|
|
5290
5535
|
const { ctx, diagnostics } = buildCompileContext(canonical);
|
|
5291
5536
|
if (diagnostics.some((d) => d.severity === "error")) throw new Error(diagnostics.map((d) => d.message).join("; "));
|
|
5292
5537
|
const identity = sqlHash(`ontology-sql-v1\0${computeSchemaHash(canonical)}\0${executionTarget}`);
|
|
5293
|
-
const
|
|
5538
|
+
const logicalNames = new Map(Object.entries(ctx.tables).map(([key, meta]) => [meta, key]));
|
|
5539
|
+
const refOf = (meta) => `${meta.namespace ?? canonical.namespace}/${logicalNames.get(meta)}`;
|
|
5294
5540
|
const definitions = [canonical, ...canonical.imports ?? []];
|
|
5295
5541
|
const internal = new Set(definitions.flatMap((owner) => [
|
|
5296
5542
|
...owner.tables.filter((t) => t.internal).map((t) => `${owner.namespace}/${t.table}`),
|
|
@@ -5366,15 +5612,48 @@ function only(node, keys) {
|
|
|
5366
5612
|
var parser;
|
|
5367
5613
|
var SQL_TYPES = ["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz"];
|
|
5368
5614
|
var pgType = (type) => type === "double" ? "double precision" : SQL_TYPES.some((t) => t === type) ? type : fail3("unsupported SQL type");
|
|
5369
|
-
async function
|
|
5370
|
-
if (
|
|
5615
|
+
async function parseSelect(sql) {
|
|
5616
|
+
if (Buffer.byteLength(sql) > 65536) fail3("SQL limit exceeded");
|
|
5371
5617
|
parser ??= new pgParser.PgParser({ version: 17 });
|
|
5372
5618
|
const parsed = await parser.parse(sql);
|
|
5373
5619
|
if (parsed.error || !parsed.tree) fail3("invalid SQL");
|
|
5374
5620
|
const statements = list(obj(parsed.tree).stmts);
|
|
5375
5621
|
if (statements.length !== 1) fail3("one SQL SELECT is required");
|
|
5622
|
+
const statement = obj(obj(statements[0]).stmt);
|
|
5623
|
+
if (!statement.SelectStmt) fail3("one SQL SELECT is required");
|
|
5624
|
+
return statement;
|
|
5625
|
+
}
|
|
5626
|
+
async function referencedSqlObjects({ sql }) {
|
|
5627
|
+
const objects = /* @__PURE__ */ new Set();
|
|
5628
|
+
let nodes = 0;
|
|
5629
|
+
function visit2(value, depth) {
|
|
5630
|
+
if (++nodes > 1e4 || depth > 96) fail3("SQL complexity limit exceeded");
|
|
5631
|
+
if (Array.isArray(value)) {
|
|
5632
|
+
for (const child of value) visit2(child, depth + 1);
|
|
5633
|
+
return;
|
|
5634
|
+
}
|
|
5635
|
+
if (!value || typeof value !== "object") return;
|
|
5636
|
+
const node = obj(value);
|
|
5637
|
+
if (node.RangeVar) {
|
|
5638
|
+
const range = obj(node.RangeVar);
|
|
5639
|
+
if (range.schemaname) {
|
|
5640
|
+
const namespace = str(range.schemaname), name2 = str(range.relname);
|
|
5641
|
+
if (!/^[a-z][a-z0-9_]{0,40}$/.test(namespace) || namespace === "pg_catalog" || namespace === "information_schema") fail3("invalid ontology namespace");
|
|
5642
|
+
objects.add(`${namespace}/${name2}`);
|
|
5643
|
+
if (objects.size > 256) fail3("too many SQL objects");
|
|
5644
|
+
}
|
|
5645
|
+
}
|
|
5646
|
+
for (const child of Object.values(node)) visit2(child, depth + 1);
|
|
5647
|
+
}
|
|
5648
|
+
visit2(await parseSelect(sql), 0);
|
|
5649
|
+
if (!objects.size) fail3("SQL operation must read an ontology object");
|
|
5650
|
+
return [...objects].sort();
|
|
5651
|
+
}
|
|
5652
|
+
async function compileOntologySql({ catalog, sql, parameterTypes = [], maxRows = 1e3 }) {
|
|
5653
|
+
if (!Number.isInteger(maxRows) || maxRows < 1 || maxRows > 1e4 || Buffer.byteLength(sql) > 65536) fail3("SQL limit exceeded");
|
|
5654
|
+
const statement = await parseSelect(sql);
|
|
5376
5655
|
const emitter = new SqlEmitter({ catalog, parameterTypes });
|
|
5377
|
-
const result = emitter.select(
|
|
5656
|
+
const result = emitter.select(statement, { relations: [], ctes: /* @__PURE__ */ new Map() });
|
|
5378
5657
|
if (emitter.used.size !== parameterTypes.length) fail3("SQL parameters must be used exactly as declared");
|
|
5379
5658
|
if (!emitter.read.size) fail3("SQL operation must read an ontology object");
|
|
5380
5659
|
return {
|
|
@@ -5707,6 +5986,118 @@ function bindNamedParameters({ sql, names }) {
|
|
|
5707
5986
|
}
|
|
5708
5987
|
return result;
|
|
5709
5988
|
}
|
|
5989
|
+
var parser2;
|
|
5990
|
+
var functions = new Set(VIEW_QUERY_FUNCTION_ALLOWLIST);
|
|
5991
|
+
var scalarTypes = /* @__PURE__ */ new Set([
|
|
5992
|
+
"bool",
|
|
5993
|
+
"boolean",
|
|
5994
|
+
"int2",
|
|
5995
|
+
"int4",
|
|
5996
|
+
"int8",
|
|
5997
|
+
"integer",
|
|
5998
|
+
"bigint",
|
|
5999
|
+
"smallint",
|
|
6000
|
+
"numeric",
|
|
6001
|
+
"decimal",
|
|
6002
|
+
"float4",
|
|
6003
|
+
"float8",
|
|
6004
|
+
"real",
|
|
6005
|
+
"text",
|
|
6006
|
+
"varchar",
|
|
6007
|
+
"bpchar",
|
|
6008
|
+
"uuid",
|
|
6009
|
+
"date",
|
|
6010
|
+
"timestamp",
|
|
6011
|
+
"timestamptz",
|
|
6012
|
+
"time",
|
|
6013
|
+
"timetz",
|
|
6014
|
+
"interval",
|
|
6015
|
+
"json",
|
|
6016
|
+
"jsonb",
|
|
6017
|
+
"bytea"
|
|
6018
|
+
]);
|
|
6019
|
+
var sqlValues = /* @__PURE__ */ new Set([
|
|
6020
|
+
"SVFOP_CURRENT_DATE",
|
|
6021
|
+
"SVFOP_CURRENT_TIME",
|
|
6022
|
+
"SVFOP_CURRENT_TIME_N",
|
|
6023
|
+
"SVFOP_CURRENT_TIMESTAMP",
|
|
6024
|
+
"SVFOP_CURRENT_TIMESTAMP_N",
|
|
6025
|
+
"SVFOP_LOCALTIME",
|
|
6026
|
+
"SVFOP_LOCALTIME_N",
|
|
6027
|
+
"SVFOP_LOCALTIMESTAMP",
|
|
6028
|
+
"SVFOP_LOCALTIMESTAMP_N",
|
|
6029
|
+
"SVFOP_CURRENT_ROLE",
|
|
6030
|
+
"SVFOP_CURRENT_USER",
|
|
6031
|
+
"SVFOP_USER",
|
|
6032
|
+
"SVFOP_SESSION_USER",
|
|
6033
|
+
"SVFOP_CURRENT_CATALOG",
|
|
6034
|
+
"SVFOP_CURRENT_SCHEMA"
|
|
6035
|
+
]);
|
|
6036
|
+
async function validateManagedViewQueries({ canonical }) {
|
|
6037
|
+
for (const definition of [canonical, ...canonical.imports ?? []]) {
|
|
6038
|
+
for (const view of definition.views ?? []) {
|
|
6039
|
+
const fail4 = (message) => {
|
|
6040
|
+
throw new OrmDefinitionError(`managed view '${definition.namespace}/${view.view}': ${message}`);
|
|
6041
|
+
};
|
|
6042
|
+
parser2 ??= new pgParser.PgParser({ version: 17 });
|
|
6043
|
+
const parsed = await parser2.parse(view.query);
|
|
6044
|
+
if (parsed.error || !parsed.tree) fail4("invalid SELECT query");
|
|
6045
|
+
const statements = list(obj(parsed.tree).stmts);
|
|
6046
|
+
if (statements.length !== 1 || !obj(obj(statements[0]).stmt).SelectStmt) fail4("one SELECT query is required");
|
|
6047
|
+
validateTree({ tree: parsed.tree, schema: ormSchemaName(definition.namespace), enums: definition.enums.map((entry) => entry.name), fail: fail4 });
|
|
6048
|
+
}
|
|
6049
|
+
}
|
|
6050
|
+
}
|
|
6051
|
+
function pgCatalogName(parts) {
|
|
6052
|
+
if (parts.length === 1) return parts[0];
|
|
6053
|
+
if (parts.length === 2 && parts[0] === "pg_catalog") return parts[1];
|
|
6054
|
+
return void 0;
|
|
6055
|
+
}
|
|
6056
|
+
function reviewedFunctionName({ parts, format }) {
|
|
6057
|
+
return format === "COERCE_SQL_SYNTAX" && parts.length === 2 && parts[0] === "pg_catalog" && parts[1] === "position" ? "strpos" : pgCatalogName(parts);
|
|
6058
|
+
}
|
|
6059
|
+
function validateTree({ tree, ...scope }) {
|
|
6060
|
+
const pending = [tree];
|
|
6061
|
+
while (pending.length) {
|
|
6062
|
+
const value = pending.pop();
|
|
6063
|
+
if (Array.isArray(value)) {
|
|
6064
|
+
pending.push(...value);
|
|
6065
|
+
continue;
|
|
6066
|
+
}
|
|
6067
|
+
if (!value || typeof value !== "object") continue;
|
|
6068
|
+
const node = obj(value);
|
|
6069
|
+
for (const [kind, child] of Object.entries(node)) {
|
|
6070
|
+
validateNode({ kind, child, scope });
|
|
6071
|
+
pending.push(child);
|
|
6072
|
+
}
|
|
6073
|
+
}
|
|
6074
|
+
}
|
|
6075
|
+
function validateNode({ kind, child, scope }) {
|
|
6076
|
+
const { fail: fail4 } = scope;
|
|
6077
|
+
if (kind.endsWith("Stmt") && kind !== "SelectStmt") fail4("only SELECT statements are permitted");
|
|
6078
|
+
if (kind === "FuncCall") {
|
|
6079
|
+
const call = obj(child);
|
|
6080
|
+
const parts = strings(call.funcname);
|
|
6081
|
+
const name2 = reviewedFunctionName({ parts, format: call.funcformat });
|
|
6082
|
+
if (!name2 || !functions.has(name2)) fail4(`only explicitly allowlisted pg_catalog functions are permitted (found '${parts.join(".")}')`);
|
|
6083
|
+
}
|
|
6084
|
+
if (kind === "typeName" || kind === "TypeName") validateType({ node: obj(child), ...scope });
|
|
6085
|
+
if (kind === "A_Expr" && pgCatalogName(strings(obj(child).name)) === void 0) fail4("only pg_catalog operators are permitted");
|
|
6086
|
+
if (kind === "SortBy" && list(obj(child).useOp).length && pgCatalogName(strings(obj(child).useOp)) === void 0) fail4("only pg_catalog sort operators are permitted");
|
|
6087
|
+
if (kind === "SQLValueFunction" && !sqlValues.has(String(obj(child).op))) fail4("unsupported SQL value expression");
|
|
6088
|
+
if (kind === "ParamRef") fail4("view queries cannot contain parameters");
|
|
6089
|
+
if (kind === "A_Indirection" && list(obj(child).indirection).some((entry) => !obj(entry).A_Indices)) {
|
|
6090
|
+
fail4("composite field notation is not permitted in managed views");
|
|
6091
|
+
}
|
|
6092
|
+
}
|
|
6093
|
+
function validateType({ node, schema, enums, fail: fail4 }) {
|
|
6094
|
+
const parts = strings(node.names);
|
|
6095
|
+
const builtin = pgCatalogName(parts);
|
|
6096
|
+
const managedEnum = parts.length === 1 && enums.includes(parts[0]) || parts.length === 2 && parts[0] === schema && enums.includes(parts[1]);
|
|
6097
|
+
if (node.setof || node.pct_type || !managedEnum && (!builtin || !scalarTypes.has(builtin))) {
|
|
6098
|
+
fail4(`casts may only use supported pg_catalog scalar types or declared enums (found '${parts.join(".")}')`);
|
|
6099
|
+
}
|
|
6100
|
+
}
|
|
5710
6101
|
|
|
5711
6102
|
// src/sql/operations.ts
|
|
5712
6103
|
function isSqlOperationDocument(path, contents) {
|
|
@@ -5722,7 +6113,13 @@ function isSqlOperationDocument(path, contents) {
|
|
|
5722
6113
|
async function compileOperationDocuments(input) {
|
|
5723
6114
|
const sqlDocuments = input.documents.filter((d) => isSqlOperationDocument(d.path, d.contents));
|
|
5724
6115
|
const result = compileNamespace({ ...input, documents: input.documents.filter((d) => !isSqlOperationDocument(d.path, d.contents)) });
|
|
5725
|
-
if (!result.ok
|
|
6116
|
+
if (!result.ok) return result;
|
|
6117
|
+
const viewDiagnostics = await managedViewDiagnostics(input.canonical);
|
|
6118
|
+
if (viewDiagnostics.length) return { ok: false, diagnostics: [...result.diagnostics, ...viewDiagnostics] };
|
|
6119
|
+
if (!sqlDocuments.length) return result;
|
|
6120
|
+
return compileSqlDocuments({ input, result, sqlDocuments });
|
|
6121
|
+
}
|
|
6122
|
+
async function compileSqlDocuments({ input, result, sqlDocuments }) {
|
|
5726
6123
|
for (const document of sqlDocuments) {
|
|
5727
6124
|
try {
|
|
5728
6125
|
const definition = defineOperation(JSON.parse(document.contents));
|
|
@@ -5816,6 +6213,18 @@ async function compileOperationDocuments(input) {
|
|
|
5816
6213
|
result.manifest.opsHash = result.artifact.opsHash;
|
|
5817
6214
|
return { ...result, artifact: normalizeIr(result.artifact) };
|
|
5818
6215
|
}
|
|
6216
|
+
async function managedViewDiagnostics(canonical) {
|
|
6217
|
+
try {
|
|
6218
|
+
await validateManagedViewQueries({ canonical });
|
|
6219
|
+
return [];
|
|
6220
|
+
} catch (error) {
|
|
6221
|
+
return [{
|
|
6222
|
+
severity: "error",
|
|
6223
|
+
code: "invalid-view-query",
|
|
6224
|
+
message: error instanceof Error ? error.message : "invalid managed view query"
|
|
6225
|
+
}];
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
5819
6228
|
var parameterName = zod.z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(63).refine((value) => !["__proto__", "prototype", "constructor"].includes(value), "reserved parameter name");
|
|
5820
6229
|
var parameter = zod.z.discriminatedUnion("type", [
|
|
5821
6230
|
zod.z.object({ type: zod.z.literal("text"), value: zod.z.string().nullable() }).strict(),
|
|
@@ -5832,7 +6241,7 @@ var sqlQueryInputSchema = zod.z.object({
|
|
|
5832
6241
|
sql: zod.z.string().min(1).max(65536).refine((value) => Buffer.byteLength(value) <= 65536, "SQL exceeds 64 KiB"),
|
|
5833
6242
|
parameters: zod.z.record(parameterName, parameter).refine((value) => Object.keys(value).length <= 128, "too many SQL parameters").default({})
|
|
5834
6243
|
}).strict();
|
|
5835
|
-
async function compileRuntimeSql({ operation, input }) {
|
|
6244
|
+
async function compileRuntimeSql({ operation, input, qualified = false }) {
|
|
5836
6245
|
const contract = operation.sqlQuery;
|
|
5837
6246
|
if (!contract) throw new Error("missing runtime SQL contract");
|
|
5838
6247
|
const names = Object.keys(input.parameters).sort();
|
|
@@ -5848,10 +6257,87 @@ async function compileRuntimeSql({ operation, input }) {
|
|
|
5848
6257
|
sqlQuery: void 0,
|
|
5849
6258
|
irVersion: SQL_IR_VERSION,
|
|
5850
6259
|
variables: names.map((name2) => ({ name: name2, type: { kind: "scalar", scalar: input.parameters[name2].type }, nullable: true, list: false })),
|
|
5851
|
-
tableSet: plan.read.map((ref) => ref.split("/")[1]).sort(),
|
|
6260
|
+
tableSet: qualified ? plan.read : plan.read.map((ref) => ref.split("/")[1]).sort(),
|
|
5852
6261
|
sql: { text: plan.text, columns: plan.columns, parameters: names, read: plan.read, maxRows: plan.maxRows }
|
|
5853
6262
|
});
|
|
5854
6263
|
}
|
|
6264
|
+
|
|
6265
|
+
// src/sql/builtin.ts
|
|
6266
|
+
function localHash(definition) {
|
|
6267
|
+
const { imports: _imports, ...local } = definition;
|
|
6268
|
+
return computeSchemaHash(normalizeCanonical(local));
|
|
6269
|
+
}
|
|
6270
|
+
function compileBuiltinSqlArtifact({ namespace, definitions, read, unbackedObjects = [], executionTarget = "default" }) {
|
|
6271
|
+
const anchor = definitions.find((definition) => definition.namespace === namespace);
|
|
6272
|
+
if (!anchor) throw new Error("missing SQL namespace definition");
|
|
6273
|
+
const tables = {};
|
|
6274
|
+
const policies = {};
|
|
6275
|
+
const objects = [];
|
|
6276
|
+
for (const definition of definitions) {
|
|
6277
|
+
const owner = definition.namespace;
|
|
6278
|
+
const { ctx, diagnostics } = buildCompileContext(definition);
|
|
6279
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) throw new Error("SQL namespace definition is invalid");
|
|
6280
|
+
const ref = (key) => {
|
|
6281
|
+
const meta = ctx.tables[key];
|
|
6282
|
+
if (!meta) throw new Error("SQL dependency is unavailable");
|
|
6283
|
+
return `${meta.namespace ?? owner}/${key}`;
|
|
6284
|
+
};
|
|
6285
|
+
for (const [key, meta] of Object.entries(ctx.tables)) {
|
|
6286
|
+
if ((meta.namespace ?? owner) !== owner) continue;
|
|
6287
|
+
const identity = ref(key);
|
|
6288
|
+
tables[identity] = {
|
|
6289
|
+
...meta,
|
|
6290
|
+
namespace: owner,
|
|
6291
|
+
links: meta.links.map((link) => ({ ...link, ...link.target ? { target: ref(link.target) } : {} })),
|
|
6292
|
+
...meta.dependencies ? { dependencies: meta.dependencies.map(ref) } : {},
|
|
6293
|
+
...meta.foreignReferences ? { foreignReferences: meta.foreignReferences.map((link) => ({ ...link, target: ref(link.target) })) } : {}
|
|
6294
|
+
};
|
|
6295
|
+
const policy = ctx.readPolicies.get(key);
|
|
6296
|
+
if (policy) policies[identity] = policy;
|
|
6297
|
+
}
|
|
6298
|
+
const selected = read.filter((identity) => identity.startsWith(`${owner}/`));
|
|
6299
|
+
if (!selected.length) continue;
|
|
6300
|
+
if (owner !== namespace && selected.some((identity) => !definition.exports?.[identity.slice(owner.length + 1)]?.read)) {
|
|
6301
|
+
throw new Error("SQL object is not exported");
|
|
6302
|
+
}
|
|
6303
|
+
objects.push(...createSqlCatalog({ canonical: definition, publishedObjects: selected, executionTarget }).objects);
|
|
6304
|
+
}
|
|
6305
|
+
if (objects.length !== read.length) throw new Error("SQL object is unavailable");
|
|
6306
|
+
const actualRead = [...new Set(objects.flatMap((object) => object.read))].sort();
|
|
6307
|
+
const catalog = { objects, identity: sqlHash(JSON.stringify([
|
|
6308
|
+
"builtin-sql-catalog-v1",
|
|
6309
|
+
namespace,
|
|
6310
|
+
executionTarget,
|
|
6311
|
+
read,
|
|
6312
|
+
definitions.map((definition) => [definition.namespace, computeSchemaHash(definition)]).sort()
|
|
6313
|
+
])) };
|
|
6314
|
+
return normalizeIr({
|
|
6315
|
+
irVersion: RUNTIME_SQL_IR_VERSION,
|
|
6316
|
+
namespace,
|
|
6317
|
+
schemaHash: catalog.identity,
|
|
6318
|
+
ownSchemaHash: localHash(anchor),
|
|
6319
|
+
opsHash: BUILTIN_SQL_READ_HASH,
|
|
6320
|
+
dependencies: Object.fromEntries(definitions.filter((definition) => definition.namespace !== namespace).map((definition) => [definition.namespace, localHash(definition)])),
|
|
6321
|
+
tables,
|
|
6322
|
+
readPolicies: policies,
|
|
6323
|
+
unbackedTables: [...unbackedObjects],
|
|
6324
|
+
operations: { BuiltinSqlRead: {
|
|
6325
|
+
irVersion: RUNTIME_SQL_IR_VERSION,
|
|
6326
|
+
name: "BuiltinSqlRead",
|
|
6327
|
+
opHash: BUILTIN_SQL_READ_HASH,
|
|
6328
|
+
language: "sql",
|
|
6329
|
+
kind: "query",
|
|
6330
|
+
requires: null,
|
|
6331
|
+
roots: [],
|
|
6332
|
+
tableSet: actualRead,
|
|
6333
|
+
variables: [
|
|
6334
|
+
{ name: "sql", type: { kind: "scalar", scalar: "text" }, nullable: false, list: false },
|
|
6335
|
+
{ name: "parameters", type: { kind: "scalar", scalar: "jsonb" }, nullable: true, list: false }
|
|
6336
|
+
],
|
|
6337
|
+
sqlQuery: { catalog, read: actualRead, maxRows: 1e3 }
|
|
6338
|
+
} }
|
|
6339
|
+
});
|
|
6340
|
+
}
|
|
5855
6341
|
var DATASET_PREVIEW_LIMIT = 100;
|
|
5856
6342
|
function compileDatasetPreviews(input) {
|
|
5857
6343
|
const canonical = normalizeCanonical(input);
|
|
@@ -5916,11 +6402,13 @@ exports.allowedBoolExpOperators = allowedBoolExpOperators;
|
|
|
5916
6402
|
exports.allowedOps = allowedOps;
|
|
5917
6403
|
exports.andPred = andPred;
|
|
5918
6404
|
exports.assertClaimsOnlyWhen = assertClaimsOnlyWhen;
|
|
6405
|
+
exports.bindNamedParameters = bindNamedParameters;
|
|
5919
6406
|
exports.buildCompileContext = buildCompileContext;
|
|
5920
6407
|
exports.camelCase = camelCase;
|
|
5921
6408
|
exports.canonicalOperationText = canonicalOperationText;
|
|
5922
6409
|
exports.classifyInputType = classifyInputType;
|
|
5923
6410
|
exports.collectPinnedColumns = collectPinnedColumns;
|
|
6411
|
+
exports.compileBuiltinSqlArtifact = compileBuiltinSqlArtifact;
|
|
5924
6412
|
exports.compileDatasetPreviews = compileDatasetPreviews;
|
|
5925
6413
|
exports.compileNamespace = compileNamespace;
|
|
5926
6414
|
exports.compileNamespaceOrThrow = compileNamespaceOrThrow;
|
|
@@ -5948,6 +6436,7 @@ exports.platformUserMeta = platformUserMeta;
|
|
|
5948
6436
|
exports.predContains = predContains;
|
|
5949
6437
|
exports.predContainsExists = predContainsExists;
|
|
5950
6438
|
exports.queryRootNames = queryRootNames;
|
|
6439
|
+
exports.referencedSqlObjects = referencedSqlObjects;
|
|
5951
6440
|
exports.restrictOperation = restrictOperation;
|
|
5952
6441
|
exports.sqlOperationDefinitionSchema = sqlOperationDefinitionSchema;
|
|
5953
6442
|
exports.sqlQueryInputSchema = sqlQueryInputSchema;
|
|
@@ -5955,6 +6444,7 @@ exports.typeNameForTable = typeNameForTable;
|
|
|
5955
6444
|
exports.validateAppendInput = validateAppendInput;
|
|
5956
6445
|
exports.validateBoolExpInput = validateBoolExpInput;
|
|
5957
6446
|
exports.validateInsertInput = validateInsertInput;
|
|
6447
|
+
exports.validateManagedViewQueries = validateManagedViewQueries;
|
|
5958
6448
|
exports.validateOnConflictInput = validateOnConflictInput;
|
|
5959
6449
|
exports.validateOrderByInput = validateOrderByInput;
|
|
5960
6450
|
exports.validateSelectColumns = validateSelectColumns;
|