@typed-sql/postgres 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/CHANGELOG.md +289 -0
  2. package/README.md +17 -0
  3. package/dist/packages/postgres/src/bulk.d.ts.map +1 -1
  4. package/dist/packages/postgres/src/bulk.js +1 -1
  5. package/dist/packages/postgres/src/bulk.js.map +1 -1
  6. package/dist/packages/postgres/src/capabilities.d.ts +19 -0
  7. package/dist/packages/postgres/src/capabilities.d.ts.map +1 -0
  8. package/dist/packages/postgres/src/capabilities.js +120 -0
  9. package/dist/packages/postgres/src/capabilities.js.map +1 -0
  10. package/dist/packages/postgres/src/catalog/generated/14.d.ts +4 -0
  11. package/dist/packages/postgres/src/catalog/generated/14.d.ts.map +1 -0
  12. package/dist/packages/postgres/src/catalog/generated/14.js +2018 -0
  13. package/dist/packages/postgres/src/catalog/generated/14.js.map +1 -0
  14. package/dist/packages/postgres/src/catalog/generated/15.d.ts +4 -0
  15. package/dist/packages/postgres/src/catalog/generated/15.d.ts.map +1 -0
  16. package/dist/packages/postgres/src/catalog/generated/15.js +2012 -0
  17. package/dist/packages/postgres/src/catalog/generated/15.js.map +1 -0
  18. package/dist/packages/postgres/src/catalog/generated/16.d.ts +4 -0
  19. package/dist/packages/postgres/src/catalog/generated/16.d.ts.map +1 -0
  20. package/dist/packages/postgres/src/catalog/generated/16.js +2012 -0
  21. package/dist/packages/postgres/src/catalog/generated/16.js.map +1 -0
  22. package/dist/packages/postgres/src/catalog/generated/17.d.ts +4 -0
  23. package/dist/packages/postgres/src/catalog/generated/17.d.ts.map +1 -0
  24. package/dist/packages/postgres/src/catalog/generated/17.js +2012 -0
  25. package/dist/packages/postgres/src/catalog/generated/17.js.map +1 -0
  26. package/dist/packages/postgres/src/catalog/generated/18.d.ts +4 -0
  27. package/dist/packages/postgres/src/catalog/generated/18.d.ts.map +1 -0
  28. package/dist/packages/postgres/src/catalog/generated/18.js +2048 -0
  29. package/dist/packages/postgres/src/catalog/generated/18.js.map +1 -0
  30. package/dist/packages/postgres/src/catalog/generated/19.d.ts +4 -0
  31. package/dist/packages/postgres/src/catalog/generated/19.d.ts.map +1 -0
  32. package/dist/packages/postgres/src/catalog/generated/19.js +2048 -0
  33. package/dist/packages/postgres/src/catalog/generated/19.js.map +1 -0
  34. package/dist/packages/postgres/src/catalog/index.d.ts +14 -0
  35. package/dist/packages/postgres/src/catalog/index.d.ts.map +1 -0
  36. package/dist/packages/postgres/src/catalog/index.js +86 -0
  37. package/dist/packages/postgres/src/catalog/index.js.map +1 -0
  38. package/dist/packages/postgres/src/catalog/types.d.ts +46 -0
  39. package/dist/packages/postgres/src/catalog/types.d.ts.map +1 -0
  40. package/dist/packages/postgres/src/catalog/types.js +2 -0
  41. package/dist/packages/postgres/src/catalog/types.js.map +1 -0
  42. package/dist/packages/postgres/src/expression-evidence.d.ts +11 -0
  43. package/dist/packages/postgres/src/expression-evidence.d.ts.map +1 -0
  44. package/dist/packages/postgres/src/expression-evidence.js +33 -0
  45. package/dist/packages/postgres/src/expression-evidence.js.map +1 -0
  46. package/dist/packages/postgres/src/extensions.d.ts +61 -0
  47. package/dist/packages/postgres/src/extensions.d.ts.map +1 -0
  48. package/dist/packages/postgres/src/extensions.js +311 -0
  49. package/dist/packages/postgres/src/extensions.js.map +1 -0
  50. package/dist/packages/postgres/src/index.d.ts +13 -0
  51. package/dist/packages/postgres/src/index.d.ts.map +1 -1
  52. package/dist/packages/postgres/src/index.js +51 -17
  53. package/dist/packages/postgres/src/index.js.map +1 -1
  54. package/dist/packages/postgres/src/parser/index.d.ts +8 -0
  55. package/dist/packages/postgres/src/parser/index.d.ts.map +1 -0
  56. package/dist/packages/postgres/src/parser/index.js +4 -0
  57. package/dist/packages/postgres/src/parser/index.js.map +1 -0
  58. package/dist/packages/postgres/src/parser/parser.d.ts +14 -0
  59. package/dist/packages/postgres/src/parser/parser.d.ts.map +1 -0
  60. package/dist/packages/postgres/src/parser/parser.js +2683 -0
  61. package/dist/packages/postgres/src/parser/parser.js.map +1 -0
  62. package/dist/packages/postgres/src/parser/tokenizer.d.ts +15 -0
  63. package/dist/packages/postgres/src/parser/tokenizer.d.ts.map +1 -0
  64. package/dist/packages/postgres/src/parser/tokenizer.js +470 -0
  65. package/dist/packages/postgres/src/parser/tokenizer.js.map +1 -0
  66. package/dist/packages/postgres/src/parser/types.d.ts +652 -0
  67. package/dist/packages/postgres/src/parser/types.d.ts.map +1 -0
  68. package/dist/packages/postgres/src/parser/types.js +9 -0
  69. package/dist/packages/postgres/src/parser/types.js.map +1 -0
  70. package/dist/packages/postgres/src/parser/walk.d.ts +13 -0
  71. package/dist/packages/postgres/src/parser/walk.d.ts.map +1 -0
  72. package/dist/packages/postgres/src/parser/walk.js +374 -0
  73. package/dist/packages/postgres/src/parser/walk.js.map +1 -0
  74. package/dist/packages/postgres/src/pg.d.ts +35 -4
  75. package/dist/packages/postgres/src/pg.d.ts.map +1 -1
  76. package/dist/packages/postgres/src/pg.js +355 -40
  77. package/dist/packages/postgres/src/pg.js.map +1 -1
  78. package/dist/packages/postgres/src/prepared.d.ts +5 -3
  79. package/dist/packages/postgres/src/prepared.d.ts.map +1 -1
  80. package/dist/packages/postgres/src/prepared.js +23 -15
  81. package/dist/packages/postgres/src/prepared.js.map +1 -1
  82. package/dist/packages/postgres/src/provider.d.ts +149 -11
  83. package/dist/packages/postgres/src/provider.d.ts.map +1 -1
  84. package/dist/packages/postgres/src/provider.js +652 -42
  85. package/dist/packages/postgres/src/provider.js.map +1 -1
  86. package/dist/packages/postgres/src/resolver.d.ts +1 -1
  87. package/dist/packages/postgres/src/resolver.d.ts.map +1 -1
  88. package/dist/packages/postgres/src/resolver.js +2767 -203
  89. package/dist/packages/postgres/src/resolver.js.map +1 -1
  90. package/dist/packages/postgres/src/routing.d.ts.map +1 -1
  91. package/dist/packages/postgres/src/routing.js +1 -1
  92. package/dist/packages/postgres/src/routing.js.map +1 -1
  93. package/dist/packages/postgres/src/runtime-compatibility.d.ts +14 -0
  94. package/dist/packages/postgres/src/runtime-compatibility.d.ts.map +1 -0
  95. package/dist/packages/postgres/src/runtime-compatibility.js +63 -0
  96. package/dist/packages/postgres/src/runtime-compatibility.js.map +1 -0
  97. package/dist/packages/postgres/src/runtime.d.ts +10 -1
  98. package/dist/packages/postgres/src/runtime.d.ts.map +1 -1
  99. package/dist/packages/postgres/src/runtime.js +50 -2
  100. package/dist/packages/postgres/src/runtime.js.map +1 -1
  101. package/dist/packages/postgres/src/semantics.d.ts +1 -1
  102. package/dist/packages/postgres/src/semantics.d.ts.map +1 -1
  103. package/dist/packages/postgres/src/semantics.js +19 -6
  104. package/dist/packages/postgres/src/semantics.js.map +1 -1
  105. package/dist/packages/postgres/src/support.d.ts +29 -0
  106. package/dist/packages/postgres/src/support.d.ts.map +1 -0
  107. package/dist/packages/postgres/src/support.js +45 -0
  108. package/dist/packages/postgres/src/support.js.map +1 -0
  109. package/dist/packages/postgres/src/type-policy.d.ts.map +1 -1
  110. package/dist/packages/postgres/src/type-policy.js +34 -43
  111. package/dist/packages/postgres/src/type-policy.js.map +1 -1
  112. package/dist/packages/postgres/src/type-resolution.d.ts +33 -0
  113. package/dist/packages/postgres/src/type-resolution.d.ts.map +1 -0
  114. package/dist/packages/postgres/src/type-resolution.js +906 -0
  115. package/dist/packages/postgres/src/type-resolution.js.map +1 -0
  116. package/package.json +7 -4
@@ -1,54 +1,19 @@
1
1
  import { closestName, ParameterCollector, ResolverSchemaIndex, unionTypeLiterals, } from "@typed-sql/core";
2
+ import { postgresCatalogRoutineRule, postgresCatalogTableRoutineRule, postgresCatalogTypeMapping, } from "./catalog/index.js";
3
+ import { fingerprintPostgresExpression, postgresExpressionIdentity } from "./expression-evidence.js";
4
+ import { walkStatement } from "./parser/index.js";
5
+ import { parsePostgresMajor } from "./support.js";
2
6
  import { defaultPostgresTypePolicy, isKnownPostgresType, mapPostgresType, } from "./type-policy.js";
3
- const booleanOperators = new Set([
4
- "=",
5
- "!=",
6
- "<>",
7
- "<",
8
- "<=",
9
- ">",
10
- ">=",
11
- "IS",
12
- "IS NOT",
13
- "IS DISTINCT FROM",
14
- "IS NOT DISTINCT FROM",
15
- "LIKE",
16
- "NOT LIKE",
17
- "ILIKE",
18
- "NOT ILIKE",
19
- "SIMILAR TO",
20
- "NOT SIMILAR",
21
- "~",
22
- "~*",
23
- "!~",
24
- "!~*",
25
- "AND",
26
- "OR",
27
- "@>",
28
- "<@",
29
- "?",
30
- "?|",
31
- "?&",
32
- "&&",
33
- ]);
34
- const numericTypes = new Set([
35
- "smallint",
36
- "int2",
37
- "integer",
38
- "int",
39
- "int4",
40
- "bigint",
41
- "int8",
42
- "numeric",
43
- "decimal",
44
- "real",
45
- "float4",
46
- "double precision",
47
- "float8",
48
- ]);
7
+ import { postgresCanCoerce, postgresCanonicalType, postgresCommonType, postgresElementType, postgresTypeCategory, resolvePostgresCandidates, resolvePostgresOperator, resolvePostgresUnaryOperator, } from "./type-resolution.js";
49
8
  function sqlName(identifier) {
50
9
  return identifier.quoted ? identifier.name : identifier.name.toLowerCase();
51
10
  }
11
+ function qualifiedSqlName(identifier) {
12
+ return identifier.parts.map(sqlName).join(".");
13
+ }
14
+ function qualifiedNameIsCaseSensitive(identifier) {
15
+ return identifier.parts.some(({ quoted }) => quoted);
16
+ }
52
17
  function normalizeDatabaseType(value) {
53
18
  return value
54
19
  .trim()
@@ -62,6 +27,31 @@ function suggestion(name, candidates) {
62
27
  return undefined;
63
28
  return `Did you mean ${candidate}?`;
64
29
  }
30
+ function mergeTypeLiterals(left, right) {
31
+ if (left === right)
32
+ return left;
33
+ const leftMembers = left.split(" | ");
34
+ if (leftMembers.includes(right))
35
+ return left;
36
+ const rightMembers = right.split(" | ");
37
+ if (rightMembers.includes(left))
38
+ return right;
39
+ return unionTypeLiterals([left, right]);
40
+ }
41
+ const aggregateRoutineRules = new Set([
42
+ "array-aggregate",
43
+ "boolean-aggregate",
44
+ "count",
45
+ "json-aggregate",
46
+ "numeric-aggregate",
47
+ "ordered-set-value",
48
+ "string-aggregate",
49
+ ]);
50
+ const aggregateExtrema = new Set(["MAX", "MIN"]);
51
+ const hypotheticalAggregates = new Set(["CUME_DIST", "DENSE_RANK", "PERCENT_RANK", "RANK"]);
52
+ function isSpecialGrouping(grouping) {
53
+ return ["empty-group", "grouping-set", "rollup", "cube", "grouping-sets"].includes(grouping.kind);
54
+ }
65
55
  class Resolver {
66
56
  #schema;
67
57
  #policy;
@@ -69,11 +59,14 @@ class Resolver {
69
59
  #diagnostics = [];
70
60
  #parameters = new ParameterCollector();
71
61
  #index;
62
+ #serverMajor;
63
+ #insideMergeReturning = false;
72
64
  constructor(schema, options) {
73
65
  this.#schema = schema;
74
66
  this.#index = ResolverSchemaIndex.for(schema);
75
67
  this.#policy = options.typePolicy ?? defaultPostgresTypePolicy;
76
68
  this.#strictExpressions = options.strictExpressions ?? true;
69
+ this.#serverMajor = parsePostgresMajor(schema.server?.versionKey ?? ("version" in schema ? (schema.version ?? "") : ""));
77
70
  }
78
71
  resolve(statement) {
79
72
  if (this.#schema.dialect !== "postgres") {
@@ -86,43 +79,40 @@ class Resolver {
86
79
  diagnostics: this.#diagnostics,
87
80
  };
88
81
  }
89
- #resolveStatement(statement, outer, inheritedCtes) {
82
+ #resolveStatement(statement, outer, inheritedCtes, expectedOutput) {
90
83
  const ctes = this.#resolveWith(statement.with, outer, inheritedCtes);
91
84
  switch (statement.kind) {
92
85
  case "select":
93
- return { columns: this.#resolveSelect(statement, outer, ctes), resultKind: "rows" };
86
+ return { columns: this.#resolveSelect(statement, outer, ctes, expectedOutput), resultKind: "rows" };
94
87
  case "insert":
95
88
  return this.#resolveInsert(statement, outer, ctes);
96
89
  case "update":
97
90
  return this.#resolveUpdate(statement, outer, ctes);
98
91
  case "delete":
99
92
  return this.#resolveDelete(statement, outer, ctes);
93
+ case "merge":
94
+ return this.#resolveMerge(statement, outer, ctes);
100
95
  }
101
96
  }
102
97
  #resolveWith(withClause, outer, inherited) {
103
98
  const ctes = new Map(inherited);
104
99
  if (withClause === undefined)
105
100
  return ctes;
106
- if (withClause.recursive) {
107
- this.#diagnostic("TSQ210", "Recursive CTE inference is not supported safely", withClause.range, "Use a non-recursive CTE or annotate the query explicitly.");
108
- }
109
101
  for (const query of withClause.queries) {
110
102
  const key = sqlName(query.name);
111
103
  if (ctes.has(key))
112
104
  this.#diagnostic("TSQ211", `Duplicate CTE ${query.name.name}`, query.name.range);
113
- if (withClause.recursive) {
114
- const placeholderColumns = {};
115
- for (const column of query.columns) {
116
- placeholderColumns[sqlName(column)] = {
117
- name: sqlName(column),
118
- databaseType: "unknown",
119
- tsType: "unknown",
120
- nullable: true,
121
- };
122
- }
123
- ctes.set(key, { name: key, columns: placeholderColumns });
105
+ const selfReferences = query.statement.kind === "select" ? this.#selfReferences(query.statement, query.name) : 0;
106
+ if ((query.search !== undefined || query.cycle !== undefined) &&
107
+ (!withClause.recursive || selfReferences === 0)) {
108
+ this.#diagnostic("TSQ220", `SEARCH and CYCLE require a recursive CTE ${query.name.name}`, query.range);
124
109
  }
125
- const resolved = this.#resolveStatement(query.statement, outer, ctes);
110
+ if (selfReferences > 0 && !withClause.recursive) {
111
+ this.#diagnostic("TSQ220", `Recursive CTE ${query.name.name} requires WITH RECURSIVE`, query.range);
112
+ }
113
+ const resolved = selfReferences > 0 && query.statement.kind === "select"
114
+ ? this.#resolveRecursiveCte(query, outer, ctes)
115
+ : this.#resolveStatement(query.statement, outer, ctes);
126
116
  if (resolved.resultKind === "command") {
127
117
  this.#diagnostic("TSQ212", `CTE ${query.name.name} does not return rows`, query.range, "Add RETURNING to the data-changing CTE.");
128
118
  }
@@ -140,15 +130,151 @@ class Resolver {
140
130
  nullable: column.nullable,
141
131
  };
142
132
  });
133
+ const addGenerated = (identifier, column) => {
134
+ const generatedName = sqlName(identifier);
135
+ if (columns[generatedName] !== undefined) {
136
+ this.#diagnostic("TSQ105", `CTE generated column ${identifier.name} conflicts with an output column`, identifier.range);
137
+ return;
138
+ }
139
+ columns[generatedName] = column;
140
+ };
141
+ if (query.search !== undefined) {
142
+ for (const identifier of query.search.by) {
143
+ if (columns[sqlName(identifier)] === undefined)
144
+ this.#diagnostic("TSQ101", `Unknown SEARCH column ${identifier.name}`, identifier.range);
145
+ }
146
+ addGenerated(query.search.set, {
147
+ name: sqlName(query.search.set),
148
+ databaseType: query.search.order === "depth" ? "record[]" : "record",
149
+ tsType: "unknown",
150
+ nullable: false,
151
+ });
152
+ }
153
+ if (query.cycle !== undefined) {
154
+ for (const identifier of query.cycle.columns) {
155
+ if (columns[sqlName(identifier)] === undefined)
156
+ this.#diagnostic("TSQ101", `Unknown CYCLE column ${identifier.name}`, identifier.range);
157
+ }
158
+ const markScope = {
159
+ relations: [],
160
+ usingColumns: new Map(),
161
+ windows: new Map(),
162
+ ...(outer === undefined ? {} : { outer }),
163
+ };
164
+ const markValue = query.cycle.markValue === undefined
165
+ ? this.#databaseType("boolean", false)
166
+ : this.#resolveExpression(query.cycle.markValue, markScope, ctes);
167
+ const markDefault = query.cycle.markDefault === undefined
168
+ ? this.#databaseType("boolean", false)
169
+ : this.#resolveExpression(query.cycle.markDefault, markScope, ctes, markValue);
170
+ for (const mark of [query.cycle.markValue, query.cycle.markDefault]) {
171
+ if (mark !== undefined &&
172
+ mark.kind !== "literal" &&
173
+ !(mark.kind === "cast" && mark.expression.kind === "literal")) {
174
+ this.#diagnostic("TSQ220", "CYCLE mark values must be constants", mark.range);
175
+ }
176
+ }
177
+ addGenerated(query.cycle.set, {
178
+ name: sqlName(query.cycle.set),
179
+ databaseType: markValue.databaseType === markDefault.databaseType && markValue.databaseType !== undefined
180
+ ? markValue.databaseType
181
+ : "unknown",
182
+ tsType: mergeTypeLiterals(markValue.tsType, markDefault.tsType),
183
+ nullable: markValue.nullable || markDefault.nullable,
184
+ });
185
+ addGenerated(query.cycle.using, {
186
+ name: sqlName(query.cycle.using),
187
+ databaseType: "record[]",
188
+ tsType: "unknown",
189
+ nullable: false,
190
+ });
191
+ }
143
192
  ctes.set(key, { name: key, columns });
144
193
  }
145
194
  return ctes;
146
195
  }
147
- #resolveSelect(statement, outer, ctes) {
148
- if (statement.compounds.length > 0) {
149
- this.#diagnostic("TSQ401", "PostgreSQL set-operation inference is not supported safely", statement.compounds[0].range);
196
+ #resolveRecursiveCte(query, outer, inherited) {
197
+ const arms = this.#selectArms(query.statement);
198
+ const references = arms.map(({ statement }) => ({
199
+ direct: this.#directSelfReferences(statement, query.name),
200
+ total: this.#selfReferences(statement, query.name),
201
+ }));
202
+ const firstRecursive = references.findIndex(({ total }) => total > 0);
203
+ if (firstRecursive <= 0) {
204
+ this.#diagnostic("TSQ220", `Recursive CTE ${query.name.name} requires a non-recursive SELECT before its recursive member`, query.range);
205
+ }
206
+ for (let index = Math.max(firstRecursive, 0); index < arms.length; index += 1) {
207
+ const arm = arms[index];
208
+ const reference = references[index];
209
+ if (reference.total === 0) {
210
+ this.#diagnostic("TSQ220", `Non-recursive members of ${query.name.name} must precede recursive members`, arm.range);
211
+ }
212
+ else if (reference.direct !== 1 || reference.total !== 1) {
213
+ this.#diagnostic("TSQ220", `Each recursive member of ${query.name.name} must reference itself exactly once in its top-level FROM`, arm.statement.range);
214
+ }
215
+ if (arm.operator !== "union") {
216
+ this.#diagnostic("TSQ220", `Recursive members of ${query.name.name} must use UNION or UNION ALL`, arm.range);
217
+ }
218
+ }
219
+ const seedStatements = arms.slice(0, Math.max(firstRecursive, 1)).map(({ statement }) => statement);
220
+ const seedColumns = seedStatements.map((statement) => this.#resolveSelect(statement, outer, inherited));
221
+ const mergedSeed = [...(seedColumns[0] ?? [])];
222
+ for (const columns of seedColumns.slice(1))
223
+ this.#mergeCompoundColumns(mergedSeed, columns, query.range);
224
+ const provisional = {};
225
+ const width = Math.max(query.columns.length, mergedSeed.length);
226
+ for (let index = 0; index < width; index += 1) {
227
+ const seed = mergedSeed[index];
228
+ const name = query.columns[index] === undefined ? seed?.name : sqlName(query.columns[index]);
229
+ if (name === undefined)
230
+ continue;
231
+ provisional[name] = {
232
+ name,
233
+ databaseType: seed?.databaseType ?? "unknown",
234
+ tsType: seed?.tsType ?? "unknown",
235
+ nullable: seed?.nullable ?? true,
236
+ };
237
+ }
238
+ const recursiveScope = new Map(inherited);
239
+ recursiveScope.set(sqlName(query.name), { name: sqlName(query.name), columns: provisional });
240
+ return { columns: this.#resolveSelect(query.statement, outer, recursiveScope), resultKind: "rows" };
241
+ }
242
+ #selectArms(statement) {
243
+ const { compounds, ...simple } = statement;
244
+ const arms = [{ statement: { ...simple, compounds: [] }, range: statement.range }];
245
+ for (const compound of compounds) {
246
+ const [first, ...rest] = this.#selectArms(compound.statement);
247
+ if (first !== undefined)
248
+ arms.push({ ...first, operator: compound.operator, all: compound.all, range: compound.range });
249
+ arms.push(...rest);
150
250
  }
151
- const scope = { relations: [], usingColumns: new Map(), ...(outer === undefined ? {} : { outer }) };
251
+ return arms;
252
+ }
253
+ #directSelfReferences(statement, identifier) {
254
+ return [statement.from, ...statement.joins.map(({ table }) => table)].filter((reference) => reference?.kind === "table" &&
255
+ reference.schema === undefined &&
256
+ sqlName(reference.name) === sqlName(identifier)).length;
257
+ }
258
+ #selfReferences(statement, identifier) {
259
+ let references = 0;
260
+ walkStatement(statement, {
261
+ table(reference) {
262
+ if (reference.kind === "table" &&
263
+ reference.schema === undefined &&
264
+ sqlName(reference.name) === sqlName(identifier)) {
265
+ references += 1;
266
+ }
267
+ },
268
+ });
269
+ return references;
270
+ }
271
+ #resolveSelect(statement, outer, ctes, expectedOutput) {
272
+ const scope = {
273
+ relations: [],
274
+ usingColumns: new Map(),
275
+ windows: new Map(),
276
+ ...(outer === undefined ? {} : { outer }),
277
+ };
152
278
  if (statement.from !== undefined)
153
279
  this.#addRelation(statement.from, false, scope, ctes);
154
280
  for (const join of statement.joins)
@@ -160,85 +286,896 @@ class Resolver {
160
286
  }
161
287
  }
162
288
  }
289
+ for (const window of statement.windows) {
290
+ const name = sqlName(window.name);
291
+ if (scope.windows.has(name)) {
292
+ this.#diagnostic("TSQ222", `Duplicate window ${window.name.name}`, window.name.range);
293
+ continue;
294
+ }
295
+ const resolved = this.#resolveWindowSpecification(window.specification, scope, ctes);
296
+ scope.windows.set(name, resolved);
297
+ }
163
298
  if (statement.where !== undefined)
164
299
  this.#resolveExpression(statement.where, scope, ctes, this.#databaseType("boolean", false));
165
- for (const expression of statement.groupBy)
166
- this.#resolveExpression(expression, scope, ctes);
300
+ for (const grouping of statement.groupBy)
301
+ this.#resolveGrouping(grouping, statement.columns, scope, ctes);
167
302
  if (statement.having !== undefined)
168
303
  this.#resolveExpression(statement.having, scope, ctes, this.#databaseType("boolean", false));
169
304
  for (const expression of statement.distinctOn)
170
305
  this.#resolveExpression(expression, scope, ctes);
171
- for (const window of statement.windows) {
172
- for (const expression of window.specification.partitionBy)
173
- this.#resolveExpression(expression, scope, ctes);
174
- for (const order of window.specification.orderBy)
175
- this.#resolveExpression(order.expression, scope, ctes);
306
+ for (const order of statement.orderBy) {
307
+ const output = this.#outputReference(order.expression, statement.columns, scope, true);
308
+ this.#resolveExpression(output ?? order.expression, scope, ctes);
309
+ }
310
+ if (statement.distinctOn.length > 0 && statement.orderBy.length > 0) {
311
+ const distinct = new Set(statement.distinctOn.map((expression) => this.#groupKey(expression, scope)));
312
+ const leading = statement.orderBy
313
+ .slice(0, statement.distinctOn.length)
314
+ .map(({ expression }) => this.#outputReference(expression, statement.columns, scope, true) ?? expression)
315
+ .map((expression) => this.#groupKey(expression, scope));
316
+ if (leading.length !== distinct.size || leading.some((key) => !distinct.has(key))) {
317
+ this.#diagnostic("TSQ228", "DISTINCT ON expressions must match the leftmost ORDER BY expressions", statement.orderBy[0].range);
318
+ }
176
319
  }
177
- for (const order of statement.orderBy)
178
- this.#resolveExpression(order.expression, scope, ctes);
179
320
  if (statement.limit !== undefined)
180
321
  this.#resolveExpression(statement.limit, scope, ctes, this.#databaseType("integer", false));
181
322
  if (statement.offset !== undefined)
182
323
  this.#resolveExpression(statement.offset, scope, ctes, this.#databaseType("integer", false));
183
- return this.#resolveItems(statement.columns, scope, ctes);
324
+ if (statement.fetch?.count !== undefined)
325
+ this.#resolveExpression(statement.fetch.count, scope, ctes, this.#databaseType("integer", false));
326
+ const columns = [...this.#resolveItems(statement.columns, scope, ctes, expectedOutput)];
327
+ this.#validateGroupedQuery(statement, columns, scope);
328
+ for (const compound of statement.compounds) {
329
+ const right = this.#resolveSelect(compound.statement, outer, ctes, expectedOutput);
330
+ this.#mergeCompoundColumns(columns, right, compound.range);
331
+ }
332
+ return columns;
333
+ }
334
+ #resolveGrouping(grouping, items, scope, ctes) {
335
+ if (!isSpecialGrouping(grouping)) {
336
+ const output = this.#outputReference(grouping, items, scope, false);
337
+ this.#resolveExpression(output ?? grouping, scope, ctes);
338
+ return;
339
+ }
340
+ if (grouping.kind !== "empty-group") {
341
+ for (const element of grouping.elements)
342
+ this.#resolveGrouping(element, items, scope, ctes);
343
+ }
344
+ }
345
+ #outputReference(expression, items, scope, preferOutput) {
346
+ if (expression.kind === "literal" &&
347
+ typeof expression.value === "number" &&
348
+ Number.isSafeInteger(expression.value) &&
349
+ expression.value >= 1) {
350
+ return items[expression.value - 1]?.expression;
351
+ }
352
+ if (expression.kind !== "column" || expression.relation !== undefined)
353
+ return undefined;
354
+ const name = sqlName(expression.column);
355
+ if (!preferOutput && this.#columnMatches(undefined, name, scope, expression.column.quoted).length > 0)
356
+ return undefined;
357
+ const matches = items.filter((item) => item.alias !== undefined && sqlName(item.alias) === name);
358
+ if (matches.length > 1) {
359
+ this.#diagnostic("TSQ105", `Output reference ${expression.column.name} is ambiguous`, expression.range);
360
+ return undefined;
361
+ }
362
+ return matches[0]?.expression;
363
+ }
364
+ #validateGroupedQuery(statement, columns, scope) {
365
+ if (statement.where !== undefined)
366
+ this.#validateClauseFunctions(statement.where, false, false, "WHERE");
367
+ for (const grouping of statement.groupBy) {
368
+ this.#forEachGroupingExpression(grouping, (expression) => this.#validateClauseFunctions(expression, false, false, "GROUP BY"));
369
+ }
370
+ if (statement.having !== undefined)
371
+ this.#validateClauseFunctions(statement.having, true, false, "HAVING");
372
+ const aggregateQuery = statement.groupBy.length > 0 ||
373
+ statement.having !== undefined ||
374
+ statement.columns.some(({ expression }) => this.#containsAggregate(expression));
375
+ if (!aggregateQuery)
376
+ return;
377
+ if (statement.locking.length > 0) {
378
+ this.#diagnostic("TSQ228", "Locking clauses are not allowed on grouped queries", statement.locking[0].range);
379
+ }
380
+ const groupingSets = this.#expandGroupingList(statement.groupBy, statement.columns, scope);
381
+ const groupedKeys = new Set(groupingSets.flatMap((set) => [...set]));
382
+ for (const item of statement.columns)
383
+ this.#validateGroupedExpression(item.expression, groupedKeys, groupingSets, scope);
384
+ if (statement.having !== undefined) {
385
+ this.#validateGroupedExpression(statement.having, groupedKeys, groupingSets, scope);
386
+ }
387
+ statement.columns.forEach((item, index) => {
388
+ const column = columns[index];
389
+ if (column === undefined)
390
+ return;
391
+ const keys = this.#columnKeysOutsideAggregates(item.expression, scope);
392
+ if (keys.some((key) => groupingSets.some((set) => !set.has(key)))) {
393
+ columns[index] = { ...column, nullable: true };
394
+ }
395
+ });
396
+ }
397
+ #validateClauseFunctions(expression, allowAggregate, allowWindow, clause) {
398
+ if (expression.kind === "subquery" || expression.kind === "exists")
399
+ return;
400
+ if (expression.kind === "call") {
401
+ const window = expression.over !== undefined;
402
+ const aggregate = !window && this.#isAggregateCall(expression);
403
+ if (window && !allowWindow) {
404
+ this.#diagnostic("TSQ228", `Window functions are not allowed in ${clause}`, expression.range);
405
+ return;
406
+ }
407
+ if (aggregate && !allowAggregate) {
408
+ this.#diagnostic("TSQ228", `Aggregate functions are not allowed in ${clause}`, expression.range);
409
+ return;
410
+ }
411
+ }
412
+ if (expression.kind === "json-object-aggregate" || expression.kind === "json-array-aggregate") {
413
+ const window = expression.over !== undefined;
414
+ if (window && !allowWindow) {
415
+ this.#diagnostic("TSQ228", `Window functions are not allowed in ${clause}`, expression.range);
416
+ return;
417
+ }
418
+ if (!window && !allowAggregate) {
419
+ this.#diagnostic("TSQ228", `Aggregate functions are not allowed in ${clause}`, expression.range);
420
+ return;
421
+ }
422
+ }
423
+ for (const child of this.#expressionChildren(expression)) {
424
+ this.#validateClauseFunctions(child, allowAggregate, allowWindow, clause);
425
+ }
426
+ }
427
+ #validateGroupedExpression(expression, groupedKeys, groupingSets, scope) {
428
+ if (groupedKeys.has(this.#groupKey(expression, scope)))
429
+ return;
430
+ if (expression.kind === "subquery" || expression.kind === "exists")
431
+ return;
432
+ if (expression.kind === "call" && expression.over === undefined && this.#isAggregateCall(expression))
433
+ return;
434
+ if ((expression.kind === "json-object-aggregate" || expression.kind === "json-array-aggregate") &&
435
+ expression.over === undefined) {
436
+ return;
437
+ }
438
+ if (expression.kind === "column") {
439
+ const matches = this.#columnMatches(expression.relation, sqlName(expression.column), scope, expression.column.quoted);
440
+ const match = matches.length === 1 ? matches[0] : undefined;
441
+ if (match !== undefined && this.#functionallyDependent(match[0], groupingSets))
442
+ return;
443
+ this.#diagnostic("TSQ228", `Column ${expression.column.name} must appear in GROUP BY or be used in an aggregate`, expression.range);
444
+ return;
445
+ }
446
+ for (const child of this.#expressionChildren(expression)) {
447
+ this.#validateGroupedExpression(child, groupedKeys, groupingSets, scope);
448
+ }
449
+ }
450
+ #functionallyDependent(relation, groupingSets) {
451
+ const primaryKey = this.#index
452
+ .relation(relation.table)
453
+ ?.constraints.find(({ kind }) => kind === "primary-key")
454
+ ?.columns.map((column) => `column:${relation.alias}.${column.toLowerCase()}`);
455
+ return (primaryKey !== undefined &&
456
+ primaryKey.length > 0 &&
457
+ groupingSets.every((set) => primaryKey.every((key) => set.has(key))));
458
+ }
459
+ #containsAggregate(expression) {
460
+ if (expression.kind === "subquery" || expression.kind === "exists")
461
+ return false;
462
+ if (expression.kind === "call" && expression.over === undefined && this.#isAggregateCall(expression))
463
+ return true;
464
+ if ((expression.kind === "json-object-aggregate" || expression.kind === "json-array-aggregate") &&
465
+ expression.over === undefined) {
466
+ return true;
467
+ }
468
+ return this.#expressionChildren(expression).some((child) => this.#containsAggregate(child));
469
+ }
470
+ #isAggregateCall(expression) {
471
+ const name = expression.name.name.toUpperCase();
472
+ const rule = postgresCatalogRoutineRule(name, this.#schema);
473
+ if (rule !== undefined && aggregateRoutineRules.has(rule))
474
+ return true;
475
+ if (aggregateExtrema.has(name))
476
+ return true;
477
+ if ((expression.withinGroup?.length ?? 0) > 0 && hypotheticalAggregates.has(name))
478
+ return true;
479
+ const schemaName = expression.schema === undefined ? undefined : sqlName(expression.schema);
480
+ return this.#index
481
+ .routineOverloads(sqlName(expression.name), expression.arguments.length, schemaName)
482
+ .some(({ kind }) => kind === "aggregate");
483
+ }
484
+ #expressionChildren(expression) {
485
+ switch (expression.kind) {
486
+ case "array":
487
+ case "row":
488
+ return expression.elements;
489
+ case "call": {
490
+ const window = expression.over !== undefined && "partitionBy" in expression.over
491
+ ? [
492
+ ...expression.over.partitionBy,
493
+ ...expression.over.orderBy.map(({ expression: item }) => item),
494
+ ...(expression.over.frame?.start.kind === "preceding" ||
495
+ expression.over.frame?.start.kind === "following"
496
+ ? [expression.over.frame.start.expression]
497
+ : []),
498
+ ...(expression.over.frame?.end?.kind === "preceding" || expression.over.frame?.end?.kind === "following"
499
+ ? [expression.over.frame.end.expression]
500
+ : []),
501
+ ]
502
+ : [];
503
+ return [
504
+ ...expression.arguments,
505
+ ...(expression.orderBy ?? []).map(({ expression: item }) => item),
506
+ ...(expression.withinGroup ?? []).map(({ expression: item }) => item),
507
+ ...(expression.filter === undefined ? [] : [expression.filter]),
508
+ ...window,
509
+ ];
510
+ }
511
+ case "cast":
512
+ case "unary":
513
+ return [expression.expression];
514
+ case "subscript":
515
+ return [
516
+ expression.expression,
517
+ ...(expression.index === undefined ? [] : [expression.index]),
518
+ ...(expression.lower === undefined ? [] : [expression.lower]),
519
+ ...(expression.upper === undefined ? [] : [expression.upper]),
520
+ ];
521
+ case "field-access":
522
+ return [expression.expression];
523
+ case "collate":
524
+ return [expression.expression];
525
+ case "at-time-zone":
526
+ return [expression.expression, ...(expression.zone === undefined ? [] : [expression.zone])];
527
+ case "json-object":
528
+ return expression.entries.flatMap(({ key, value }) => [key, value.expression]);
529
+ case "json-array":
530
+ return expression.values.map(({ expression: value }) => value);
531
+ case "json-object-aggregate":
532
+ case "json-array-aggregate": {
533
+ const window = expression.over !== undefined && "partitionBy" in expression.over
534
+ ? [
535
+ ...expression.over.partitionBy,
536
+ ...expression.over.orderBy.map(({ expression: item }) => item),
537
+ ...(expression.over.frame?.start.kind === "preceding" ||
538
+ expression.over.frame?.start.kind === "following"
539
+ ? [expression.over.frame.start.expression]
540
+ : []),
541
+ ...(expression.over.frame?.end?.kind === "preceding" || expression.over.frame?.end?.kind === "following"
542
+ ? [expression.over.frame.end.expression]
543
+ : []),
544
+ ]
545
+ : [];
546
+ return [
547
+ ...(expression.kind === "json-object-aggregate" ? [expression.key] : []),
548
+ expression.value.expression,
549
+ ...(expression.kind === "json-array-aggregate" ? expression.orderBy.map(({ expression: item }) => item) : []),
550
+ ...(expression.filter === undefined ? [] : [expression.filter]),
551
+ ...window,
552
+ ];
553
+ }
554
+ case "json-parse":
555
+ return [expression.value.expression];
556
+ case "json-scalar":
557
+ return [expression.expression];
558
+ case "json-serialize":
559
+ return [expression.value.expression];
560
+ case "json-is":
561
+ return [expression.expression];
562
+ case "json-exists":
563
+ return [
564
+ expression.context.expression,
565
+ expression.path,
566
+ ...expression.passing.map(({ value }) => value.expression),
567
+ ];
568
+ case "json-query":
569
+ return [
570
+ expression.context.expression,
571
+ expression.path,
572
+ ...expression.passing.map(({ value }) => value.expression),
573
+ ...(expression.onEmpty?.expression === undefined ? [] : [expression.onEmpty.expression]),
574
+ ...(expression.onError?.expression === undefined ? [] : [expression.onError.expression]),
575
+ ];
576
+ case "json-value":
577
+ return [
578
+ expression.context.expression,
579
+ expression.path,
580
+ ...expression.passing.map(({ value }) => value.expression),
581
+ ...(expression.onEmpty?.expression === undefined ? [] : [expression.onEmpty.expression]),
582
+ ...(expression.onError?.expression === undefined ? [] : [expression.onError.expression]),
583
+ ];
584
+ case "binary":
585
+ return [expression.left, expression.right];
586
+ case "case":
587
+ return [
588
+ ...(expression.operand === undefined ? [] : [expression.operand]),
589
+ ...expression.branches.flatMap(({ when, then }) => [when, then]),
590
+ ...(expression.elseExpression === undefined ? [] : [expression.elseExpression]),
591
+ ];
592
+ case "in":
593
+ return [
594
+ expression.expression,
595
+ ...(Array.isArray(expression.values) ? expression.values : []),
596
+ ];
597
+ case "between":
598
+ return [expression.expression, expression.lower, expression.upper];
599
+ case "quantified-comparison":
600
+ return [expression.left, ...(expression.right.kind === "select" ? [] : [expression.right])];
601
+ case "column":
602
+ case "exists":
603
+ case "literal":
604
+ case "parameter":
605
+ case "star":
606
+ case "subquery":
607
+ return [];
608
+ }
609
+ }
610
+ #columnKeysOutsideAggregates(expression, scope) {
611
+ if (expression.kind === "subquery" || expression.kind === "exists")
612
+ return [];
613
+ if (expression.kind === "call" && expression.over === undefined && this.#isAggregateCall(expression))
614
+ return [];
615
+ if ((expression.kind === "json-object-aggregate" || expression.kind === "json-array-aggregate") &&
616
+ expression.over === undefined) {
617
+ return [];
618
+ }
619
+ if (expression.kind === "call" && postgresCatalogRoutineRule(expression.name.name, this.#schema) === "grouping") {
620
+ return [];
621
+ }
622
+ if (expression.kind === "column")
623
+ return [this.#groupKey(expression, scope)];
624
+ return this.#expressionChildren(expression).flatMap((child) => this.#columnKeysOutsideAggregates(child, scope));
625
+ }
626
+ #groupKey(expression, scope) {
627
+ if (expression.kind === "column") {
628
+ const matches = this.#columnMatches(expression.relation, sqlName(expression.column), scope, expression.column.quoted);
629
+ if (matches.length === 1)
630
+ return `column:${matches[0][0].alias}.${matches[0][1].name.toLowerCase()}`;
631
+ }
632
+ return `expression:${postgresExpressionIdentity(expression)}`;
633
+ }
634
+ #forEachGroupingExpression(grouping, visit) {
635
+ if (!isSpecialGrouping(grouping))
636
+ visit(grouping);
637
+ else if (grouping.kind !== "empty-group")
638
+ for (const element of grouping.elements)
639
+ this.#forEachGroupingExpression(element, visit);
640
+ }
641
+ #expandGroupingList(groupings, items, scope) {
642
+ let result = [new Set()];
643
+ for (const grouping of groupings) {
644
+ const alternatives = this.#expandGrouping(grouping, items, scope);
645
+ result = result.flatMap((left) => alternatives.map((right) => new Set([...left, ...right])));
646
+ }
647
+ return result;
648
+ }
649
+ #expandGrouping(grouping, items, scope) {
650
+ if (grouping.kind === "empty-group")
651
+ return [new Set()];
652
+ if (!isSpecialGrouping(grouping)) {
653
+ const expression = this.#outputReference(grouping, items, scope, false) ?? grouping;
654
+ return [new Set([this.#groupKey(expression, scope)])];
655
+ }
656
+ if (grouping.kind === "grouping-sets") {
657
+ return grouping.elements.flatMap((element) => this.#expandGrouping(element, items, scope));
658
+ }
659
+ const units = grouping.elements.map((element) => {
660
+ const expanded = this.#expandGrouping(element, items, scope);
661
+ return new Set(expanded.flatMap((set) => [...set]));
662
+ });
663
+ if (grouping.kind === "grouping-set") {
664
+ return [new Set(units.flatMap((set) => [...set]))];
665
+ }
666
+ if (grouping.kind === "rollup") {
667
+ return Array.from({ length: units.length + 1 }, (_, omitted) => new Set(units.slice(0, units.length - omitted).flatMap((set) => [...set])));
668
+ }
669
+ return Array.from({ length: 2 ** units.length }, (_, mask) => new Set(units.flatMap((set, index) => ((mask & (1 << index)) === 0 ? [] : [...set]))));
670
+ }
671
+ #resolveWindowSpecification(specification, scope, ctes) {
672
+ const base = specification.base === undefined ? undefined : scope.windows.get(sqlName(specification.base));
673
+ if (specification.base !== undefined && base === undefined) {
674
+ this.#diagnostic("TSQ222", `Unknown window ${specification.base.name}`, specification.base.range);
675
+ }
676
+ if (base !== undefined && specification.partitionBy.length > 0) {
677
+ this.#diagnostic("TSQ222", "A derived window cannot override PARTITION BY", specification.range);
678
+ }
679
+ if (base !== undefined && base.orderBy.length > 0 && specification.orderBy.length > 0) {
680
+ this.#diagnostic("TSQ222", "A derived window cannot override ORDER BY", specification.range);
681
+ }
682
+ if (base?.frame !== undefined) {
683
+ this.#diagnostic("TSQ222", "A framed window cannot be inherited", specification.range);
684
+ }
685
+ const partitionBy = specification.partitionBy.length > 0 ? specification.partitionBy : (base?.partitionBy ?? []);
686
+ const orderBy = specification.orderBy.length > 0 ? specification.orderBy : (base?.orderBy ?? []);
687
+ for (const expression of specification.partitionBy)
688
+ this.#resolveExpression(expression, scope, ctes);
689
+ for (const order of specification.orderBy)
690
+ this.#resolveExpression(order.expression, scope, ctes);
691
+ if (specification.frame !== undefined) {
692
+ const { frame } = specification;
693
+ if (frame.start.kind === "unbounded-following") {
694
+ this.#diagnostic("TSQ222", "A window frame cannot start with UNBOUNDED FOLLOWING", frame.start.range);
695
+ }
696
+ if (frame.end?.kind === "unbounded-preceding") {
697
+ this.#diagnostic("TSQ222", "A window frame cannot end with UNBOUNDED PRECEDING", frame.end.range);
698
+ }
699
+ const boundRank = (bound) => {
700
+ if (bound.kind === "unbounded-preceding")
701
+ return 0;
702
+ if (bound.kind === "preceding")
703
+ return 1;
704
+ if (bound.kind === "current-row")
705
+ return 2;
706
+ if (bound.kind === "following")
707
+ return 3;
708
+ return 4;
709
+ };
710
+ if (frame.end !== undefined && boundRank(frame.start) > boundRank(frame.end)) {
711
+ this.#diagnostic("TSQ222", "A window frame end cannot precede its start", frame.range);
712
+ }
713
+ const offsetBounds = [frame.start, frame.end].filter((bound) => bound?.kind === "preceding" || bound?.kind === "following");
714
+ if (frame.unit === "range" && offsetBounds.length > 0 && orderBy.length !== 1) {
715
+ this.#diagnostic("TSQ222", "RANGE offset frames require exactly one ORDER BY expression", frame.range);
716
+ }
717
+ for (const bound of offsetBounds) {
718
+ const orderType = frame.unit === "range" && orderBy.length === 1
719
+ ? this.#resolveExpression(orderBy[0].expression, scope, ctes)
720
+ : undefined;
721
+ const expected = frame.unit === "range"
722
+ ? orderType?.databaseType !== undefined && this.#isNumericType(orderType.databaseType)
723
+ ? orderType
724
+ : undefined
725
+ : this.#databaseType("integer", false);
726
+ this.#resolveExpression(bound.expression, scope, ctes, expected);
727
+ }
728
+ }
729
+ return {
730
+ ...specification,
731
+ partitionBy,
732
+ orderBy,
733
+ };
734
+ }
735
+ #mergeCompoundColumns(columns, right, range) {
736
+ if (right.length !== columns.length) {
737
+ this.#diagnostic("TSQ214", `Compound query has ${columns.length} columns on the left and ${right.length} on the right`, range);
738
+ return;
739
+ }
740
+ for (let index = 0; index < columns.length; index += 1) {
741
+ const left = columns[index];
742
+ const candidate = right[index];
743
+ const { databaseType: _databaseType, ...base } = left;
744
+ columns[index] = {
745
+ ...base,
746
+ tsType: mergeTypeLiterals(left.tsType, candidate.tsType),
747
+ nullable: left.nullable || candidate.nullable,
748
+ ...(left.databaseType === candidate.databaseType && left.databaseType !== undefined
749
+ ? { databaseType: left.databaseType }
750
+ : {}),
751
+ };
752
+ }
184
753
  }
185
754
  #resolveInsert(statement, outer, ctes) {
186
- const scope = { relations: [], usingColumns: new Map(), ...(outer === undefined ? {} : { outer }) };
755
+ const scope = {
756
+ relations: [],
757
+ usingColumns: new Map(),
758
+ windows: new Map(),
759
+ ...(outer === undefined ? {} : { outer }),
760
+ };
187
761
  const target = this.#addRelation(statement.table, false, scope, ctes);
188
- const targetColumns = statement.columns.length === 0
189
- ? Object.values(target?.table.columns ?? {})
190
- : statement.columns.map((column) => this.#findColumn(target?.table, column));
762
+ const allTargetColumns = Object.values(target?.table.columns ?? {});
763
+ let targetColumns = statement.columns.length > 0
764
+ ? statement.columns.map((column) => this.#findColumn(target?.table, column))
765
+ : statement.source.kind === "values"
766
+ ? allTargetColumns.slice(0, statement.source.rows[0]?.length ?? 0)
767
+ : statement.source.kind === "default-values"
768
+ ? []
769
+ : allTargetColumns;
770
+ const insertNames = statement.columns.map(sqlName);
771
+ if (new Set(insertNames).size !== insertNames.length)
772
+ this.#diagnostic("TSQ227", "INSERT cannot target the same column more than once", statement.table.range);
191
773
  if (statement.source.kind === "values") {
192
774
  for (const row of statement.source.rows) {
193
775
  if (row.length !== targetColumns.length) {
194
776
  this.#diagnostic("TSQ214", `INSERT has ${targetColumns.length} target columns but ${row.length} values`, statement.source.range);
195
777
  }
196
778
  row.forEach((value, index) => {
197
- this.#resolveExpression(value, scope, ctes, this.#snapshotType(targetColumns[index]));
779
+ const resolved = this.#resolveExpression(value, scope, ctes, this.#snapshotType(targetColumns[index]));
780
+ this.#validateWriteValue(targetColumns[index], resolved, value.range, "INSERT");
198
781
  });
199
782
  }
200
783
  }
201
784
  else if (statement.source.kind === "select") {
202
- const source = this.#resolveStatement(statement.source, outer, ctes);
203
- if (source.columns.length !== targetColumns.length) {
785
+ const source = this.#resolveStatement(statement.source, outer, ctes, targetColumns.map((column) => this.#snapshotType(column)));
786
+ if (statement.columns.length === 0)
787
+ targetColumns = allTargetColumns.slice(0, source.columns.length);
788
+ if ((statement.columns.length > 0 && source.columns.length !== targetColumns.length) ||
789
+ source.columns.length > allTargetColumns.length) {
204
790
  this.#diagnostic("TSQ214", `INSERT has ${targetColumns.length} target columns but SELECT returns ${source.columns.length}`, statement.source.range);
205
791
  }
792
+ this.#validateWriteColumns(targetColumns, source.columns, statement.source.range, "INSERT");
793
+ }
794
+ if (target !== undefined) {
795
+ targetColumns.forEach((column, index) => {
796
+ if (column === undefined)
797
+ return;
798
+ const evidence = this.#index.columnEvidence(target.table, column);
799
+ const identityOverride = statement.overriding !== undefined && evidence?.identity !== "none";
800
+ const defaultOnly = statement.source.kind === "default-values" ||
801
+ (statement.source.kind === "values" &&
802
+ statement.source.rows.every((row) => this.#isDefaultValue(row[index])));
803
+ if (this.#index.columnEligibility(target.table, column, "insert") === false &&
804
+ !identityOverride &&
805
+ !defaultOnly) {
806
+ this.#diagnostic("TSQ218", `Cannot INSERT into non-insertable column ${column.name}`, statement.columns[index]?.range ?? statement.source.range);
807
+ }
808
+ });
809
+ const supplied = new Set(targetColumns.flatMap((column, index) => {
810
+ if (column === undefined || statement.source.kind === "default-values")
811
+ return [];
812
+ if (statement.source.kind === "values" &&
813
+ statement.source.rows.every((row) => this.#isDefaultValue(row[index])))
814
+ return [];
815
+ return [column.name.toLowerCase()];
816
+ }));
817
+ const required = this.#index.requiredInsertColumns(target.table);
818
+ if (required !== "unknown") {
819
+ for (const column of required) {
820
+ if (!supplied.has(column.name.toLowerCase())) {
821
+ this.#diagnostic("TSQ219", `INSERT omits required column ${column.name}`, statement.table.range);
822
+ }
823
+ }
824
+ }
825
+ }
826
+ if (statement.conflict !== undefined && target !== undefined) {
827
+ const conflict = statement.conflict;
828
+ if (conflict.action.kind === "update" && conflict.target === undefined) {
829
+ this.#diagnostic("TSQ227", "ON CONFLICT DO UPDATE requires a conflict target", conflict.range);
830
+ }
831
+ if (conflict.target?.kind === "constraint") {
832
+ const targetConstraint = conflict.target;
833
+ const requested = sqlName(targetConstraint.constraint);
834
+ const relation = this.#index.relation(target.table);
835
+ const match = relation?.constraints.find((constraint) => {
836
+ if (constraint.kind !== "primary-key" && constraint.kind !== "unique" && constraint.kind !== "exclusion")
837
+ return false;
838
+ const name = constraint.identity.slice(constraint.identity.lastIndexOf(".") + 1);
839
+ return targetConstraint.constraint.quoted ? name === requested : name.toLowerCase() === requested;
840
+ });
841
+ if (relation !== undefined && match === undefined) {
842
+ this.#diagnostic("TSQ226", `Unknown conflict constraint ${requested}`, conflict.target.range);
843
+ }
844
+ else if (relation === undefined) {
845
+ this.#diagnostic("TSQ402", `Conflict constraint ${requested} requires schema snapshot v2 evidence`, conflict.target.range, undefined, "warning");
846
+ }
847
+ else if (match?.deferrable === true) {
848
+ this.#diagnostic("TSQ224", `Deferrable constraint ${requested} cannot arbitrate ON CONFLICT`, conflict.target.range);
849
+ }
850
+ else if (match !== undefined && (match.deferrable === "unknown" || match.deferrable === undefined)) {
851
+ this.#diagnostic("TSQ402", `Conflict constraint ${requested} has unknown deferrability`, conflict.target.range, undefined, "warning");
852
+ }
853
+ else if (match?.kind === "exclusion" && conflict.action.kind === "update") {
854
+ this.#diagnostic("TSQ224", "Exclusion constraints cannot arbitrate ON CONFLICT DO UPDATE", conflict.target.range);
855
+ }
856
+ }
857
+ else if (conflict.target?.kind === "inference") {
858
+ for (const element of conflict.target.elements) {
859
+ this.#resolveExpression(element.expression, scope, ctes);
860
+ }
861
+ if (conflict.target.predicate !== undefined)
862
+ this.#resolveExpression(conflict.target.predicate, scope, ctes, this.#databaseType("boolean", false));
863
+ const inference = this.#matchConflictInference(target.table, conflict.target);
864
+ if (inference === "unknown") {
865
+ this.#diagnostic("TSQ402", "Exact conflict-target inference requires canonical schema snapshot v2 evidence", conflict.target.range, undefined, "warning");
866
+ }
867
+ else if (!inference) {
868
+ this.#diagnostic("TSQ226", "Conflict target does not match a valid unique index or primary-key constraint", conflict.target.range);
869
+ }
870
+ }
871
+ if (conflict.action.kind === "update") {
872
+ const excludedAlias = "excluded";
873
+ if (scope.relations.some(({ alias }) => alias === excludedAlias))
874
+ this.#diagnostic("TSQ108", "INSERT target alias excluded conflicts with the ON CONFLICT excluded row", statement.table.range);
875
+ const conflictScope = {
876
+ ...scope,
877
+ relations: [
878
+ ...scope.relations.filter(({ alias }) => alias !== excludedAlias),
879
+ { alias: excludedAlias, table: target.table, nullable: false, qualifiedOnly: true },
880
+ ],
881
+ };
882
+ this.#resolveAssignments(conflict.action.assignments, target.table, conflictScope, ctes);
883
+ if (conflict.action.where !== undefined)
884
+ this.#resolveExpression(conflict.action.where, conflictScope, ctes, this.#databaseType("boolean", false));
885
+ }
206
886
  }
207
- const columns = this.#resolveItems(statement.returning, scope, ctes);
887
+ const returningScope = this.#returningScope(scope, target?.table, statement.returningAliases, statement.returning, "insert");
888
+ const columns = this.#resolveItems(statement.returning, returningScope, ctes);
208
889
  return { columns, resultKind: statement.returning.length === 0 ? "command" : "rows" };
209
890
  }
210
891
  #resolveUpdate(statement, outer, ctes) {
211
- const scope = { relations: [], usingColumns: new Map(), ...(outer === undefined ? {} : { outer }) };
892
+ const scope = {
893
+ relations: [],
894
+ usingColumns: new Map(),
895
+ windows: new Map(),
896
+ ...(outer === undefined ? {} : { outer }),
897
+ };
212
898
  const target = this.#addRelation(statement.table, false, scope, ctes);
213
- for (const assignment of statement.assignments) {
214
- const column = this.#findColumn(target?.table, assignment.column);
215
- this.#resolveExpression(assignment.value, scope, ctes, this.#snapshotType(column));
216
- }
217
899
  if (statement.from !== undefined)
218
900
  this.#addRelation(statement.from, false, scope, ctes);
219
901
  for (const join of statement.joins)
220
902
  this.#addJoin(join, scope, ctes);
903
+ this.#resolveAssignments(statement.assignments, target?.table, scope, ctes);
221
904
  if (statement.where !== undefined)
222
905
  this.#resolveExpression(statement.where, scope, ctes, this.#databaseType("boolean", false));
223
- const columns = this.#resolveItems(statement.returning, scope, ctes);
906
+ const columns = this.#resolveItems(statement.returning, this.#returningScope(scope, target?.table, statement.returningAliases, statement.returning, "update"), ctes);
224
907
  return { columns, resultKind: statement.returning.length === 0 ? "command" : "rows" };
225
908
  }
226
909
  #resolveDelete(statement, outer, ctes) {
227
- const scope = { relations: [], usingColumns: new Map(), ...(outer === undefined ? {} : { outer }) };
228
- this.#addRelation(statement.table, false, scope, ctes);
910
+ const scope = {
911
+ relations: [],
912
+ usingColumns: new Map(),
913
+ windows: new Map(),
914
+ ...(outer === undefined ? {} : { outer }),
915
+ };
916
+ const target = this.#addRelation(statement.table, false, scope, ctes);
229
917
  for (const reference of statement.using)
230
918
  this.#addRelation(reference, false, scope, ctes);
919
+ for (const join of statement.joins)
920
+ this.#addJoin(join, scope, ctes);
231
921
  if (statement.where !== undefined)
232
922
  this.#resolveExpression(statement.where, scope, ctes, this.#databaseType("boolean", false));
233
- const columns = this.#resolveItems(statement.returning, scope, ctes);
923
+ const columns = this.#resolveItems(statement.returning, this.#returningScope(scope, target?.table, statement.returningAliases, statement.returning, "delete"), ctes);
924
+ return { columns, resultKind: statement.returning.length === 0 ? "command" : "rows" };
925
+ }
926
+ #resolveMerge(statement, outer, ctes) {
927
+ this.#requireServerMajor(15, "MERGE", statement.range);
928
+ if (statement.with?.recursive)
929
+ this.#diagnostic("TSQ227", "MERGE does not support WITH RECURSIVE", statement.with.range);
930
+ const scope = {
931
+ relations: [],
932
+ usingColumns: new Map(),
933
+ windows: new Map(),
934
+ ...(outer === undefined ? {} : { outer }),
935
+ };
936
+ const target = this.#addRelation(statement.table, false, scope, ctes);
937
+ const targetAlias = target?.alias;
938
+ if (statement.source.kind === "values") {
939
+ const rows = statement.source.rows.map((row) => row.map((expression) => this.#resolveExpression(expression, { ...scope, relations: [] }, ctes)));
940
+ const width = Math.max(0, ...rows.map((row) => row.length));
941
+ if (rows.some((row) => row.length !== width))
942
+ this.#diagnostic("TSQ214", "MERGE VALUES source rows must have equal arity", statement.source.range);
943
+ if (statement.source.columns.length > 0 && statement.source.columns.length !== width)
944
+ this.#diagnostic("TSQ214", "MERGE VALUES source alias list does not match row arity", statement.source.range);
945
+ const columns = {};
946
+ for (let index = 0; index < width; index += 1) {
947
+ const candidates = rows.map((row) => row[index]).filter((value) => value !== undefined);
948
+ const name = statement.source.columns[index]?.name ?? `column${index + 1}`;
949
+ columns[name] = {
950
+ name,
951
+ databaseType: candidates[0]?.databaseType ?? "unknown",
952
+ tsType: unionTypeLiterals(candidates.map(({ tsType }) => tsType)),
953
+ nullable: candidates.some(({ nullable }) => nullable),
954
+ };
955
+ }
956
+ const sourceAlias = statement.source.alias === undefined
957
+ ? `*merge-values*${statement.source.range.start}`
958
+ : sqlName(statement.source.alias);
959
+ if (sourceAlias === targetAlias)
960
+ this.#diagnostic("TSQ108", `Duplicate relation alias ${sourceAlias}`, statement.source.range);
961
+ scope.relations.push({
962
+ alias: sourceAlias,
963
+ table: { name: statement.source.alias?.name ?? sourceAlias, columns },
964
+ nullable: false,
965
+ });
966
+ }
967
+ else
968
+ this.#addRelation(statement.source, false, scope, ctes);
969
+ this.#resolveExpression(statement.on, scope, ctes, this.#databaseType("boolean", false));
970
+ const terminal = new Set();
971
+ for (const clause of statement.clauses) {
972
+ if (clause.by !== undefined)
973
+ this.#requireServerMajor(17, `MERGE WHEN NOT MATCHED BY ${clause.by.toUpperCase()}`, clause.range);
974
+ if (terminal.has(clause.match))
975
+ this.#diagnostic("TSQ227", `Unreachable MERGE ${clause.match} clause`, clause.range);
976
+ if (clause.condition === undefined)
977
+ terminal.add(clause.match);
978
+ const clauseScope = {
979
+ ...scope,
980
+ relations: scope.relations.filter(({ alias }) => {
981
+ if (clause.match === "matched")
982
+ return true;
983
+ return clause.match === "not-matched-source" ? alias === targetAlias : alias !== targetAlias;
984
+ }),
985
+ };
986
+ if (clause.condition !== undefined)
987
+ this.#resolveExpression(clause.condition, clauseScope, ctes, this.#databaseType("boolean", false));
988
+ const action = clause.action;
989
+ if (action.kind === "insert") {
990
+ if (clause.match !== "not-matched-target")
991
+ this.#diagnostic("TSQ227", "MERGE INSERT is only valid for NOT MATCHED BY TARGET", action.range);
992
+ const actionNames = action.columns.map(sqlName);
993
+ if (new Set(actionNames).size !== actionNames.length)
994
+ this.#diagnostic("TSQ227", "MERGE INSERT cannot target the same column more than once", action.range);
995
+ if (action.source.kind === "default-values" && action.overriding !== undefined)
996
+ this.#diagnostic("TSQ227", "MERGE DEFAULT VALUES cannot use OVERRIDING", action.range);
997
+ const targetColumns = action.columns.length === 0
998
+ ? Object.values(target?.table.columns ?? {})
999
+ : action.columns.map((column) => this.#findColumn(target?.table, column));
1000
+ if (target !== undefined) {
1001
+ targetColumns.forEach((column, index) => {
1002
+ if (column === undefined)
1003
+ return;
1004
+ const evidence = this.#index.columnEvidence(target.table, column);
1005
+ const identityOverride = action.overriding !== undefined && evidence?.identity !== "none";
1006
+ const defaultOnly = action.source.kind === "default-values" ||
1007
+ action.source.rows.every((row) => this.#isDefaultValue(row[index]));
1008
+ if (this.#index.columnEligibility(target.table, column, "insert") === false &&
1009
+ !identityOverride &&
1010
+ !defaultOnly) {
1011
+ this.#diagnostic("TSQ218", `Cannot INSERT into non-insertable column ${column.name}`, action.columns[index]?.range ?? action.range);
1012
+ }
1013
+ });
1014
+ const supplied = new Set();
1015
+ if (action.source.kind === "values") {
1016
+ const rows = action.source.rows;
1017
+ targetColumns.forEach((column, index) => {
1018
+ if (column !== undefined && !rows.every((row) => this.#isDefaultValue(row[index])))
1019
+ supplied.add(column.name.toLowerCase());
1020
+ });
1021
+ }
1022
+ const required = this.#index.requiredInsertColumns(target.table);
1023
+ if (required !== "unknown") {
1024
+ for (const column of required) {
1025
+ if (!supplied.has(column.name.toLowerCase()))
1026
+ this.#diagnostic("TSQ219", `MERGE INSERT omits required column ${column.name}`, action.range);
1027
+ }
1028
+ }
1029
+ }
1030
+ if (action.source.kind === "values") {
1031
+ const values = action.source.rows[0] ?? [];
1032
+ if (values.length !== targetColumns.length)
1033
+ this.#diagnostic("TSQ214", `MERGE INSERT has ${targetColumns.length} columns but ${values.length} values`, action.range);
1034
+ values.forEach((value, index) => {
1035
+ const resolved = this.#resolveExpression(value, clauseScope, ctes, this.#snapshotType(targetColumns[index]));
1036
+ this.#validateWriteValue(targetColumns[index], resolved, value.range, "MERGE INSERT");
1037
+ });
1038
+ }
1039
+ }
1040
+ else if (action.kind === "update") {
1041
+ if (clause.match === "not-matched-target")
1042
+ this.#diagnostic("TSQ227", "MERGE UPDATE cannot modify an unmatched target row", action.range);
1043
+ this.#resolveAssignments(action.assignments, target?.table, clauseScope, ctes);
1044
+ }
1045
+ else if (action.kind === "delete" && clause.match === "not-matched-target") {
1046
+ this.#diagnostic("TSQ227", "MERGE DELETE cannot delete an unmatched target row", action.range);
1047
+ }
1048
+ }
1049
+ if (statement.returning.length > 0)
1050
+ this.#requireServerMajor(17, "MERGE RETURNING", statement.range);
1051
+ const returningScope = this.#returningScope(scope, target?.table, statement.returningAliases, statement.returning, "merge");
1052
+ this.#insideMergeReturning = true;
1053
+ const columns = this.#resolveItems(statement.returning, returningScope, ctes);
1054
+ this.#insideMergeReturning = false;
234
1055
  return { columns, resultKind: statement.returning.length === 0 ? "command" : "rows" };
235
1056
  }
1057
+ #resolveAssignments(assignments, table, scope, ctes) {
1058
+ const assigned = new Set();
1059
+ for (const assignment of assignments) {
1060
+ const identifiers = "column" in assignment ? [assignment.column] : assignment.columns;
1061
+ const names = identifiers.map(sqlName);
1062
+ if (new Set(names).size !== names.length || names.some((name) => assigned.has(name))) {
1063
+ this.#diagnostic("TSQ227", "An assignment cannot target the same column more than once", assignment.range);
1064
+ }
1065
+ for (const name of names)
1066
+ assigned.add(name);
1067
+ const columns = identifiers.map((identifier) => this.#findColumn(table, identifier));
1068
+ for (let index = 0; index < columns.length; index += 1) {
1069
+ const column = columns[index];
1070
+ if (table !== undefined &&
1071
+ column !== undefined &&
1072
+ this.#index.columnEligibility(table, column, "update") === false) {
1073
+ this.#diagnostic("TSQ218", `Cannot UPDATE non-updatable column ${column.name}`, identifiers[index].range);
1074
+ }
1075
+ }
1076
+ if (identifiers.length === 1) {
1077
+ const resolved = this.#resolveExpression(assignment.value, scope, ctes, this.#snapshotType(columns[0]));
1078
+ this.#validateWriteValue(columns[0], resolved, assignment.value.range, "UPDATE");
1079
+ }
1080
+ else if (assignment.value.kind === "row") {
1081
+ assignment.value.elements.forEach((element, index) => {
1082
+ const resolved = this.#resolveExpression(element, scope, ctes, this.#snapshotType(columns[index]));
1083
+ this.#validateWriteValue(columns[index], resolved, element.range, "row assignment");
1084
+ });
1085
+ if (assignment.value.elements.length !== identifiers.length)
1086
+ this.#diagnostic("TSQ214", `Row assignment has ${identifiers.length} targets but ${assignment.value.elements.length} values`, assignment.range);
1087
+ }
1088
+ else if (assignment.value.kind === "subquery") {
1089
+ const resolved = this.#resolveStatement(assignment.value.query, scope, ctes, columns.map((column) => this.#snapshotType(column)));
1090
+ if (resolved.columns.length !== identifiers.length)
1091
+ this.#diagnostic("TSQ214", `Row assignment has ${identifiers.length} targets but subquery returns ${resolved.columns.length} columns`, assignment.range);
1092
+ this.#validateWriteColumns(columns, resolved.columns, assignment.range, "row assignment");
1093
+ }
1094
+ else {
1095
+ this.#resolveExpression(assignment.value, scope, ctes);
1096
+ this.#diagnostic("TSQ227", "Multi-column assignment requires a row or scalar subquery", assignment.range);
1097
+ }
1098
+ }
1099
+ }
1100
+ #returningScope(scope, table, aliases, items, operation) {
1101
+ if (aliases !== undefined)
1102
+ this.#requireServerMajor(18, "RETURNING OLD/NEW aliases", aliases.range);
1103
+ if (aliases === undefined &&
1104
+ items.some(({ expression }) => this.#referencesDefaultReturningRow(expression, scope))) {
1105
+ this.#requireServerMajor(18, "RETURNING OLD/NEW rows", items[0].range);
1106
+ }
1107
+ if (items.length === 0)
1108
+ return scope;
1109
+ if (table === undefined || (aliases === undefined && (this.#serverMajor ?? 0) < 18))
1110
+ return scope;
1111
+ const oldAlias = aliases?.old === undefined ? "old" : sqlName(aliases.old);
1112
+ const newAlias = aliases?.new === undefined ? "new" : sqlName(aliases.new);
1113
+ const hiddenOld = aliases !== undefined && aliases.old !== undefined;
1114
+ const hiddenNew = aliases !== undefined && aliases.new !== undefined;
1115
+ if (oldAlias === newAlias)
1116
+ this.#diagnostic("TSQ108", `RETURNING OLD and NEW rows cannot share alias ${oldAlias}`, aliases?.range ?? items[0].range);
1117
+ for (const alias of [oldAlias, newAlias]) {
1118
+ if (scope.relations.some((relation) => relation.alias === alias))
1119
+ this.#diagnostic("TSQ108", `RETURNING row alias ${alias} conflicts with an existing relation`, aliases?.range ?? items[0].range);
1120
+ }
1121
+ return {
1122
+ ...scope,
1123
+ relations: [
1124
+ ...scope.relations,
1125
+ ...(!hiddenOld || aliases?.old !== undefined
1126
+ ? [{ alias: oldAlias, table, nullable: operation === "insert" || operation === "merge", qualifiedOnly: true }]
1127
+ : []),
1128
+ ...(!hiddenNew || aliases?.new !== undefined
1129
+ ? [{ alias: newAlias, table, nullable: operation === "delete" || operation === "merge", qualifiedOnly: true }]
1130
+ : []),
1131
+ ],
1132
+ };
1133
+ }
1134
+ #referencesDefaultReturningRow(expression, scope) {
1135
+ if (expression.kind === "column" &&
1136
+ expression.relation !== undefined &&
1137
+ (sqlName(expression.relation) === "old" || sqlName(expression.relation) === "new") &&
1138
+ !scope.relations.some(({ alias }) => alias === sqlName(expression.relation)))
1139
+ return true;
1140
+ if (expression.kind === "star" && expression.relation !== undefined) {
1141
+ const alias = sqlName(expression.relation);
1142
+ return (alias === "old" || alias === "new") && !scope.relations.some((relation) => relation.alias === alias);
1143
+ }
1144
+ return this.#expressionChildren(expression).some((child) => this.#referencesDefaultReturningRow(child, scope));
1145
+ }
1146
+ #requireServerMajor(minimum, feature, range) {
1147
+ if (this.#serverMajor === undefined) {
1148
+ this.#diagnostic("TSQ402", `${feature} requires PostgreSQL server-version evidence`, range);
1149
+ }
1150
+ else if (this.#serverMajor < minimum) {
1151
+ this.#diagnostic("TSQ403", `${feature} requires PostgreSQL ${minimum} or newer`, range);
1152
+ }
1153
+ }
236
1154
  #addJoin(join, scope, ctes) {
237
1155
  const previous = [...scope.relations];
238
1156
  if (join.kind === "right" || join.kind === "full")
239
1157
  for (const relation of previous)
240
1158
  relation.nullable = true;
241
- const relation = this.#addRelation(join.table, join.kind === "left" || join.kind === "full", scope, ctes);
1159
+ const restrictLateral = (join.kind === "right" || join.kind === "full") &&
1160
+ (join.table.kind === "function" ||
1161
+ join.table.kind === "json-table" ||
1162
+ (join.table.kind === "subquery" && join.table.lateral));
1163
+ const relationScope = restrictLateral
1164
+ ? {
1165
+ relations: [],
1166
+ usingColumns: new Map(),
1167
+ windows: scope.windows,
1168
+ ...(scope.outer === undefined ? {} : { outer: scope.outer }),
1169
+ }
1170
+ : scope;
1171
+ const relation = this.#addRelation(join.table, join.kind === "left" || join.kind === "full", relationScope, ctes);
1172
+ if (restrictLateral && relation !== undefined) {
1173
+ if (scope.relations.some(({ alias }) => alias === relation.alias)) {
1174
+ this.#diagnostic("TSQ108", `Duplicate relation alias ${relation.alias}`, join.table.range);
1175
+ }
1176
+ else
1177
+ scope.relations.push(relation);
1178
+ }
242
1179
  if (join.on !== undefined)
243
1180
  this.#resolveExpression(join.on, scope, ctes, this.#databaseType("boolean", false));
244
1181
  if (join.using !== undefined && relation !== undefined) {
@@ -269,20 +1206,33 @@ class Resolver {
269
1206
  #addRelation(reference, nullable, scope, ctes) {
270
1207
  let table;
271
1208
  let alias;
272
- if (reference.kind === "subquery") {
1209
+ if (reference.kind === "function") {
1210
+ table = this.#resolveFunctionTable(reference, scope, ctes);
1211
+ alias = reference.alias === undefined ? sqlName(reference.functions[0].call.name) : sqlName(reference.alias);
1212
+ }
1213
+ else if (reference.kind === "json-table") {
1214
+ table = this.#resolveJsonTable(reference, scope, ctes);
1215
+ alias = reference.alias === undefined ? "json_table" : sqlName(reference.alias);
1216
+ }
1217
+ else if (reference.kind === "subquery") {
1218
+ if (reference.alias === undefined)
1219
+ this.#requireServerMajor(16, "Unaliased derived tables", reference.range);
273
1220
  const outer = reference.lateral ? scope : scope.outer;
274
1221
  const resolved = this.#resolveStatement(reference.query, outer, ctes);
275
1222
  const columns = {};
276
- for (const column of resolved.columns) {
277
- columns[column.name] = {
278
- name: column.name,
1223
+ resolved.columns.forEach((column, index) => {
1224
+ const name = reference.columns[index] === undefined ? column.name : sqlName(reference.columns[index]);
1225
+ columns[name] = {
1226
+ name,
279
1227
  databaseType: column.databaseType ?? "unknown",
280
1228
  tsType: column.tsType,
281
1229
  nullable: column.nullable,
282
1230
  };
283
- }
284
- table = { name: sqlName(reference.alias), columns };
285
- alias = sqlName(reference.alias);
1231
+ });
1232
+ if (reference.columns.length > 0 && reference.columns.length !== resolved.columns.length)
1233
+ this.#diagnostic("TSQ213", "Derived-table column alias list has the wrong arity", reference.range);
1234
+ alias = reference.alias === undefined ? `*derived*${reference.range.start}` : sqlName(reference.alias);
1235
+ table = { name: alias, columns };
286
1236
  }
287
1237
  else {
288
1238
  const requested = sqlName(reference.name);
@@ -309,26 +1259,398 @@ class Resolver {
309
1259
  }
310
1260
  const relation = { alias, table, nullable };
311
1261
  scope.relations.push(relation);
1262
+ if (reference.kind === "table" && reference.sample !== undefined) {
1263
+ const sampleMethod = reference.sample.method.name.toUpperCase();
1264
+ if ((sampleMethod === "SYSTEM" || sampleMethod === "BERNOULLI") && reference.sample.arguments.length !== 1) {
1265
+ this.#diagnostic("TSQ227", `${sampleMethod} TABLESAMPLE requires one percentage argument`, reference.sample.range);
1266
+ }
1267
+ if (reference.sample.repeatable?.kind === "literal" && reference.sample.repeatable.value === null) {
1268
+ this.#diagnostic("TSQ227", "TABLESAMPLE REPEATABLE seed cannot be NULL", reference.sample.repeatable.range);
1269
+ }
1270
+ const sampleScope = {
1271
+ relations: [],
1272
+ usingColumns: new Map(),
1273
+ windows: scope.windows,
1274
+ ...(scope.outer === undefined ? {} : { outer: scope.outer }),
1275
+ };
1276
+ for (const argument of reference.sample.arguments) {
1277
+ this.#resolveExpression(argument, sampleScope, ctes, this.#databaseType("numeric", false));
1278
+ }
1279
+ if (reference.sample.repeatable !== undefined) {
1280
+ this.#resolveExpression(reference.sample.repeatable, sampleScope, ctes, this.#databaseType("double precision", false));
1281
+ }
1282
+ }
312
1283
  return relation;
313
1284
  }
314
- #resolveItems(items, scope, ctes) {
315
- const columns = [];
316
- const names = new Set();
317
- const add = (column) => {
318
- if (names.has(column.name)) {
319
- this.#diagnostic("TSQ105", `Duplicate output property ${column.name}`, column.range, "Give one expression a unique alias.");
320
- return;
1285
+ #resolveJsonTable(reference, scope, ctes) {
1286
+ this.#requireServerMajor(17, "JSON_TABLE", reference.range);
1287
+ const context = this.#resolveSqlJsonValue(reference.context, scope, ctes, true);
1288
+ let pathValid = reference.path.kind === "literal" && typeof reference.path.value === "string";
1289
+ if (!pathValid) {
1290
+ this.#resolveExpression(reference.path, scope, ctes);
1291
+ this.#diagnostic("TSQ203", "PostgreSQL JSON_TABLE path must be a string constant", reference.path.range);
1292
+ }
1293
+ const passing = reference.passing.map((argument) => this.#resolveSqlJsonValue(argument.value, scope, ctes, false));
1294
+ const topOnErrorValid = reference.onError === undefined || reference.onError.kind === "error" || reference.onError.kind === "empty-array";
1295
+ if (!topOnErrorValid) {
1296
+ this.#diagnostic("TSQ203", "JSON_TABLE allows only ERROR or EMPTY ARRAY ON ERROR", reference.onError.range);
1297
+ this.#resolveJsonQueryBehavior(reference.onError, undefined, scope, ctes);
1298
+ }
1299
+ if (!context.valid || passing.some(({ valid }) => !valid))
1300
+ pathValid = false;
1301
+ const jsonNames = new Set();
1302
+ if (reference.pathName !== undefined)
1303
+ jsonNames.add(sqlName(reference.pathName));
1304
+ this.#validateJsonTableNames(reference.jsonColumns, jsonNames);
1305
+ const resolvedColumns = this.#resolveJsonTableColumns(reference.jsonColumns, scope, ctes, false);
1306
+ reference.columns.forEach((identifier, index) => {
1307
+ const column = resolvedColumns[index];
1308
+ if (column === undefined) {
1309
+ this.#diagnostic("TSQ213", "JSON_TABLE column alias list has more names than outputs", identifier.range);
321
1310
  }
322
- names.add(column.name);
323
- columns.push(column);
324
- };
325
- for (const item of items) {
326
- if (item.expression.kind === "star") {
327
- const star = item.expression;
328
- const relations = star.relation === undefined
329
- ? scope.relations
330
- : scope.relations.filter((relation) => relation.alias === sqlName(star.relation));
331
- if (relations.length === 0) {
1311
+ else
1312
+ column.name = sqlName(identifier);
1313
+ });
1314
+ const columns = {};
1315
+ for (const column of resolvedColumns) {
1316
+ if (columns[column.name] !== undefined) {
1317
+ this.#diagnostic("TSQ105", `Duplicate JSON_TABLE column ${column.name}`, reference.range);
1318
+ continue;
1319
+ }
1320
+ columns[column.name] =
1321
+ pathValid && topOnErrorValid
1322
+ ? column
1323
+ : { ...column, databaseType: "unknown", tsType: "unknown", nullable: true };
1324
+ }
1325
+ return { name: reference.alias === undefined ? "json_table" : sqlName(reference.alias), columns };
1326
+ }
1327
+ #validateJsonTableNames(definitions, names) {
1328
+ for (const definition of definitions) {
1329
+ const identifier = definition.kind === "nested" ? definition.pathName : definition.name;
1330
+ if (identifier !== undefined) {
1331
+ const name = sqlName(identifier);
1332
+ if (names.has(name)) {
1333
+ this.#diagnostic("TSQ105", `Duplicate JSON_TABLE column or path name ${name}`, identifier.range);
1334
+ }
1335
+ names.add(name);
1336
+ }
1337
+ if (definition.kind === "nested")
1338
+ this.#validateJsonTableNames(definition.columns, names);
1339
+ }
1340
+ }
1341
+ #resolveJsonTableColumns(definitions, scope, ctes, nested) {
1342
+ return definitions.flatMap((definition) => {
1343
+ if (definition.kind === "nested") {
1344
+ return this.#resolveJsonTableColumns(definition.columns, scope, ctes, true).map((column) => ({
1345
+ ...column,
1346
+ nullable: true,
1347
+ }));
1348
+ }
1349
+ if (definition.kind === "ordinality") {
1350
+ return [{ name: sqlName(definition.name), databaseType: "integer", tsType: "number", nullable: nested }];
1351
+ }
1352
+ const requestedType = definition.databaseType.name;
1353
+ const known = isKnownPostgresType(requestedType, this.#schema);
1354
+ if (!known) {
1355
+ this.#diagnostic("TSQ106", `Invalid or unknown PostgreSQL JSON_TABLE column type ${requestedType}`, definition.databaseType.range);
1356
+ }
1357
+ const canonical = known ? postgresCanonicalType(requestedType, this.#schema) : undefined;
1358
+ let valid = known;
1359
+ if (definition.kind === "exists") {
1360
+ if (canonical !== undefined && !postgresCanCoerce("boolean", canonical, "explicit", this.#schema)) {
1361
+ this.#diagnostic("TSQ203", `JSON_TABLE EXISTS result cannot be coerced to ${requestedType}`, definition.databaseType.range);
1362
+ valid = false;
1363
+ }
1364
+ if (definition.onError !== undefined &&
1365
+ !["error", "true", "false", "unknown"].includes(definition.onError.kind)) {
1366
+ this.#diagnostic("TSQ203", "JSON_TABLE EXISTS allows only ERROR, TRUE, FALSE, or UNKNOWN ON ERROR", definition.onError.range);
1367
+ this.#resolveJsonQueryBehavior(definition.onError, undefined, scope, ctes);
1368
+ valid = false;
1369
+ }
1370
+ return [
1371
+ {
1372
+ name: sqlName(definition.name),
1373
+ databaseType: valid ? canonical : "unknown",
1374
+ tsType: valid ? mapPostgresType(canonical, this.#policy, this.#schema) : "unknown",
1375
+ nullable: nested || definition.onError?.kind === "unknown",
1376
+ },
1377
+ ];
1378
+ }
1379
+ if (definition.format !== undefined && canonical !== undefined) {
1380
+ const mapping = postgresCatalogTypeMapping(canonical, this.#schema);
1381
+ if (mapping !== "string" && mapping !== "bytes" && mapping !== "json") {
1382
+ this.#diagnostic("TSQ203", `JSON_TABLE FORMAT JSON requires a string, bytea, json, or jsonb column type, received ${requestedType}`, definition.format.range);
1383
+ valid = false;
1384
+ }
1385
+ if (definition.format.encoding !== undefined) {
1386
+ if (definition.format.encoding !== "UTF8") {
1387
+ this.#diagnostic("TSQ203", `PostgreSQL JSON ENCODING supports UTF8, received ${definition.format.encoding}`, definition.format.range);
1388
+ valid = false;
1389
+ }
1390
+ if (canonical !== "bytea") {
1391
+ this.#diagnostic("TSQ203", `JSON ENCODING requires bytea output, received ${requestedType}`, definition.format.range);
1392
+ valid = false;
1393
+ }
1394
+ }
1395
+ }
1396
+ if (definition.quotes === "omit" && definition.wrapper !== undefined && definition.wrapper !== "without") {
1397
+ this.#diagnostic("TSQ203", "JSON_TABLE QUOTES behavior cannot be combined with WITH WRAPPER", definition.range);
1398
+ valid = false;
1399
+ }
1400
+ for (const behavior of [definition.onEmpty, definition.onError]) {
1401
+ if (behavior !== undefined && ["true", "false", "unknown"].includes(behavior.kind)) {
1402
+ this.#diagnostic("TSQ203", "JSON_TABLE value columns do not allow TRUE, FALSE, or UNKNOWN behavior", behavior.range);
1403
+ valid = false;
1404
+ }
1405
+ }
1406
+ const expected = canonical === undefined ? undefined : this.#databaseType(canonical, true);
1407
+ const allowCollections = definition.format !== undefined || definition.wrapper !== undefined || definition.quotes !== undefined;
1408
+ const onEmpty = this.#resolveJsonQueryBehavior(definition.onEmpty, expected, scope, ctes, allowCollections, "JSON_TABLE scalar columns");
1409
+ const onError = this.#resolveJsonQueryBehavior(definition.onError, expected, scope, ctes, allowCollections, "JSON_TABLE scalar columns");
1410
+ valid &&= onEmpty.valid && onError.valid;
1411
+ return [
1412
+ {
1413
+ name: sqlName(definition.name),
1414
+ databaseType: valid ? canonical : "unknown",
1415
+ tsType: valid ? mapPostgresType(canonical, this.#policy, this.#schema) : "unknown",
1416
+ // Scalar columns convert JSON null to SQL NULL. Formatted columns
1417
+ // preserve JSON null and are nullable only through their behaviors.
1418
+ nullable: nested || !allowCollections || onEmpty.nullable || onError.nullable,
1419
+ },
1420
+ ];
1421
+ });
1422
+ }
1423
+ #resolveFunctionTable(reference, scope, ctes) {
1424
+ const outerDefinitions = reference.columns.some(({ databaseType }) => databaseType !== undefined);
1425
+ const directDefinitions = reference.functions.length === 1 &&
1426
+ reference.columns.length > 0 &&
1427
+ reference.columns.every(({ databaseType }) => databaseType !== undefined);
1428
+ if (outerDefinitions &&
1429
+ (reference.withOrdinality || (reference.rowsFrom === true && reference.functions.length > 1))) {
1430
+ this.#diagnostic("TSQ227", "An outer record definition list requires one function and cannot be combined with WITH ORDINALITY", reference.range);
1431
+ }
1432
+ const resolvedColumns = reference.functions.flatMap((item) => this.#resolveTableFunctionItem(directDefinitions ? { ...item, columns: reference.columns } : item, scope, ctes));
1433
+ if (reference.functions.length > 1) {
1434
+ for (const column of resolvedColumns)
1435
+ column.nullable = true;
1436
+ }
1437
+ if (reference.withOrdinality) {
1438
+ resolvedColumns.push({
1439
+ name: "ordinality",
1440
+ databaseType: "bigint",
1441
+ tsType: this.#policy.bigint,
1442
+ nullable: false,
1443
+ });
1444
+ }
1445
+ (directDefinitions ? [] : reference.columns).forEach((definition, index) => {
1446
+ const column = resolvedColumns[index];
1447
+ if (column === undefined) {
1448
+ this.#diagnostic("TSQ213", "Table-function column alias list has more names than outputs", definition.range);
1449
+ return;
1450
+ }
1451
+ column.name = sqlName(definition.name);
1452
+ if (definition.databaseType !== undefined) {
1453
+ column.databaseType = normalizeDatabaseType(definition.databaseType.name);
1454
+ column.tsType = mapPostgresType(definition.databaseType.name, this.#policy, this.#schema);
1455
+ }
1456
+ });
1457
+ const columns = {};
1458
+ for (const column of resolvedColumns) {
1459
+ if (columns[column.name] !== undefined) {
1460
+ this.#diagnostic("TSQ105", `Duplicate table-function column ${column.name}`, reference.range);
1461
+ continue;
1462
+ }
1463
+ columns[column.name] = column;
1464
+ }
1465
+ return {
1466
+ name: reference.alias === undefined ? sqlName(reference.functions[0].call.name) : sqlName(reference.alias),
1467
+ columns,
1468
+ };
1469
+ }
1470
+ #resolveTableFunctionItem(item, scope, ctes) {
1471
+ const expression = item.call;
1472
+ const argumentsList = expression.arguments.map((argument) => this.#resolveExpression(argument, scope, ctes));
1473
+ if (item.columns.length > 0) {
1474
+ return item.columns.map((definition) => ({
1475
+ name: sqlName(definition.name),
1476
+ databaseType: definition.databaseType?.name ?? "unknown",
1477
+ tsType: definition.databaseType === undefined
1478
+ ? "unknown"
1479
+ : mapPostgresType(definition.databaseType.name, this.#policy, this.#schema),
1480
+ nullable: true,
1481
+ }));
1482
+ }
1483
+ const functionName = sqlName(expression.name);
1484
+ const schemaName = expression.schema === undefined ? undefined : sqlName(expression.schema);
1485
+ const routines = this.#index.routineOverloads(functionName, expression.arguments.length, schemaName);
1486
+ const selectedRoutine = this.#selectTableRoutine(routines, argumentsList, expression);
1487
+ if (selectedRoutine !== undefined) {
1488
+ const { routine, argumentTypes, resultType } = selectedRoutine;
1489
+ expression.arguments.forEach((argument, index) => {
1490
+ if (this.#acceptsTypeContext(argument)) {
1491
+ this.#resolveExpression(argument, scope, ctes, this.#databaseType(argumentTypes[index], true));
1492
+ }
1493
+ });
1494
+ const result = routine.result;
1495
+ if (result.kind === "record" || result.kind === "table") {
1496
+ return Object.values(result.columns)
1497
+ .sort((left, right) => left.position - right.position)
1498
+ .map((column) => ({
1499
+ name: column.name,
1500
+ databaseType: column.databaseType,
1501
+ tsType: column.tsType,
1502
+ nullable: column.nullable,
1503
+ }));
1504
+ }
1505
+ if (result.kind === "scalar" || result.kind === "set") {
1506
+ return [
1507
+ {
1508
+ name: functionName,
1509
+ databaseType: resultType,
1510
+ tsType: resultType === normalizeDatabaseType(result.databaseType)
1511
+ ? result.tsType
1512
+ : mapPostgresType(resultType, this.#policy, this.#schema),
1513
+ nullable: result.nullable,
1514
+ },
1515
+ ];
1516
+ }
1517
+ this.#diagnostic("TSQ212", `Routine ${expression.name.name} does not return rows`, expression.range);
1518
+ return [];
1519
+ }
1520
+ const rule = postgresCatalogTableRoutineRule(expression.name.name, this.#schema);
1521
+ if (rule === "array-elements") {
1522
+ return argumentsList.map((argument, index) => ({
1523
+ name: index === 0 ? functionName : `${functionName}_${index + 1}`,
1524
+ databaseType: argument.databaseType?.replace(/\[\]$/u, "") ?? "unknown",
1525
+ tsType: this.#arrayElementType(argument.tsType),
1526
+ nullable: true,
1527
+ }));
1528
+ }
1529
+ if (rule === "first-argument") {
1530
+ const value = argumentsList[0] ?? { tsType: "unknown", nullable: true };
1531
+ return [
1532
+ { name: functionName, databaseType: value.databaseType ?? "unknown", tsType: value.tsType, nullable: false },
1533
+ ];
1534
+ }
1535
+ if (rule === "integer") {
1536
+ return [{ name: functionName, databaseType: "integer", tsType: "number", nullable: false }];
1537
+ }
1538
+ if (rule === "json-each" || rule === "json-each-text") {
1539
+ const json = rule === "json-each";
1540
+ return [
1541
+ { name: "key", databaseType: "text", tsType: "string", nullable: false },
1542
+ {
1543
+ name: "value",
1544
+ databaseType: json ? (expression.name.name.toUpperCase().startsWith("JSONB") ? "jsonb" : "json") : "text",
1545
+ tsType: json ? this.#policy.json : "string",
1546
+ nullable: false,
1547
+ },
1548
+ ];
1549
+ }
1550
+ if (rule === "json-array-elements" || rule === "json-array-elements-text") {
1551
+ const json = rule === "json-array-elements";
1552
+ return [
1553
+ {
1554
+ name: "value",
1555
+ databaseType: json ? (expression.name.name.toUpperCase().startsWith("JSONB") ? "jsonb" : "json") : "text",
1556
+ tsType: json ? this.#policy.json : "string",
1557
+ nullable: false,
1558
+ },
1559
+ ];
1560
+ }
1561
+ if (rule === "json-path-query") {
1562
+ const signature = this.#jsonPathCallTypes(expression);
1563
+ const selection = signature === undefined
1564
+ ? { kind: "none" }
1565
+ : resolvePostgresCandidates([{ value: functionName, argumentTypes: signature, resultType: "jsonb" }], expression.arguments.map((argument, index) => this.#overloadInputType(argument, argumentsList[index])), this.#schema);
1566
+ if (signature === undefined || selection.kind !== "selected") {
1567
+ this.#diagnostic("TSQ202", `No overload of ${expression.name.name} accepts these argument types`, expression.range, "Use jsonb, jsonpath, optional jsonb variables, and an optional boolean silent flag.", "warning");
1568
+ return [];
1569
+ }
1570
+ expression.arguments.forEach((argument, index) => {
1571
+ if (this.#acceptsTypeContext(argument)) {
1572
+ this.#resolveExpression(argument, scope, ctes, this.#databaseType(signature[index], true));
1573
+ }
1574
+ });
1575
+ return [
1576
+ {
1577
+ name: functionName,
1578
+ databaseType: "jsonb",
1579
+ tsType: this.#policy.json,
1580
+ nullable: false,
1581
+ },
1582
+ ];
1583
+ }
1584
+ if (rule === "record") {
1585
+ this.#diagnostic("TSQ213", `Record-returning routine ${expression.name.name} requires a typed column definition list`, expression.range);
1586
+ return [];
1587
+ }
1588
+ const candidates = this.#index.functions(functionName, expression.arguments.length, schemaName);
1589
+ if (candidates.length === 1) {
1590
+ const type = this.#functionType(candidates[0]);
1591
+ return [
1592
+ {
1593
+ name: functionName,
1594
+ databaseType: type.databaseType ?? "unknown",
1595
+ tsType: type.tsType,
1596
+ nullable: type.nullable,
1597
+ },
1598
+ ];
1599
+ }
1600
+ this.#diagnostic("TSQ202", `Unknown table function ${expression.name.name}`, expression.range, undefined, "warning");
1601
+ return [{ name: functionName, databaseType: "unknown", tsType: "unknown", nullable: true }];
1602
+ }
1603
+ #selectTableRoutine(candidates, argumentsList, expression) {
1604
+ const result = resolvePostgresCandidates(candidates.flatMap((candidate) => {
1605
+ const routineResult = candidate.result;
1606
+ if (routineResult.kind === "void" || routineResult.kind === "command")
1607
+ return [];
1608
+ const argumentTypes = this.#routineCallTypes(candidate, expression);
1609
+ if (argumentTypes === undefined)
1610
+ return [];
1611
+ return [
1612
+ {
1613
+ value: candidate,
1614
+ argumentTypes,
1615
+ resultType: routineResult.kind === "scalar" || routineResult.kind === "set"
1616
+ ? routineResult.databaseType
1617
+ : routineResult.kind,
1618
+ },
1619
+ ];
1620
+ }), expression.arguments.map((argument, index) => this.#overloadInputType(argument, argumentsList[index])), this.#schema);
1621
+ if (result.kind === "selected") {
1622
+ return { routine: result.candidate, argumentTypes: result.argumentTypes, resultType: result.resultType };
1623
+ }
1624
+ if (result.kind === "ambiguous") {
1625
+ this.#diagnostic("TSQ204", `Ambiguous overloaded table function ${expression.name.name}`, expression.range, "Cast arguments to select a specific overload.");
1626
+ }
1627
+ else if (candidates.length > 0) {
1628
+ this.#diagnostic("TSQ202", `No table-function overload of ${expression.name.name} accepts these argument types`, expression.range, "Cast arguments to a supported overload.", "warning");
1629
+ }
1630
+ return undefined;
1631
+ }
1632
+ #arrayElementType(type) {
1633
+ const match = /^readonly \((.*)\)\[\]$/u.exec(type);
1634
+ return match?.[1] ?? "unknown";
1635
+ }
1636
+ #resolveItems(items, scope, ctes, expectedOutput) {
1637
+ const columns = [];
1638
+ const names = new Set();
1639
+ const add = (column) => {
1640
+ if (names.has(column.name)) {
1641
+ this.#diagnostic("TSQ105", `Duplicate output property ${column.name}`, column.range, "Give one expression a unique alias.");
1642
+ return;
1643
+ }
1644
+ names.add(column.name);
1645
+ columns.push(column);
1646
+ };
1647
+ for (const item of items) {
1648
+ if (item.expression.kind === "star") {
1649
+ const star = item.expression;
1650
+ const relations = star.relation === undefined
1651
+ ? scope.relations.filter((relation) => relation.qualifiedOnly !== true)
1652
+ : scope.relations.filter((relation) => relation.alias === sqlName(star.relation));
1653
+ if (relations.length === 0) {
332
1654
  this.#diagnostic("TSQ103", star.relation === undefined
333
1655
  ? "SELECT * requires a FROM relation"
334
1656
  : `Unknown relation alias ${star.relation.name}`, star.range);
@@ -348,7 +1670,7 @@ class Resolver {
348
1670
  }
349
1671
  continue;
350
1672
  }
351
- const type = this.#resolveExpression(item.expression, scope, ctes);
1673
+ const type = this.#resolveExpression(item.expression, scope, ctes, expectedOutput?.[columns.length]);
352
1674
  const inferredName = this.#outputName(item.expression);
353
1675
  const name = item.alias === undefined ? inferredName : sqlName(item.alias);
354
1676
  if (name === undefined) {
@@ -365,6 +1687,34 @@ class Resolver {
365
1687
  return sqlName(expression.column);
366
1688
  if (expression.kind === "cast")
367
1689
  return this.#outputName(expression.expression);
1690
+ if (expression.kind === "subscript")
1691
+ return this.#outputName(expression.expression);
1692
+ if (expression.kind === "field-access")
1693
+ return sqlName(expression.field);
1694
+ if (expression.kind === "collate")
1695
+ return this.#outputName(expression.expression);
1696
+ if (expression.kind === "at-time-zone")
1697
+ return "timezone";
1698
+ if (expression.kind === "json-object")
1699
+ return "json_object";
1700
+ if (expression.kind === "json-array")
1701
+ return "json_array";
1702
+ if (expression.kind === "json-object-aggregate")
1703
+ return "json_objectagg";
1704
+ if (expression.kind === "json-array-aggregate")
1705
+ return "json_arrayagg";
1706
+ if (expression.kind === "json-parse")
1707
+ return "json";
1708
+ if (expression.kind === "json-scalar")
1709
+ return "json_scalar";
1710
+ if (expression.kind === "json-serialize")
1711
+ return "json_serialize";
1712
+ if (expression.kind === "json-exists")
1713
+ return "json_exists";
1714
+ if (expression.kind === "json-query")
1715
+ return "json_query";
1716
+ if (expression.kind === "json-value")
1717
+ return "json_value";
368
1718
  if (expression.kind === "call")
369
1719
  return sqlName(expression.name);
370
1720
  if (expression.kind === "case")
@@ -396,15 +1746,17 @@ class Resolver {
396
1746
  case "star":
397
1747
  return { tsType: "unknown", nullable: false };
398
1748
  case "array": {
399
- const elements = expression.elements.map((element) => this.#resolveExpression(element, scope, ctes));
1749
+ const expectedElement = expected?.databaseType === undefined ? undefined : postgresElementType(expected.databaseType, this.#schema);
1750
+ const expectedElementType = expectedElement === undefined ? undefined : this.#databaseType(expectedElement, expected?.nullable ?? true);
1751
+ const elements = expression.elements.map((element) => this.#resolveExpression(element, scope, ctes, expectedElementType));
400
1752
  const elementType = unionTypeLiterals(elements.map((element) => element.tsType));
401
- const databaseTypes = [
402
- ...new Set(elements.map((element) => element.databaseType).filter((value) => value !== undefined)),
403
- ];
1753
+ const databaseType = (elements.length === 0
1754
+ ? undefined
1755
+ : postgresCommonType(elements.map((element) => element.databaseType), this.#schema)) ?? expectedElement;
404
1756
  return {
405
1757
  tsType: `readonly (${elementType})[]`,
406
1758
  nullable: false,
407
- ...(databaseTypes.length === 1 ? { databaseType: `${databaseTypes[0]}[]` } : {}),
1759
+ ...(databaseType === undefined ? {} : { databaseType: `${databaseType}[]` }),
408
1760
  };
409
1761
  }
410
1762
  case "row": {
@@ -414,26 +1766,386 @@ class Resolver {
414
1766
  nullable: false,
415
1767
  };
416
1768
  }
1769
+ case "subscript": {
1770
+ const source = this.#resolveExpression(expression.expression, scope, ctes);
1771
+ const expectedIndex = this.#databaseType("integer", false);
1772
+ const bounds = [expression.index, expression.lower, expression.upper]
1773
+ .filter((bound) => bound !== undefined)
1774
+ .map((bound) => ({
1775
+ expression: bound,
1776
+ resolved: this.#resolveExpression(bound, scope, ctes, expectedIndex),
1777
+ }));
1778
+ for (const bound of bounds) {
1779
+ if (bound.resolved.databaseType !== undefined &&
1780
+ !postgresCanCoerce(bound.resolved.databaseType, "integer", "implicit", this.#schema)) {
1781
+ this.#diagnostic("TSQ203", `Array subscript must resolve to integer, received ${bound.resolved.databaseType}`, bound.expression.range);
1782
+ }
1783
+ }
1784
+ if (expression.slice) {
1785
+ if (source.databaseType === undefined ||
1786
+ postgresElementType(source.databaseType, this.#schema) === undefined) {
1787
+ this.#diagnostic("TSQ203", `Array slicing requires an array value, received ${source.databaseType ?? source.tsType}`, expression.range);
1788
+ return { tsType: "unknown", nullable: true };
1789
+ }
1790
+ return {
1791
+ ...source,
1792
+ nullable: source.nullable || bounds.some((bound) => bound.resolved.nullable),
1793
+ };
1794
+ }
1795
+ const elementType = source.databaseType === "point"
1796
+ ? "double precision"
1797
+ : source.databaseType === "box" || source.databaseType === "lseg"
1798
+ ? "point"
1799
+ : source.databaseType === undefined
1800
+ ? undefined
1801
+ : postgresElementType(source.databaseType, this.#schema);
1802
+ if (elementType === undefined) {
1803
+ this.#diagnostic("TSQ203", `Subscripting requires an array or subscriptable geometric value, received ${source.databaseType ?? source.tsType}`, expression.range);
1804
+ return { tsType: "unknown", nullable: true };
1805
+ }
1806
+ return {
1807
+ tsType: mapPostgresType(elementType, this.#policy, this.#schema),
1808
+ nullable: true,
1809
+ databaseType: elementType,
1810
+ };
1811
+ }
1812
+ case "field-access": {
1813
+ const source = this.#resolveExpression(expression.expression, scope, ctes);
1814
+ const canonical = source.databaseType === undefined ? undefined : postgresCanonicalType(source.databaseType, this.#schema);
1815
+ const composite = canonical === undefined || this.#schema.formatVersion !== 2
1816
+ ? undefined
1817
+ : Object.values(this.#schema.types).find((type) => {
1818
+ if (type.kind !== "composite")
1819
+ return false;
1820
+ const qualified = type.schema === undefined ? type.name : `${type.schema}.${type.name}`;
1821
+ return [type.databaseType, type.identity, qualified].some((identity) => normalizeDatabaseType(identity) === normalizeDatabaseType(canonical));
1822
+ });
1823
+ if (composite === undefined) {
1824
+ this.#diagnostic("TSQ203", `Field selection requires snapshot composite evidence for ${source.databaseType ?? source.tsType}`, expression.range);
1825
+ return { tsType: "unknown", nullable: true };
1826
+ }
1827
+ const fieldName = sqlName(expression.field);
1828
+ const field = composite.fields.find((candidate) => expression.field.quoted ? candidate.name === fieldName : candidate.name.toLowerCase() === fieldName);
1829
+ if (field === undefined) {
1830
+ this.#diagnostic("TSQ101", `Unknown field ${expression.field.name} on composite ${composite.databaseType}`, expression.field.range, suggestion(fieldName, composite.fields.map(({ name }) => name)));
1831
+ return { tsType: "unknown", nullable: true };
1832
+ }
1833
+ return {
1834
+ tsType: field.tsType,
1835
+ nullable: source.nullable || field.nullable,
1836
+ databaseType: field.databaseType,
1837
+ };
1838
+ }
1839
+ case "collate": {
1840
+ const contextual = expected !== undefined && this.#acceptsTypeContext(expression.expression)
1841
+ ? { ...expected, nullable: true }
1842
+ : expected;
1843
+ const source = this.#resolveExpression(expression.expression, scope, ctes, contextual);
1844
+ if (source.databaseType === undefined &&
1845
+ (this.#acceptsTypeContext(expression.expression) ||
1846
+ (expression.expression.kind === "literal" && expression.expression.value === null)))
1847
+ return source;
1848
+ if (source.databaseType === undefined || postgresTypeCategory(source.databaseType, this.#schema) !== "string") {
1849
+ this.#diagnostic("TSQ203", `COLLATE requires a collatable string value, received ${source.databaseType ?? source.tsType}`, expression.range);
1850
+ return { tsType: "unknown", nullable: true };
1851
+ }
1852
+ return source;
1853
+ }
1854
+ case "at-time-zone": {
1855
+ const conversion = expression.local ? "AT LOCAL" : "AT TIME ZONE";
1856
+ if (expression.local)
1857
+ this.#requireServerMajor(17, "AT LOCAL", expression.range);
1858
+ const source = this.#resolveExpression(expression.expression, scope, ctes);
1859
+ const zone = expression.zone === undefined
1860
+ ? undefined
1861
+ : this.#resolveExpression(expression.zone, scope, ctes, this.#databaseType("text", true));
1862
+ const sourceType = source.databaseType === undefined ? undefined : postgresCanonicalType(source.databaseType, this.#schema);
1863
+ const zoneType = zone?.databaseType === undefined ? undefined : postgresCanonicalType(zone.databaseType, this.#schema);
1864
+ const validZone = expression.local ||
1865
+ (zoneType !== undefined &&
1866
+ (zoneType === "interval" || postgresCanCoerce(zoneType, "text", "implicit", this.#schema)));
1867
+ const resultType = sourceType === "timestamp"
1868
+ ? "timestamptz"
1869
+ : sourceType === "timestamptz"
1870
+ ? "timestamp"
1871
+ : sourceType === "timetz"
1872
+ ? "timetz"
1873
+ : undefined;
1874
+ if (resultType === undefined) {
1875
+ this.#diagnostic("TSQ203", `${conversion} requires timestamp, timestamp with time zone, or time with time zone; received ${source.databaseType ?? source.tsType}`, expression.expression.range);
1876
+ }
1877
+ if (!validZone) {
1878
+ this.#diagnostic("TSQ203", `${conversion} requires a text or interval zone, received ${zone?.databaseType ?? zone?.tsType ?? "unknown"}`, expression.zone?.range ?? expression.range);
1879
+ }
1880
+ if (resultType === undefined || !validZone)
1881
+ return { tsType: "unknown", nullable: true };
1882
+ return {
1883
+ tsType: mapPostgresType(resultType, this.#policy, this.#schema),
1884
+ nullable: source.nullable || (zone?.nullable ?? false),
1885
+ databaseType: resultType,
1886
+ };
1887
+ }
417
1888
  case "cast": {
418
1889
  const castType = this.#databaseType(expression.databaseType.name, true);
419
1890
  const source = this.#resolveExpression(expression.expression, scope, ctes, castType);
420
1891
  if (!isKnownPostgresType(expression.databaseType.name, this.#schema)) {
421
1892
  this.#diagnostic("TSQ106", `Invalid or unknown PostgreSQL cast type ${expression.databaseType.name}`, expression.databaseType.range);
422
1893
  }
1894
+ else if (source.databaseType !== undefined &&
1895
+ !this.#isUnknownCastSource(expression.expression) &&
1896
+ !postgresCanCoerce(source.databaseType, expression.databaseType.name, "explicit", this.#schema)) {
1897
+ this.#diagnostic("TSQ230", `PostgreSQL has no recorded explicit cast from ${source.databaseType} to ${expression.databaseType.name}`, expression.range, "Use a supported intermediate type or add exact extension cast evidence.");
1898
+ }
423
1899
  return {
424
1900
  tsType: mapPostgresType(expression.databaseType.name, this.#policy, this.#schema),
425
1901
  nullable: source.nullable,
426
- databaseType: normalizeDatabaseType(expression.databaseType.name),
1902
+ databaseType: postgresCanonicalType(expression.databaseType.name, this.#schema),
427
1903
  };
428
1904
  }
429
1905
  case "unary": {
430
- const operand = this.#resolveExpression(expression.expression, scope, ctes);
431
- return expression.operator === "NOT"
432
- ? { tsType: "boolean", nullable: operand.nullable, databaseType: "boolean" }
433
- : operand;
1906
+ let operand = this.#resolveExpression(expression.expression, scope, ctes, expression.operator === "NOT" ? this.#databaseType("boolean", false) : undefined);
1907
+ const operator = resolvePostgresUnaryOperator(expression.operator, this.#overloadInputType(expression.expression, operand), this.#schema);
1908
+ if (operator.kind !== "selected") {
1909
+ this.#diagnostic("TSQ203", `${operator.kind === "ambiguous" ? "Ambiguous" : "No matching"} unary operator ${expression.operator} for ${operand.databaseType ?? operand.tsType}`, expression.range, operator.kind === "ambiguous" ? "Cast the operand to select a specific overload." : undefined);
1910
+ return { tsType: "unknown", nullable: true };
1911
+ }
1912
+ if (this.#acceptsTypeContext(expression.expression)) {
1913
+ operand = this.#resolveExpression(expression.expression, scope, ctes, this.#databaseType(operator.argumentTypes[0], operand.nullable));
1914
+ }
1915
+ return {
1916
+ tsType: mapPostgresType(operator.resultType, this.#policy, this.#schema),
1917
+ nullable: operand.nullable,
1918
+ databaseType: operator.resultType,
1919
+ };
434
1920
  }
435
1921
  case "binary":
436
1922
  return this.#resolveBinary(expression, scope, ctes);
1923
+ case "json-object": {
1924
+ this.#requireServerMajor(16, "JSON_OBJECT constructor", expression.range);
1925
+ let inputsValid = true;
1926
+ for (const entry of expression.entries) {
1927
+ const keyValid = this.#resolveJsonObjectKey(entry.key, scope, ctes, "JSON_OBJECT");
1928
+ inputsValid = keyValid && inputsValid;
1929
+ const valueValid = this.#resolveJsonConstructorValue(entry.value, scope, ctes);
1930
+ inputsValid = valueValid && inputsValid;
1931
+ }
1932
+ const output = this.#resolveJsonConstructorOutput(expression.returning, "JSON_OBJECT", expression.range);
1933
+ return inputsValid && output.valid
1934
+ ? output.resolved
1935
+ : { tsType: "unknown", nullable: true, databaseType: "unknown" };
1936
+ }
1937
+ case "json-object-aggregate": {
1938
+ this.#requireServerMajor(16, "JSON_OBJECTAGG aggregate", expression.range);
1939
+ const keyValid = this.#resolveJsonObjectKey(expression.key, scope, ctes, "JSON_OBJECTAGG");
1940
+ const valueValid = this.#resolveJsonAggregateValue(expression.value, scope, ctes);
1941
+ this.#resolveJsonAggregateSuffix(expression, scope, ctes);
1942
+ const output = this.#resolveJsonConstructorOutput(expression.returning, "JSON_OBJECTAGG", expression.range, true);
1943
+ return keyValid && valueValid && output.valid
1944
+ ? output.resolved
1945
+ : { tsType: "unknown", nullable: true, databaseType: "unknown" };
1946
+ }
1947
+ case "json-array-aggregate": {
1948
+ this.#requireServerMajor(16, "JSON_ARRAYAGG aggregate", expression.range);
1949
+ const valueValid = this.#resolveJsonAggregateValue(expression.value, scope, ctes);
1950
+ for (const item of expression.orderBy)
1951
+ this.#resolveExpression(item.expression, scope, ctes);
1952
+ this.#resolveJsonAggregateSuffix(expression, scope, ctes);
1953
+ const output = this.#resolveJsonConstructorOutput(expression.returning, "JSON_ARRAYAGG", expression.range, true);
1954
+ return valueValid && output.valid
1955
+ ? output.resolved
1956
+ : { tsType: "unknown", nullable: true, databaseType: "unknown" };
1957
+ }
1958
+ case "json-array": {
1959
+ this.#requireServerMajor(16, "JSON_ARRAY constructor", expression.range);
1960
+ let inputsValid = true;
1961
+ for (const value of expression.values) {
1962
+ const valueValid = this.#resolveJsonConstructorValue(value, scope, ctes);
1963
+ inputsValid = valueValid && inputsValid;
1964
+ }
1965
+ if (expression.query !== undefined) {
1966
+ const query = this.#resolveStatement(expression.query, scope, ctes);
1967
+ if (query.columns.length !== 1) {
1968
+ this.#diagnostic("TSQ216", `JSON_ARRAY query returns ${query.columns.length} columns instead of one`, expression.query.range);
1969
+ inputsValid = false;
1970
+ }
1971
+ else if (expression.queryFormat !== undefined) {
1972
+ const formatValid = this.#validateSqlJsonFormat(expression.queryFormat, query.columns[0], "JSON_ARRAY query");
1973
+ inputsValid = formatValid && inputsValid;
1974
+ }
1975
+ }
1976
+ const output = this.#resolveJsonConstructorOutput(expression.returning, "JSON_ARRAY", expression.range);
1977
+ return inputsValid && output.valid
1978
+ ? output.resolved
1979
+ : { tsType: "unknown", nullable: true, databaseType: "unknown" };
1980
+ }
1981
+ case "json-parse": {
1982
+ if (this.#serverMajor !== undefined && this.#serverMajor < 17) {
1983
+ if (expression.value.format !== undefined || expression.uniqueKeys) {
1984
+ this.#requireServerMajor(17, "JSON constructor", expression.range);
1985
+ this.#resolveExpression(expression.value.expression, scope, ctes);
1986
+ return { tsType: "unknown", nullable: true, databaseType: "unknown" };
1987
+ }
1988
+ const source = this.#resolveExpression(expression.value.expression, scope, ctes);
1989
+ const nullLiteral = expression.value.expression.kind === "literal" && expression.value.expression.value === null;
1990
+ if (source.databaseType === undefined && !nullLiteral) {
1991
+ this.#diagnostic("TSQ202", "PostgreSQL before 17 cannot resolve this functional json cast input", expression.value.expression.range);
1992
+ return { tsType: "unknown", nullable: true, databaseType: "unknown" };
1993
+ }
1994
+ if (source.databaseType !== undefined &&
1995
+ !this.#isUnknownCastSource(expression.value.expression) &&
1996
+ !postgresCanCoerce(source.databaseType, "json", "explicit", this.#schema)) {
1997
+ this.#diagnostic("TSQ230", `PostgreSQL has no recorded explicit cast from ${source.databaseType} to json`, expression.range);
1998
+ return { tsType: "unknown", nullable: true, databaseType: "unknown" };
1999
+ }
2000
+ return { tsType: this.#policy.json, nullable: source.nullable, databaseType: "json" };
2001
+ }
2002
+ this.#requireServerMajor(17, "JSON constructor", expression.range);
2003
+ const input = this.#resolveJsonStringInput(expression.value, scope, ctes, "JSON constructor");
2004
+ return input.valid
2005
+ ? { tsType: this.#policy.json, nullable: input.resolved.nullable, databaseType: "json" }
2006
+ : { tsType: "unknown", nullable: true, databaseType: "unknown" };
2007
+ }
2008
+ case "json-scalar": {
2009
+ this.#requireServerMajor(17, "JSON_SCALAR", expression.range);
2010
+ const input = this.#resolveExpression(expression.expression, scope, ctes, this.#databaseType("text", true));
2011
+ return input.databaseType !== undefined || input.tsType !== "unknown"
2012
+ ? { tsType: this.#policy.json, nullable: input.nullable, databaseType: "json" }
2013
+ : { tsType: "unknown", nullable: true, databaseType: "unknown" };
2014
+ }
2015
+ case "json-serialize": {
2016
+ this.#requireServerMajor(17, "JSON_SERIALIZE", expression.range);
2017
+ const input = this.#resolveJsonStringInput(expression.value, scope, ctes, "JSON_SERIALIZE");
2018
+ const output = this.#resolveJsonSerializeOutput(expression.returning, expression.range, input.resolved.nullable);
2019
+ return input.valid && output.valid
2020
+ ? output.resolved
2021
+ : { tsType: "unknown", nullable: true, databaseType: "unknown" };
2022
+ }
2023
+ case "json-is": {
2024
+ this.#requireServerMajor(16, "IS JSON predicate", expression.range);
2025
+ const input = this.#resolveJsonStringInput({ expression: expression.expression, range: expression.expression.range }, scope, ctes, "IS JSON predicate");
2026
+ return input.valid
2027
+ ? { tsType: "boolean", nullable: input.resolved.nullable, databaseType: "boolean" }
2028
+ : { tsType: "unknown", nullable: true, databaseType: "unknown" };
2029
+ }
2030
+ case "json-exists": {
2031
+ this.#requireServerMajor(17, "JSON_EXISTS", expression.range);
2032
+ const context = this.#resolveSqlJsonValue(expression.context, scope, ctes, true);
2033
+ const path = this.#resolveExpression(expression.path, scope, ctes, this.#databaseType("jsonpath", true));
2034
+ const pathType = path.databaseType === undefined ? undefined : postgresCanonicalType(path.databaseType, this.#schema);
2035
+ const pathValid = pathType === "jsonpath" ||
2036
+ (pathType !== undefined && postgresTypeCategory(pathType, this.#schema) === "string") ||
2037
+ (expression.path.kind === "literal" && expression.path.value === null);
2038
+ if (!pathValid) {
2039
+ this.#diagnostic("TSQ203", `JSON_EXISTS path must resolve to jsonpath or a character string, received ${path.databaseType ?? path.tsType}`, expression.path.range);
2040
+ }
2041
+ const passing = expression.passing.map(({ value }) => this.#resolveSqlJsonValue(value, scope, ctes, false));
2042
+ if (!context.valid || !pathValid || passing.some(({ valid }) => !valid)) {
2043
+ return { tsType: "unknown", nullable: true };
2044
+ }
2045
+ return {
2046
+ tsType: "boolean",
2047
+ nullable: context.resolved.nullable || path.nullable || expression.onError === "unknown",
2048
+ databaseType: "boolean",
2049
+ };
2050
+ }
2051
+ case "json-query": {
2052
+ this.#requireServerMajor(17, "JSON_QUERY", expression.range);
2053
+ const context = this.#resolveSqlJsonValue(expression.context, scope, ctes, true);
2054
+ const path = this.#resolveExpression(expression.path, scope, ctes, this.#databaseType("jsonpath", true));
2055
+ const pathType = path.databaseType === undefined ? undefined : postgresCanonicalType(path.databaseType, this.#schema);
2056
+ const pathValid = pathType === "jsonpath" ||
2057
+ (pathType !== undefined && postgresTypeCategory(pathType, this.#schema) === "string") ||
2058
+ (expression.path.kind === "literal" && expression.path.value === null);
2059
+ if (!pathValid) {
2060
+ this.#diagnostic("TSQ203", `JSON_QUERY path must resolve to jsonpath or a character string, received ${path.databaseType ?? path.tsType}`, expression.path.range);
2061
+ }
2062
+ const passing = expression.passing.map(({ value }) => this.#resolveSqlJsonValue(value, scope, ctes, false));
2063
+ const outputType = expression.returning?.databaseType.name ?? "jsonb";
2064
+ const outputKnown = isKnownPostgresType(outputType, this.#schema);
2065
+ if (!outputKnown) {
2066
+ this.#diagnostic("TSQ106", `Invalid or unknown PostgreSQL JSON_QUERY return type ${outputType}`, expression.returning?.databaseType.range ?? expression.range);
2067
+ }
2068
+ const canonicalOutput = outputKnown ? postgresCanonicalType(outputType, this.#schema) : undefined;
2069
+ let outputValid = outputKnown;
2070
+ const outputFormat = expression.returning?.format;
2071
+ if (outputFormat !== undefined) {
2072
+ const mapping = canonicalOutput === undefined ? undefined : postgresCatalogTypeMapping(canonicalOutput, this.#schema);
2073
+ if (mapping !== "string" && mapping !== "bytes" && mapping !== "json") {
2074
+ this.#diagnostic("TSQ203", `JSON_QUERY FORMAT JSON requires a string, bytea, json, or jsonb return type, received ${outputType}`, outputFormat.range);
2075
+ outputValid = false;
2076
+ }
2077
+ if (outputFormat.encoding !== undefined) {
2078
+ if (outputFormat.encoding !== "UTF8") {
2079
+ this.#diagnostic("TSQ203", `PostgreSQL JSON ENCODING supports UTF8, received ${outputFormat.encoding}`, outputFormat.range);
2080
+ outputValid = false;
2081
+ }
2082
+ if (canonicalOutput !== "bytea") {
2083
+ this.#diagnostic("TSQ203", `JSON ENCODING requires bytea output, received ${outputType}`, outputFormat.range);
2084
+ outputValid = false;
2085
+ }
2086
+ }
2087
+ }
2088
+ if (expression.quotes === "omit" && expression.wrapper !== undefined && expression.wrapper !== "without") {
2089
+ this.#diagnostic("TSQ203", "JSON_QUERY QUOTES behavior cannot be combined with WITH WRAPPER", expression.range);
2090
+ outputValid = false;
2091
+ }
2092
+ const expectedOutput = canonicalOutput === undefined ? undefined : this.#databaseType(canonicalOutput, true);
2093
+ const onEmpty = this.#resolveJsonQueryBehavior(expression.onEmpty, expectedOutput, scope, ctes);
2094
+ const onError = this.#resolveJsonQueryBehavior(expression.onError, expectedOutput, scope, ctes);
2095
+ if (!context.valid ||
2096
+ !pathValid ||
2097
+ passing.some(({ valid }) => !valid) ||
2098
+ !outputValid ||
2099
+ !onEmpty.valid ||
2100
+ !onError.valid) {
2101
+ return { tsType: "unknown", nullable: true };
2102
+ }
2103
+ return {
2104
+ tsType: mapPostgresType(canonicalOutput, this.#policy, this.#schema),
2105
+ nullable: context.resolved.nullable || path.nullable || onEmpty.nullable || onError.nullable,
2106
+ databaseType: canonicalOutput,
2107
+ };
2108
+ }
2109
+ case "json-value": {
2110
+ this.#requireServerMajor(17, "JSON_VALUE", expression.range);
2111
+ const context = this.#resolveSqlJsonValue(expression.context, scope, ctes, true);
2112
+ const path = this.#resolveExpression(expression.path, scope, ctes, this.#databaseType("jsonpath", true));
2113
+ const pathType = path.databaseType === undefined ? undefined : postgresCanonicalType(path.databaseType, this.#schema);
2114
+ const pathValid = pathType === "jsonpath" ||
2115
+ (pathType !== undefined && postgresTypeCategory(pathType, this.#schema) === "string") ||
2116
+ (expression.path.kind === "literal" && expression.path.value === null);
2117
+ if (!pathValid) {
2118
+ this.#diagnostic("TSQ203", `JSON_VALUE path must resolve to jsonpath or a character string, received ${path.databaseType ?? path.tsType}`, expression.path.range);
2119
+ }
2120
+ const passing = expression.passing.map(({ value }) => this.#resolveSqlJsonValue(value, scope, ctes, false));
2121
+ const outputType = expression.returning?.databaseType.name ?? "text";
2122
+ const outputKnown = isKnownPostgresType(outputType, this.#schema);
2123
+ if (!outputKnown) {
2124
+ this.#diagnostic("TSQ106", `Invalid or unknown PostgreSQL JSON_VALUE return type ${outputType}`, expression.returning?.databaseType.range ?? expression.range);
2125
+ }
2126
+ const canonicalOutput = outputKnown ? postgresCanonicalType(outputType, this.#schema) : undefined;
2127
+ let outputValid = outputKnown;
2128
+ if (expression.returning?.format !== undefined) {
2129
+ this.#diagnostic("TSQ203", "JSON_VALUE does not allow FORMAT JSON in its RETURNING clause", expression.returning.format.range);
2130
+ outputValid = false;
2131
+ }
2132
+ const expectedOutput = canonicalOutput === undefined ? undefined : this.#databaseType(canonicalOutput, true);
2133
+ const onEmpty = this.#resolveJsonQueryBehavior(expression.onEmpty, expectedOutput, scope, ctes, false);
2134
+ const onError = this.#resolveJsonQueryBehavior(expression.onError, expectedOutput, scope, ctes, false);
2135
+ if (!context.valid ||
2136
+ !pathValid ||
2137
+ passing.some(({ valid }) => !valid) ||
2138
+ !outputValid ||
2139
+ !onEmpty.valid ||
2140
+ !onError.valid) {
2141
+ return { tsType: "unknown", nullable: true };
2142
+ }
2143
+ return {
2144
+ tsType: mapPostgresType(canonicalOutput, this.#policy, this.#schema),
2145
+ nullable: true,
2146
+ databaseType: canonicalOutput,
2147
+ };
2148
+ }
437
2149
  case "call":
438
2150
  return this.#resolveCall(expression, scope, ctes);
439
2151
  case "case": {
@@ -454,7 +2166,7 @@ class Resolver {
454
2166
  };
455
2167
  }
456
2168
  case "subquery": {
457
- const resolved = this.#resolveStatement(expression.query, scope, ctes);
2169
+ const resolved = this.#resolveStatement(expression.query, scope, ctes, expected === undefined ? undefined : [expected]);
458
2170
  if (resolved.columns.length !== 1) {
459
2171
  this.#diagnostic("TSQ216", `Scalar subquery returns ${resolved.columns.length} columns instead of one`, expression.range);
460
2172
  return { tsType: "unknown", nullable: true };
@@ -470,21 +2182,53 @@ class Resolver {
470
2182
  this.#resolveStatement(expression.query, scope, ctes);
471
2183
  return { tsType: "boolean", nullable: false, databaseType: "boolean" };
472
2184
  }
473
- case "in": {
474
- const subject = this.#resolveExpression(expression.expression, scope, ctes);
475
- let nullable = subject.nullable;
476
- if (Array.isArray(expression.values)) {
477
- const values = expression.values.map((value) => this.#resolveExpression(value, scope, ctes, subject));
478
- nullable ||= values.some((value) => value.nullable);
2185
+ case "quantified-comparison": {
2186
+ if (expression.left.kind === "row" && expression.right.kind === "select") {
2187
+ return this.#resolveQuantifiedRowSubquery(expression, scope, ctes);
479
2188
  }
480
- else {
481
- const resolved = this.#resolveStatement(expression.values, scope, ctes);
482
- if (resolved.columns.length !== 1)
483
- this.#diagnostic("TSQ217", `IN subquery returns ${resolved.columns.length} columns instead of one`, expression.range);
2189
+ let left = this.#resolveExpression(expression.left, scope, ctes);
2190
+ let rightExpression;
2191
+ let rightType;
2192
+ let nullable = left.nullable;
2193
+ if (expression.right.kind === "select") {
2194
+ const resolved = this.#resolveStatement(expression.right, scope, ctes);
2195
+ if (resolved.columns.length !== 1) {
2196
+ this.#diagnostic("TSQ217", `${expression.quantifier.toUpperCase()} subquery returns ${resolved.columns.length} columns instead of one`, expression.range);
2197
+ }
2198
+ rightType = resolved.columns[0]?.databaseType;
484
2199
  nullable ||= resolved.columns[0]?.nullable ?? true;
485
2200
  }
2201
+ else {
2202
+ rightExpression = this.#resolveExpression(expression.right, scope, ctes);
2203
+ rightType =
2204
+ rightExpression.databaseType === undefined
2205
+ ? undefined
2206
+ : postgresElementType(rightExpression.databaseType, this.#schema);
2207
+ if (rightExpression.databaseType !== undefined && rightType === undefined) {
2208
+ this.#diagnostic("TSQ203", `Quantified comparison requires an array, received ${rightExpression.databaseType}`, expression.right.range);
2209
+ return { tsType: "unknown", nullable: true };
2210
+ }
2211
+ nullable = true;
2212
+ }
2213
+ if (expression.right.kind === "select" && rightType === undefined) {
2214
+ this.#diagnostic("TSQ203", "Quantified subquery result type is unknown", expression.right.range);
2215
+ return { tsType: "unknown", nullable: true };
2216
+ }
2217
+ const operator = resolvePostgresOperator(expression.operator, this.#overloadInputType(expression.left, left), rightType, this.#schema);
2218
+ if (operator.kind !== "selected") {
2219
+ this.#diagnostic("TSQ203", `${operator.kind === "ambiguous" ? "Ambiguous" : "No matching"} quantified operator ${expression.operator} for ${left.databaseType ?? left.tsType} and ${rightType ?? rightExpression?.databaseType ?? "unknown collection"}`, expression.range, operator.kind === "ambiguous" ? "Cast the left operand or quantified input." : undefined);
2220
+ return { tsType: "unknown", nullable: true };
2221
+ }
2222
+ if (this.#acceptsTypeContext(expression.left)) {
2223
+ left = this.#resolveExpression(expression.left, scope, ctes, this.#databaseType(operator.argumentTypes[0], left.nullable));
2224
+ }
2225
+ if (expression.right.kind !== "select") {
2226
+ rightExpression = this.#resolveExpression(expression.right, scope, ctes, this.#databaseType(`${operator.argumentTypes[1]}[]`, rightExpression?.nullable ?? true));
2227
+ }
486
2228
  return { tsType: "boolean", nullable, databaseType: "boolean" };
487
2229
  }
2230
+ case "in":
2231
+ return this.#resolveIn(expression, scope, ctes);
488
2232
  case "between": {
489
2233
  const subject = this.#resolveExpression(expression.expression, scope, ctes);
490
2234
  const lower = this.#resolveExpression(expression.lower, scope, ctes, subject);
@@ -498,59 +2242,282 @@ class Resolver {
498
2242
  }
499
2243
  }
500
2244
  #resolveBinary(expression, scope, ctes) {
2245
+ if (expression.left.kind === "row" && expression.right.kind === "row") {
2246
+ return this.#resolveRowComparison(expression, scope, ctes);
2247
+ }
2248
+ if (expression.left.kind === "row" || expression.right.kind === "row") {
2249
+ this.#resolveExpression(expression.left, scope, ctes);
2250
+ this.#resolveExpression(expression.right, scope, ctes);
2251
+ this.#diagnostic("TSQ203", "Row comparison requires a row constructor on both sides", expression.range);
2252
+ return { tsType: "unknown", nullable: true };
2253
+ }
501
2254
  let left;
502
2255
  let right;
503
- if (expression.left.kind === "parameter" && expression.right.kind !== "parameter") {
2256
+ if (this.#acceptsTypeContext(expression.left) && !this.#acceptsTypeContext(expression.right)) {
504
2257
  right = this.#resolveExpression(expression.right, scope, ctes);
505
- left = this.#resolveExpression(expression.left, scope, ctes, right);
2258
+ left = this.#resolveExpression(expression.left, scope, ctes);
506
2259
  }
507
2260
  else {
508
2261
  left = this.#resolveExpression(expression.left, scope, ctes);
509
- right = this.#resolveExpression(expression.right, scope, ctes, expression.right.kind === "parameter" ? left : undefined);
2262
+ right = this.#resolveExpression(expression.right, scope, ctes);
2263
+ }
2264
+ if (expression.operator === "IS" || expression.operator === "IS NOT") {
2265
+ return { tsType: "boolean", nullable: false, databaseType: "boolean" };
510
2266
  }
511
- if (booleanOperators.has(expression.operator)) {
512
- const neverNull = expression.operator.startsWith("IS");
2267
+ const operator = resolvePostgresOperator(expression.operator, this.#overloadInputType(expression.left, left), this.#overloadInputType(expression.right, right), this.#schema);
2268
+ if (operator.kind === "selected") {
2269
+ const invalidNumericLiteral = [expression.left, expression.right].some((operand, index) => {
2270
+ if (operand.kind !== "literal" || typeof operand.value !== "string")
2271
+ return false;
2272
+ const target = operator.argumentTypes[index];
2273
+ if (!this.#isNumericType(target))
2274
+ return false;
2275
+ const integer = ["smallint", "integer", "bigint"].includes(normalizeDatabaseType(target));
2276
+ return integer
2277
+ ? !/^[+-]?\d+$/u.test(operand.value)
2278
+ : !/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/iu.test(operand.value);
2279
+ });
2280
+ if (invalidNumericLiteral) {
2281
+ this.#diagnostic("TSQ203", `A string literal is not valid input for the selected ${expression.operator} numeric overload`, expression.range);
2282
+ return { tsType: "unknown", nullable: true };
2283
+ }
2284
+ if (this.#acceptsTypeContext(expression.left)) {
2285
+ left = this.#resolveExpression(expression.left, scope, ctes, this.#databaseType(operator.argumentTypes[0], right.nullable));
2286
+ }
2287
+ if (this.#acceptsTypeContext(expression.right)) {
2288
+ right = this.#resolveExpression(expression.right, scope, ctes, this.#databaseType(operator.argumentTypes[1], left.nullable));
2289
+ }
513
2290
  return {
514
- tsType: "boolean",
515
- nullable: neverNull ? false : left.nullable || right.nullable,
516
- databaseType: "boolean",
2291
+ tsType: mapPostgresType(operator.resultType, this.#policy, this.#schema),
2292
+ nullable: expression.operator.startsWith("IS ")
2293
+ ? false
2294
+ : ["->", "->>", "#>", "#>>"].includes(expression.operator) || left.nullable || right.nullable,
2295
+ databaseType: operator.resultType,
517
2296
  };
518
2297
  }
519
- if (["->", "#>"].includes(expression.operator))
520
- return { tsType: this.#policy.json, nullable: true, databaseType: "jsonb" };
521
- if (["->>", "#>>"].includes(expression.operator))
522
- return { tsType: "string", nullable: true, databaseType: "text" };
523
- if (expression.operator === "||") {
524
- if (left.tsType.startsWith("readonly (") && right.tsType.startsWith("readonly (")) {
525
- const databaseType = left.databaseType ?? right.databaseType;
526
- return {
527
- tsType: unionTypeLiterals([left.tsType, right.tsType]),
528
- nullable: left.nullable || right.nullable,
529
- ...(databaseType === undefined ? {} : { databaseType }),
530
- };
2298
+ const compositeLeftType = left.databaseType ?? (this.#acceptsTypeContext(expression.left) ? right.databaseType : undefined);
2299
+ const compositeRightType = right.databaseType ?? (this.#acceptsTypeContext(expression.right) ? left.databaseType : undefined);
2300
+ if (compositeLeftType !== undefined &&
2301
+ compositeRightType !== undefined &&
2302
+ this.#compositeTypesComparable(compositeLeftType, compositeRightType, expression.operator)) {
2303
+ if (this.#acceptsTypeContext(expression.left)) {
2304
+ left = this.#resolveExpression(expression.left, scope, ctes, this.#databaseType(compositeLeftType, left.nullable));
2305
+ }
2306
+ if (this.#acceptsTypeContext(expression.right)) {
2307
+ right = this.#resolveExpression(expression.right, scope, ctes, this.#databaseType(compositeRightType, right.nullable));
531
2308
  }
532
- return { tsType: "string", nullable: left.nullable || right.nullable, databaseType: "text" };
533
- }
534
- const leftDatabase = left.databaseType === undefined ? undefined : normalizeDatabaseType(left.databaseType);
535
- const rightDatabase = right.databaseType === undefined ? undefined : normalizeDatabaseType(right.databaseType);
536
- if (leftDatabase !== undefined &&
537
- rightDatabase !== undefined &&
538
- numericTypes.has(leftDatabase) &&
539
- numericTypes.has(rightDatabase)) {
540
- const databaseType = [leftDatabase, rightDatabase].some((type) => type === "numeric" || type === "decimal")
541
- ? "numeric"
542
- : "double precision";
543
2309
  return {
544
- tsType: databaseType === "numeric" ? this.#policy.numeric : "number",
545
- nullable: left.nullable || right.nullable,
546
- databaseType,
2310
+ tsType: "boolean",
2311
+ nullable: expression.operator.includes("DISTINCT FROM") ? false : left.nullable || right.nullable,
2312
+ databaseType: "boolean",
547
2313
  };
548
2314
  }
549
- if (left.tsType === "unknown" || right.tsType === "unknown")
550
- return { tsType: "unknown", nullable: left.nullable || right.nullable };
551
- this.#diagnostic("TSQ203", `Cannot safely infer operator ${expression.operator} for ${leftDatabase ?? left.tsType} and ${rightDatabase ?? right.tsType}`, expression.range);
2315
+ this.#diagnostic("TSQ203", `${operator.kind === "ambiguous" ? "Ambiguous" : "No matching"} operator ${expression.operator} for ${left.databaseType ?? left.tsType} and ${right.databaseType ?? right.tsType}`, expression.range, operator.kind === "ambiguous" ? "Cast an operand to select a specific overload." : undefined);
552
2316
  return { tsType: "unknown", nullable: true };
553
2317
  }
2318
+ #resolveIn(expression, scope, ctes) {
2319
+ if (expression.expression.kind === "row")
2320
+ return this.#resolveRowIn(expression, scope, ctes);
2321
+ let subject = this.#resolveExpression(expression.expression, scope, ctes);
2322
+ let nullable = subject.nullable;
2323
+ if (Array.isArray(expression.values)) {
2324
+ for (const valueExpression of expression.values) {
2325
+ let value = this.#resolveExpression(valueExpression, scope, ctes, subject);
2326
+ const compared = this.#resolveEqualityPair(expression.expression, subject, valueExpression, value, expression.range, "IN value", scope, ctes);
2327
+ subject = compared.left;
2328
+ value = compared.right;
2329
+ nullable ||= value.nullable;
2330
+ }
2331
+ }
2332
+ else {
2333
+ const resolved = this.#resolveStatement(expression.values, scope, ctes, [subject]);
2334
+ if (resolved.columns.length !== 1) {
2335
+ this.#diagnostic("TSQ217", `IN subquery returns ${resolved.columns.length} columns instead of one`, expression.range);
2336
+ }
2337
+ else {
2338
+ const compared = this.#resolveEqualityPair(expression.expression, subject, undefined, resolved.columns[0], expression.range, "IN subquery", scope, ctes);
2339
+ subject = compared.left;
2340
+ nullable ||= compared.right.nullable;
2341
+ }
2342
+ }
2343
+ return { tsType: "boolean", nullable, databaseType: "boolean" };
2344
+ }
2345
+ #resolveRowIn(expression, scope, ctes) {
2346
+ const row = expression.expression;
2347
+ const leftExpressions = row.elements;
2348
+ const left = leftExpressions.map((element) => this.#resolveExpression(element, scope, ctes));
2349
+ let nullable = left.some((element) => element.nullable);
2350
+ const compare = (rightExpressions, right, range) => {
2351
+ if (left.length !== right.length) {
2352
+ this.#diagnostic("TSQ217", `Row IN comparison requires ${left.length} columns, received ${right.length}`, range);
2353
+ nullable = true;
2354
+ return;
2355
+ }
2356
+ left.forEach((leftType, index) => {
2357
+ const compared = this.#resolveEqualityPair(leftExpressions[index], leftType, rightExpressions[index], right[index], range, `row IN position ${index + 1}`, scope, ctes);
2358
+ left[index] = compared.left;
2359
+ nullable ||= compared.left.nullable || compared.right.nullable;
2360
+ });
2361
+ };
2362
+ if (Array.isArray(expression.values)) {
2363
+ for (const valueExpression of expression.values) {
2364
+ if (valueExpression.kind !== "row") {
2365
+ const value = this.#resolveExpression(valueExpression, scope, ctes);
2366
+ this.#diagnostic("TSQ203", "A row-valued IN list requires row values", valueExpression.range);
2367
+ nullable ||= value.nullable;
2368
+ continue;
2369
+ }
2370
+ const right = valueExpression.elements.map((element, index) => this.#resolveExpression(element, scope, ctes, left[index]));
2371
+ compare(valueExpression.elements, right, valueExpression.range);
2372
+ }
2373
+ }
2374
+ else {
2375
+ const resolved = this.#resolveStatement(expression.values, scope, ctes, left);
2376
+ compare(resolved.columns.map(() => undefined), resolved.columns, expression.range);
2377
+ }
2378
+ return { tsType: "boolean", nullable, databaseType: "boolean" };
2379
+ }
2380
+ #resolveEqualityPair(leftExpression, initialLeft, rightExpression, initialRight, range, label, scope, ctes) {
2381
+ let left = initialLeft;
2382
+ let right = initialRight;
2383
+ if ((left.databaseType === undefined && !this.#isUnknownOverloadExpression(leftExpression)) ||
2384
+ (right.databaseType === undefined &&
2385
+ (rightExpression === undefined || !this.#isUnknownOverloadExpression(rightExpression)))) {
2386
+ this.#diagnostic("TSQ203", `No recorded equality type for ${label}: ${left.databaseType ?? left.tsType} and ${right.databaseType ?? right.tsType}`, range);
2387
+ return { left, right };
2388
+ }
2389
+ const operator = resolvePostgresOperator("=", this.#overloadInputType(leftExpression, left), rightExpression === undefined ? right.databaseType : this.#overloadInputType(rightExpression, right), this.#schema);
2390
+ let argumentTypes = operator.kind === "selected" ? operator.argumentTypes : undefined;
2391
+ const compositeLeftType = left.databaseType ?? (this.#acceptsTypeContext(leftExpression) ? right.databaseType : undefined);
2392
+ const compositeRightType = right.databaseType ??
2393
+ (rightExpression !== undefined && this.#acceptsTypeContext(rightExpression) ? left.databaseType : undefined);
2394
+ if (argumentTypes === undefined &&
2395
+ compositeLeftType !== undefined &&
2396
+ compositeRightType !== undefined &&
2397
+ this.#compositeTypesComparable(compositeLeftType, compositeRightType, "=")) {
2398
+ argumentTypes = [compositeLeftType, compositeRightType];
2399
+ }
2400
+ if (argumentTypes === undefined) {
2401
+ this.#diagnostic("TSQ203", `${operator.kind === "ambiguous" ? "Ambiguous" : "No matching"} equality operator for ${label}: ${left.databaseType ?? left.tsType} and ${right.databaseType ?? right.tsType}`, range, operator.kind === "ambiguous" ? "Cast an operand to select a specific equality overload." : undefined);
2402
+ return { left, right };
2403
+ }
2404
+ const invalidNumericLiteral = (candidate, databaseType) => candidate?.kind === "literal" &&
2405
+ typeof candidate.value === "string" &&
2406
+ this.#isNumericType(databaseType) &&
2407
+ !this.#validNumericLiteral(candidate.value, databaseType);
2408
+ if (invalidNumericLiteral(leftExpression, argumentTypes[0]) ||
2409
+ invalidNumericLiteral(rightExpression, argumentTypes[1])) {
2410
+ this.#diagnostic("TSQ203", `A string literal is not valid input for ${label}'s numeric type`, range);
2411
+ return { left, right };
2412
+ }
2413
+ if (this.#acceptsTypeContext(leftExpression)) {
2414
+ left = this.#resolveExpression(leftExpression, scope, ctes, this.#databaseType(argumentTypes[0], left.nullable));
2415
+ }
2416
+ if (rightExpression !== undefined && this.#acceptsTypeContext(rightExpression)) {
2417
+ right = this.#resolveExpression(rightExpression, scope, ctes, this.#databaseType(argumentTypes[1], right.nullable));
2418
+ }
2419
+ return { left, right };
2420
+ }
2421
+ #compositeTypesComparable(leftType, rightType, operator) {
2422
+ const schema = this.#schema;
2423
+ if (schema.formatVersion !== 2)
2424
+ return false;
2425
+ const composite = (databaseType) => {
2426
+ const canonical = postgresCanonicalType(databaseType, schema);
2427
+ return Object.values(schema.types).find((type) => {
2428
+ if (type.kind !== "composite")
2429
+ return false;
2430
+ const qualified = type.schema === undefined ? type.name : `${type.schema}.${type.name}`;
2431
+ return [type.databaseType, type.identity, qualified].some((identity) => normalizeDatabaseType(identity) === normalizeDatabaseType(canonical));
2432
+ });
2433
+ };
2434
+ const left = composite(leftType);
2435
+ const right = composite(rightType);
2436
+ if (left === undefined || right === undefined || left.fields.length !== right.fields.length)
2437
+ return false;
2438
+ return left.fields.every((field, index) => {
2439
+ const candidate = resolvePostgresOperator(operator, field.databaseType, right.fields[index].databaseType, schema);
2440
+ return candidate.kind === "selected";
2441
+ });
2442
+ }
2443
+ #resolveQuantifiedRowSubquery(expression, scope, ctes) {
2444
+ const leftRow = expression.left;
2445
+ const subquery = expression.right;
2446
+ const resolved = this.#resolveStatement(subquery, scope, ctes);
2447
+ if (leftRow.elements.length !== resolved.columns.length) {
2448
+ for (const element of leftRow.elements)
2449
+ this.#resolveExpression(element, scope, ctes);
2450
+ this.#diagnostic("TSQ217", `${expression.quantifier.toUpperCase()} row comparison requires ${leftRow.elements.length} subquery columns, received ${resolved.columns.length}`, expression.range);
2451
+ return { tsType: "unknown", nullable: true };
2452
+ }
2453
+ let nullable = false;
2454
+ leftRow.elements.forEach((leftExpression, index) => {
2455
+ let left = this.#resolveExpression(leftExpression, scope, ctes);
2456
+ const right = resolved.columns[index];
2457
+ const operator = resolvePostgresOperator(expression.operator, this.#overloadInputType(leftExpression, left), right.databaseType, this.#schema);
2458
+ let argumentTypes = operator.kind === "selected" ? operator.argumentTypes : undefined;
2459
+ const compositeLeftType = left.databaseType ?? (this.#acceptsTypeContext(leftExpression) ? right.databaseType : undefined);
2460
+ const compositeRightType = right.databaseType;
2461
+ if (argumentTypes === undefined &&
2462
+ compositeLeftType !== undefined &&
2463
+ compositeRightType !== undefined &&
2464
+ this.#compositeTypesComparable(compositeLeftType, compositeRightType, expression.operator)) {
2465
+ argumentTypes = [compositeLeftType, compositeRightType];
2466
+ }
2467
+ if (argumentTypes === undefined) {
2468
+ this.#diagnostic("TSQ203", `${operator.kind === "ambiguous" ? "Ambiguous" : "No matching"} quantified row operator ${expression.operator} for position ${index + 1}`, expression.range);
2469
+ }
2470
+ else if (this.#acceptsTypeContext(leftExpression)) {
2471
+ left = this.#resolveExpression(leftExpression, scope, ctes, this.#databaseType(argumentTypes[0], left.nullable));
2472
+ }
2473
+ nullable ||= left.nullable || right.nullable;
2474
+ });
2475
+ return { tsType: "boolean", nullable, databaseType: "boolean" };
2476
+ }
2477
+ #resolveRowComparison(expression, scope, ctes) {
2478
+ const leftRow = expression.left;
2479
+ const rightRow = expression.right;
2480
+ const supported = ["=", "!=", "<>", "<", "<=", ">", ">=", "IS DISTINCT FROM", "IS NOT DISTINCT FROM"].includes(expression.operator);
2481
+ if (!supported || leftRow.elements.length !== rightRow.elements.length) {
2482
+ for (const element of [...leftRow.elements, ...rightRow.elements])
2483
+ this.#resolveExpression(element, scope, ctes);
2484
+ this.#diagnostic("TSQ203", supported
2485
+ ? `Row comparison requires equal arity, received ${leftRow.elements.length} and ${rightRow.elements.length}`
2486
+ : `PostgreSQL does not support row comparison operator ${expression.operator}`, expression.range);
2487
+ return { tsType: "unknown", nullable: true };
2488
+ }
2489
+ let nullable = false;
2490
+ leftRow.elements.forEach((leftExpression, index) => {
2491
+ const rightExpression = rightRow.elements[index];
2492
+ let left = this.#resolveExpression(leftExpression, scope, ctes);
2493
+ let right = this.#resolveExpression(rightExpression, scope, ctes);
2494
+ const operator = resolvePostgresOperator(expression.operator, this.#overloadInputType(leftExpression, left), this.#overloadInputType(rightExpression, right), this.#schema);
2495
+ let argumentTypes = operator.kind === "selected" ? operator.argumentTypes : undefined;
2496
+ const compositeLeftType = left.databaseType ?? (this.#acceptsTypeContext(leftExpression) ? right.databaseType : undefined);
2497
+ const compositeRightType = right.databaseType ?? (this.#acceptsTypeContext(rightExpression) ? left.databaseType : undefined);
2498
+ if (argumentTypes === undefined &&
2499
+ compositeLeftType !== undefined &&
2500
+ compositeRightType !== undefined &&
2501
+ this.#compositeTypesComparable(compositeLeftType, compositeRightType, expression.operator)) {
2502
+ argumentTypes = [compositeLeftType, compositeRightType];
2503
+ }
2504
+ if (argumentTypes === undefined) {
2505
+ this.#diagnostic("TSQ203", `${operator.kind === "ambiguous" ? "Ambiguous" : "No matching"} row operator ${expression.operator} for position ${index + 1}`, expression.range);
2506
+ }
2507
+ else {
2508
+ if (this.#acceptsTypeContext(leftExpression))
2509
+ left = this.#resolveExpression(leftExpression, scope, ctes, this.#databaseType(argumentTypes[0], left.nullable));
2510
+ if (this.#acceptsTypeContext(rightExpression))
2511
+ right = this.#resolveExpression(rightExpression, scope, ctes, this.#databaseType(argumentTypes[1], right.nullable));
2512
+ }
2513
+ nullable ||= left.nullable || right.nullable;
2514
+ });
2515
+ return {
2516
+ tsType: "boolean",
2517
+ nullable: expression.operator.includes("DISTINCT FROM") ? false : nullable,
2518
+ databaseType: "boolean",
2519
+ };
2520
+ }
554
2521
  #resolveColumn(relationIdentifier, columnIdentifier, scope) {
555
2522
  const name = sqlName(columnIdentifier);
556
2523
  if (scope.relations.length === 0 && this.#diagnostics.some((diagnostic) => diagnostic.code === "TSQ100")) {
@@ -588,6 +2555,8 @@ class Resolver {
588
2555
  return relation === undefined || column === undefined ? [] : [[relation, column]];
589
2556
  }
590
2557
  return scope.relations.flatMap((relation) => {
2558
+ if (relation.qualifiedOnly === true)
2559
+ return [];
591
2560
  const column = this.#column(relation, name, quoted);
592
2561
  return column === undefined ? [] : [[relation, column]];
593
2562
  });
@@ -610,18 +2579,87 @@ class Resolver {
610
2579
  }
611
2580
  #resolveCall(expression, scope, ctes) {
612
2581
  const resolved = expression.arguments.map((argument) => this.#resolveExpression(argument, scope, ctes));
613
- if (expression.filter !== undefined)
614
- this.#resolveExpression(expression.filter, scope, ctes);
615
- if (expression.over !== undefined && "partitionBy" in expression.over) {
616
- for (const item of expression.over.partitionBy)
617
- this.#resolveExpression(item, scope, ctes);
618
- for (const item of expression.over.orderBy)
619
- this.#resolveExpression(item.expression, scope, ctes);
2582
+ const aggregateOrder = (expression.orderBy ?? []).map((item) => this.#resolveExpression(item.expression, scope, ctes));
2583
+ const withinOrder = (expression.withinGroup ?? []).map((item) => this.#resolveExpression(item.expression, scope, ctes));
2584
+ if (expression.filter !== undefined) {
2585
+ this.#resolveExpression(expression.filter, scope, ctes, this.#databaseType("boolean", false));
2586
+ }
2587
+ if ((expression.orderBy?.length ?? 0) > 0 && (expression.withinGroup?.length ?? 0) > 0) {
2588
+ this.#diagnostic("TSQ223", "An aggregate cannot use both argument ORDER BY and WITHIN GROUP", expression.range);
2589
+ }
2590
+ if (expression.over !== undefined) {
2591
+ if ("partitionBy" in expression.over)
2592
+ this.#resolveWindowSpecification(expression.over, scope, ctes);
2593
+ else if (!scope.windows.has(sqlName(expression.over))) {
2594
+ this.#diagnostic("TSQ222", `Unknown window ${expression.over.name}`, expression.over.range);
2595
+ }
620
2596
  }
621
2597
  const name = expression.name.name.toUpperCase();
622
- if (name === "COUNT")
2598
+ if (name === "MERGE_ACTION" && expression.arguments.length === 0) {
2599
+ this.#requireServerMajor(17, "merge_action()", expression.range);
2600
+ if (!this.#insideMergeReturning)
2601
+ this.#diagnostic("TSQ227", "merge_action() is only valid in MERGE RETURNING", expression.range);
2602
+ return { tsType: "string", nullable: false, databaseType: "text" };
2603
+ }
2604
+ const routineRule = postgresCatalogRoutineRule(name, this.#schema);
2605
+ const aggregate = this.#isAggregateCall(expression);
2606
+ const windowOnly = routineRule === "bigint-window" ||
2607
+ routineRule === "double-window" ||
2608
+ routineRule === "integer-window" ||
2609
+ routineRule === "value-window";
2610
+ const hypothetical = (expression.withinGroup?.length ?? 0) > 0 && hypotheticalAggregates.has(name);
2611
+ if (expression.distinct &&
2612
+ (expression.orderBy ?? []).some(({ expression: ordered }) => !expression.arguments.some((argument) => postgresExpressionIdentity(argument) === postgresExpressionIdentity(ordered)))) {
2613
+ this.#diagnostic("TSQ227", "DISTINCT aggregate ORDER BY expressions must appear in its arguments", expression.range);
2614
+ }
2615
+ if (name === "MODE" && (expression.arguments.length !== 0 || withinOrder.length !== 1)) {
2616
+ this.#diagnostic("TSQ227", "MODE requires no direct arguments and one WITHIN GROUP key", expression.range);
2617
+ }
2618
+ if ((name === "PERCENTILE_CONT" || name === "PERCENTILE_DISC") &&
2619
+ (expression.arguments.length !== 1 || withinOrder.length !== 1)) {
2620
+ this.#diagnostic("TSQ227", `${expression.name.name} requires one fraction and one WITHIN GROUP key`, expression.range);
2621
+ }
2622
+ if (hypothetical && (expression.arguments.length === 0 || expression.arguments.length !== withinOrder.length)) {
2623
+ this.#diagnostic("TSQ227", `${expression.name.name} hypothetical arguments must match its WITHIN GROUP keys`, expression.range);
2624
+ }
2625
+ if (windowOnly && expression.over === undefined && !hypothetical) {
2626
+ this.#diagnostic("TSQ223", `${expression.name.name} requires an OVER clause`, expression.range);
2627
+ }
2628
+ if ((expression.withinGroup?.length ?? 0) > 0 && routineRule !== "ordered-set-value" && !hypothetical) {
2629
+ this.#diagnostic("TSQ227", `${expression.name.name} does not accept WITHIN GROUP`, expression.range);
2630
+ }
2631
+ if (expression.over !== undefined &&
2632
+ (expression.distinct || (expression.orderBy?.length ?? 0) > 0 || (expression.withinGroup?.length ?? 0) > 0)) {
2633
+ this.#diagnostic("TSQ223", "Window invocations cannot use DISTINCT, argument ORDER BY, or WITHIN GROUP", expression.range);
2634
+ }
2635
+ if (expression.filter !== undefined && !aggregate) {
2636
+ this.#diagnostic("TSQ223", "FILTER is only valid for aggregate functions", expression.filter.range);
2637
+ }
2638
+ if (expression.distinct && !aggregate) {
2639
+ this.#diagnostic("TSQ223", "DISTINCT is only valid for aggregate functions", expression.range);
2640
+ }
2641
+ if ((expression.orderBy?.length ?? 0) > 0 && !aggregate) {
2642
+ this.#diagnostic("TSQ223", "Argument ORDER BY is only valid for aggregate functions", expression.range);
2643
+ }
2644
+ if (routineRule === "count")
2645
+ return { tsType: this.#policy.bigint, nullable: false, databaseType: "bigint" };
2646
+ if (routineRule === "bigint-window")
623
2647
  return { tsType: this.#policy.bigint, nullable: false, databaseType: "bigint" };
624
- if (name === "COALESCE") {
2648
+ if (routineRule === "double-window")
2649
+ return { tsType: "number", nullable: false, databaseType: "double precision" };
2650
+ if (routineRule === "integer-window" || routineRule === "grouping")
2651
+ return { tsType: "number", nullable: false, databaseType: "integer" };
2652
+ if (routineRule === "value-window") {
2653
+ return { ...(resolved[0] ?? { tsType: "unknown" }), nullable: true };
2654
+ }
2655
+ if (routineRule === "ordered-set-value") {
2656
+ if (withinOrder.length === 0) {
2657
+ this.#diagnostic("TSQ227", `${expression.name.name} requires WITHIN GROUP`, expression.range);
2658
+ return { tsType: "unknown", nullable: true };
2659
+ }
2660
+ return { ...withinOrder[0], nullable: true };
2661
+ }
2662
+ if (routineRule === "coalesce") {
625
2663
  const known = resolved.filter((_, index) => {
626
2664
  const argument = expression.arguments[index];
627
2665
  return argument?.kind !== "literal" || argument.value !== null;
@@ -636,27 +2674,60 @@ class Resolver {
636
2674
  : {}),
637
2675
  };
638
2676
  }
639
- if (name === "NULLIF")
2677
+ if (routineRule === "nullif")
640
2678
  return { ...(resolved[0] ?? { tsType: "unknown" }), nullable: true };
641
- if (["MIN", "MAX", "GREATEST", "LEAST"].includes(name))
2679
+ if (routineRule === "extrema")
642
2680
  return {
643
2681
  ...(resolved[0] ?? { tsType: "unknown", nullable: true }),
644
2682
  nullable: name === "MIN" || name === "MAX" ? true : resolved.some((result) => result.nullable),
645
2683
  };
646
- if (name === "SUM" || name === "AVG")
2684
+ if (routineRule === "numeric-aggregate")
647
2685
  return {
648
2686
  tsType: resolved[0]?.tsType ?? this.#policy.numeric,
649
2687
  nullable: true,
650
2688
  databaseType: resolved[0]?.databaseType ?? "numeric",
651
2689
  };
652
- if (["BOOL_AND", "BOOL_OR", "EVERY"].includes(name))
2690
+ if (routineRule === "boolean-aggregate")
653
2691
  return { tsType: "boolean", nullable: true, databaseType: "boolean" };
654
- if (name === "STRING_AGG")
2692
+ if (routineRule === "string-aggregate")
655
2693
  return { tsType: "string", nullable: true, databaseType: "text" };
656
- if (["JSON_AGG", "JSONB_AGG", "JSON_OBJECT_AGG", "JSONB_OBJECT_AGG"].includes(name))
2694
+ if (routineRule === "json-aggregate")
657
2695
  return { tsType: this.#policy.json, nullable: true, databaseType: name.startsWith("JSONB") ? "jsonb" : "json" };
658
- if (name === "ARRAY_AGG") {
659
- const item = resolved[0] ?? { tsType: "unknown", nullable: true };
2696
+ if (routineRule === "json-path-boolean" || routineRule === "json-path-json" || routineRule === "json-path-set") {
2697
+ const signature = this.#jsonPathCallTypes(expression);
2698
+ const selection = signature === undefined
2699
+ ? { kind: "none" }
2700
+ : resolvePostgresCandidates([
2701
+ {
2702
+ value: name,
2703
+ argumentTypes: signature,
2704
+ resultType: routineRule === "json-path-boolean" ? "boolean" : "jsonb",
2705
+ },
2706
+ ], expression.arguments.map((argument, index) => this.#overloadInputType(argument, resolved[index])), this.#schema);
2707
+ if (signature === undefined || selection.kind !== "selected") {
2708
+ this.#diagnostic("TSQ202", `No overload of ${expression.name.name} accepts these argument types`, expression.range, "Use jsonb, jsonpath, optional jsonb variables, and an optional boolean silent flag.", "warning");
2709
+ return { tsType: "unknown", nullable: true };
2710
+ }
2711
+ expression.arguments.forEach((argument, index) => {
2712
+ if (this.#acceptsTypeContext(argument)) {
2713
+ this.#resolveExpression(argument, scope, ctes, this.#databaseType(signature[index], true));
2714
+ }
2715
+ });
2716
+ if (routineRule === "json-path-boolean") {
2717
+ return {
2718
+ tsType: "boolean",
2719
+ nullable: name.includes("MATCH") || resolved.some(({ nullable }) => nullable),
2720
+ databaseType: "boolean",
2721
+ };
2722
+ }
2723
+ return {
2724
+ tsType: this.#policy.json,
2725
+ nullable: routineRule === "json-path-json" && (name.includes("FIRST") || resolved.some(({ nullable }) => nullable)),
2726
+ databaseType: "jsonb",
2727
+ };
2728
+ }
2729
+ if (routineRule === "array-aggregate") {
2730
+ const item = resolved[0] ?? aggregateOrder[0] ?? { tsType: "unknown", nullable: true };
660
2731
  return {
661
2732
  tsType: `readonly (${item.tsType}${item.nullable ? " | null" : ""})[]`,
662
2733
  nullable: true,
@@ -665,6 +2736,48 @@ class Resolver {
665
2736
  }
666
2737
  const functionName = sqlName(expression.name);
667
2738
  const schemaName = expression.schema === undefined ? undefined : sqlName(expression.schema);
2739
+ const routines = this.#index.routineOverloads(functionName, expression.arguments.length, schemaName);
2740
+ if (routines.length > 0) {
2741
+ const selection = resolvePostgresCandidates(routines.flatMap((routine) => {
2742
+ const result = routine.result;
2743
+ if (result.kind !== "scalar" && result.kind !== "set")
2744
+ return [];
2745
+ const argumentTypes = this.#routineCallTypes(routine, expression);
2746
+ if (argumentTypes === undefined)
2747
+ return [];
2748
+ return [
2749
+ {
2750
+ value: routine,
2751
+ argumentTypes,
2752
+ resultType: result.databaseType,
2753
+ },
2754
+ ];
2755
+ }), expression.arguments.map((argument, index) => this.#overloadInputType(argument, resolved[index])), this.#schema);
2756
+ if (selection.kind === "selected") {
2757
+ expression.arguments.forEach((argument, index) => {
2758
+ if (this.#acceptsTypeContext(argument)) {
2759
+ this.#resolveExpression(argument, scope, ctes, this.#databaseType(selection.argumentTypes[index], true));
2760
+ }
2761
+ });
2762
+ const result = selection.candidate.result;
2763
+ if (result.kind === "scalar" || result.kind === "set") {
2764
+ return {
2765
+ tsType: selection.resultType === normalizeDatabaseType(result.databaseType)
2766
+ ? result.tsType
2767
+ : mapPostgresType(selection.resultType, this.#policy, this.#schema),
2768
+ nullable: result.nullable,
2769
+ databaseType: selection.resultType,
2770
+ };
2771
+ }
2772
+ }
2773
+ if (selection.kind === "ambiguous") {
2774
+ this.#diagnostic("TSQ204", `Ambiguous overloaded function ${expression.name.name}`, expression.range, "Cast arguments to select a specific overload.");
2775
+ }
2776
+ else {
2777
+ this.#diagnostic("TSQ202", `No overload of ${expression.name.name} accepts these argument types`, expression.range, "Cast arguments to a supported overload.", "warning");
2778
+ }
2779
+ return { tsType: "unknown", nullable: true };
2780
+ }
668
2781
  const candidates = this.#index.functions(functionName, expression.arguments.length, schemaName);
669
2782
  const exact = candidates.filter((candidate) => candidate.argumentTypes.every((type, index) => {
670
2783
  const actual = resolved[index]?.databaseType;
@@ -673,7 +2786,7 @@ class Resolver {
673
2786
  const selected = exact.length === 1 ? exact[0] : candidates.length === 1 ? candidates[0] : undefined;
674
2787
  if (selected !== undefined) {
675
2788
  expression.arguments.forEach((argument, index) => {
676
- if (argument.kind === "parameter") {
2789
+ if (this.#acceptsTypeContext(argument)) {
677
2790
  this.#resolveExpression(argument, scope, ctes, this.#databaseType(selected.argumentTypes[index], true));
678
2791
  }
679
2792
  });
@@ -686,14 +2799,465 @@ class Resolver {
686
2799
  this.#diagnostic("TSQ202", `Unknown function ${expression.name.name}`, expression.range, undefined, "warning");
687
2800
  return { tsType: "unknown", nullable: true };
688
2801
  }
689
- #typesCompatible(expected, actual) {
690
- const left = normalizeDatabaseType(expected);
691
- const right = normalizeDatabaseType(actual);
692
- if (left === right)
693
- return true;
694
- if (numericTypes.has(left) && numericTypes.has(right))
2802
+ #typesCompatible(expected, actual, context = "implicit") {
2803
+ return postgresCanCoerce(actual, expected, context, this.#schema);
2804
+ }
2805
+ #overloadInputType(expression, resolved) {
2806
+ if (this.#isUnknownOverloadExpression(expression))
2807
+ return undefined;
2808
+ return resolved?.databaseType;
2809
+ }
2810
+ #isUnknownOverloadExpression(expression) {
2811
+ return (this.#acceptsTypeContext(expression) ||
2812
+ (expression.kind === "literal" && (expression.value === null || typeof expression.value === "string")));
2813
+ }
2814
+ #acceptsTypeContext(expression) {
2815
+ return (expression.kind === "parameter" ||
2816
+ (expression.kind === "collate" && this.#acceptsTypeContext(expression.expression)));
2817
+ }
2818
+ #jsonPathCallTypes(expression) {
2819
+ const declaredNames = ["target", "path", "vars", "silent"];
2820
+ const declaredTypes = ["jsonb", "jsonpath", "jsonb", "boolean"];
2821
+ const assigned = new Set();
2822
+ const argumentTypes = expression.arguments.map((_, index) => {
2823
+ const name = expression.argumentNames?.[index];
2824
+ const position = name === undefined ? index : declaredNames.indexOf(sqlName(name));
2825
+ if (position < 0 || assigned.has(position))
2826
+ return undefined;
2827
+ assigned.add(position);
2828
+ return declaredTypes[position];
2829
+ });
2830
+ if (expression.arguments.length > declaredTypes.length ||
2831
+ argumentTypes.some((type) => type === undefined) ||
2832
+ !assigned.has(0) ||
2833
+ !assigned.has(1)) {
2834
+ return undefined;
2835
+ }
2836
+ return argumentTypes;
2837
+ }
2838
+ #isUnknownCastSource(expression) {
2839
+ return (expression.kind === "parameter" ||
2840
+ (expression.kind === "literal" && (expression.value === null || typeof expression.value === "string")));
2841
+ }
2842
+ #routineCallTypes(routine, expression) {
2843
+ const inputs = routine.arguments.filter(({ mode }) => mode !== "out");
2844
+ const names = expression.argumentNames;
2845
+ if (names !== undefined) {
2846
+ const used = new Set();
2847
+ let positional = 0;
2848
+ const mapped = names.map((name) => {
2849
+ let index;
2850
+ if (name === undefined) {
2851
+ while (used.has(positional))
2852
+ positional += 1;
2853
+ index = positional;
2854
+ positional += 1;
2855
+ }
2856
+ else {
2857
+ index = inputs.findIndex((argument) => argument.name !== undefined && argument.name.toLowerCase() === sqlName(name));
2858
+ }
2859
+ if (index < 0 || index >= inputs.length || used.has(index))
2860
+ return undefined;
2861
+ used.add(index);
2862
+ return inputs[index].databaseType;
2863
+ });
2864
+ if (mapped.some((type) => type === undefined))
2865
+ return undefined;
2866
+ if (inputs.some((argument, index) => !used.has(index) && argument.mode !== "variadic" && argument.default !== "present"))
2867
+ return undefined;
2868
+ return mapped;
2869
+ }
2870
+ const variadic = inputs.at(-1)?.mode === "variadic";
2871
+ if (!variadic) {
2872
+ if (expression.variadic === true)
2873
+ return undefined;
2874
+ if (expression.arguments.length > inputs.length)
2875
+ return undefined;
2876
+ if (inputs.slice(expression.arguments.length).some(({ default: defaultValue }) => defaultValue !== "present")) {
2877
+ return undefined;
2878
+ }
2879
+ return inputs.slice(0, expression.arguments.length).map(({ databaseType }) => databaseType);
2880
+ }
2881
+ const fixed = inputs.slice(0, -1);
2882
+ const variadicType = inputs.at(-1).databaseType;
2883
+ if (expression.variadic === true) {
2884
+ if (expression.arguments.length !== inputs.length)
2885
+ return undefined;
2886
+ return [...fixed.map(({ databaseType }) => databaseType), variadicType];
2887
+ }
2888
+ if (expression.arguments.length < fixed.length &&
2889
+ fixed.slice(expression.arguments.length).some(({ default: defaultValue }) => defaultValue !== "present")) {
2890
+ return undefined;
2891
+ }
2892
+ const elementType = normalizeDatabaseType(variadicType).replace(/\[\]$/u, "");
2893
+ if (elementType === normalizeDatabaseType(variadicType))
2894
+ return undefined;
2895
+ return expression.arguments.map((_, index) => (index < fixed.length ? fixed[index].databaseType : elementType));
2896
+ }
2897
+ #resolveSqlJsonValue(value, scope, ctes, requireJsonInput) {
2898
+ const expected = value.format?.encoding === undefined ? this.#databaseType("text", true) : undefined;
2899
+ const resolved = this.#resolveExpression(value.expression, scope, ctes, expected);
2900
+ const canonical = resolved.databaseType === undefined ? undefined : postgresCanonicalType(resolved.databaseType, this.#schema);
2901
+ let valid = true;
2902
+ if (value.format?.encoding !== undefined) {
2903
+ if (value.format.encoding !== "UTF8") {
2904
+ this.#diagnostic("TSQ203", `PostgreSQL JSON ENCODING supports UTF8, received ${value.format.encoding}`, value.format.range);
2905
+ valid = false;
2906
+ }
2907
+ if (canonical !== "bytea") {
2908
+ this.#diagnostic("TSQ203", `JSON ENCODING requires bytea input, received ${resolved.databaseType ?? resolved.tsType}`, value.expression.range);
2909
+ valid = false;
2910
+ }
2911
+ }
2912
+ if (requireJsonInput || value.format !== undefined) {
2913
+ const nullLiteral = value.expression.kind === "literal" && value.expression.value === null;
2914
+ const jsonInput = canonical === "json" ||
2915
+ canonical === "jsonb" ||
2916
+ (canonical !== undefined && postgresTypeCategory(canonical, this.#schema) === "string") ||
2917
+ (value.format !== undefined && canonical === "bytea");
2918
+ if (!nullLiteral && !jsonInput) {
2919
+ this.#diagnostic("TSQ203", `SQL/JSON input must resolve to jsonb, a character string, or formatted bytea; received ${resolved.databaseType ?? resolved.tsType}`, value.expression.range);
2920
+ valid = false;
2921
+ }
2922
+ }
2923
+ return { resolved, valid };
2924
+ }
2925
+ #resolveJsonConstructorValue(value, scope, ctes) {
2926
+ if (value.format !== undefined)
2927
+ return this.#resolveSqlJsonValue(value, scope, ctes, false).valid;
2928
+ const resolved = this.#resolveExpression(value.expression, scope, ctes);
2929
+ return (resolved.databaseType !== undefined || (value.expression.kind === "literal" && value.expression.value === null));
2930
+ }
2931
+ #resolveJsonObjectKey(expression, scope, ctes, feature) {
2932
+ const key = this.#resolveExpression(expression, scope, ctes);
2933
+ const keyType = key.databaseType === undefined ? undefined : postgresCanonicalType(key.databaseType, this.#schema);
2934
+ const keyCategory = keyType === undefined ? undefined : postgresTypeCategory(keyType, this.#schema);
2935
+ if ((expression.kind === "literal" && expression.value === null) ||
2936
+ expression.kind === "row" ||
2937
+ keyType === "json" ||
2938
+ keyType === "jsonb" ||
2939
+ keyCategory === "array" ||
2940
+ keyCategory === "composite") {
2941
+ this.#diagnostic("TSQ203", `${feature} keys must be non-null scalar values and cannot be arrays, composites, json, or jsonb`, expression.range);
2942
+ return false;
2943
+ }
2944
+ return key.databaseType !== undefined;
2945
+ }
2946
+ #resolveJsonAggregateValue(value, scope, ctes) {
2947
+ if (value.format !== undefined)
2948
+ return this.#resolveSqlJsonValue(value, scope, ctes, false).valid;
2949
+ const resolved = this.#resolveExpression(value.expression, scope, ctes, this.#databaseType("text", true));
2950
+ return (resolved.databaseType !== undefined || (value.expression.kind === "literal" && value.expression.value === null));
2951
+ }
2952
+ #resolveJsonAggregateSuffix(expression, scope, ctes) {
2953
+ if (expression.filter !== undefined) {
2954
+ this.#resolveExpression(expression.filter, scope, ctes, this.#databaseType("boolean", false));
2955
+ }
2956
+ if (expression.over === undefined)
2957
+ return;
2958
+ if ("partitionBy" in expression.over)
2959
+ this.#resolveWindowSpecification(expression.over, scope, ctes);
2960
+ else if (!scope.windows.has(sqlName(expression.over))) {
2961
+ this.#diagnostic("TSQ222", `Unknown window ${expression.over.name}`, expression.over.range);
2962
+ }
2963
+ }
2964
+ #resolveJsonStringInput(value, scope, ctes, feature) {
2965
+ const expected = value.format?.encoding === undefined ? this.#databaseType("text", true) : undefined;
2966
+ const resolved = this.#resolveExpression(value.expression, scope, ctes, expected);
2967
+ const canonical = resolved.databaseType === undefined ? undefined : postgresCanonicalType(resolved.databaseType, this.#schema);
2968
+ const validInput = canonical === "json" ||
2969
+ canonical === "jsonb" ||
2970
+ canonical === "bytea" ||
2971
+ (canonical !== undefined && postgresTypeCategory(canonical, this.#schema) === "string");
2972
+ let valid = validInput;
2973
+ if (!validInput) {
2974
+ this.#diagnostic("TSQ203", `${feature} input must resolve to json, jsonb, a character string, or bytea; received ${resolved.databaseType ?? resolved.tsType}`, value.expression.range);
2975
+ }
2976
+ if (value.format?.encoding !== undefined) {
2977
+ if (value.format.encoding !== "UTF8") {
2978
+ this.#diagnostic("TSQ203", `PostgreSQL JSON ENCODING supports UTF8, received ${value.format.encoding}`, value.format.range);
2979
+ valid = false;
2980
+ }
2981
+ if (canonical !== "bytea") {
2982
+ this.#diagnostic("TSQ203", `JSON ENCODING requires bytea input, received ${resolved.databaseType ?? resolved.tsType}`, value.format.range);
2983
+ valid = false;
2984
+ }
2985
+ }
2986
+ return { resolved, valid };
2987
+ }
2988
+ #resolveJsonSerializeOutput(returning, range, nullable) {
2989
+ const requestedType = returning?.databaseType.name ?? "text";
2990
+ const known = isKnownPostgresType(requestedType, this.#schema);
2991
+ if (!known) {
2992
+ this.#diagnostic("TSQ106", `Invalid or unknown PostgreSQL JSON_SERIALIZE return type ${requestedType}`, returning?.databaseType.range ?? range);
2993
+ return { resolved: { tsType: "unknown", nullable: true, databaseType: "unknown" }, valid: false };
2994
+ }
2995
+ const canonical = postgresCanonicalType(requestedType, this.#schema);
2996
+ const mapping = postgresCatalogTypeMapping(canonical, this.#schema);
2997
+ let valid = mapping === "string" || mapping === "bytes";
2998
+ if (!valid) {
2999
+ this.#diagnostic("TSQ203", `JSON_SERIALIZE RETURNING requires a character string or bytea type; received ${requestedType}`, returning?.databaseType.range ?? range);
3000
+ }
3001
+ if (returning?.format?.encoding !== undefined) {
3002
+ if (returning.format.encoding !== "UTF8") {
3003
+ this.#diagnostic("TSQ203", `PostgreSQL JSON ENCODING supports UTF8, received ${returning.format.encoding}`, returning.format.range);
3004
+ valid = false;
3005
+ }
3006
+ if (canonical !== "bytea") {
3007
+ this.#diagnostic("TSQ203", `JSON ENCODING requires bytea output, received ${requestedType}`, returning.format.range);
3008
+ valid = false;
3009
+ }
3010
+ }
3011
+ return {
3012
+ resolved: valid
3013
+ ? { tsType: mapPostgresType(canonical, this.#policy, this.#schema), nullable, databaseType: canonical }
3014
+ : { tsType: "unknown", nullable: true, databaseType: "unknown" },
3015
+ valid,
3016
+ };
3017
+ }
3018
+ #validateSqlJsonFormat(format, resolved, feature) {
3019
+ const canonical = resolved.databaseType === undefined ? undefined : postgresCanonicalType(resolved.databaseType, this.#schema);
3020
+ const validInput = canonical === "json" ||
3021
+ canonical === "jsonb" ||
3022
+ (canonical !== undefined && postgresTypeCategory(canonical, this.#schema) === "string") ||
3023
+ canonical === "bytea";
3024
+ let valid = validInput;
3025
+ if (!validInput) {
3026
+ this.#diagnostic("TSQ203", `${feature} FORMAT JSON requires json, jsonb, a character string, or bytea input; received ${resolved.databaseType ?? resolved.tsType}`, format.range);
3027
+ }
3028
+ if (format.encoding !== undefined) {
3029
+ if (format.encoding !== "UTF8") {
3030
+ this.#diagnostic("TSQ203", `PostgreSQL JSON ENCODING supports UTF8, received ${format.encoding}`, format.range);
3031
+ valid = false;
3032
+ }
3033
+ if (canonical !== "bytea") {
3034
+ this.#diagnostic("TSQ203", `JSON ENCODING requires bytea input, received ${resolved.databaseType ?? resolved.tsType}`, format.range);
3035
+ valid = false;
3036
+ }
3037
+ }
3038
+ return valid;
3039
+ }
3040
+ #resolveJsonConstructorOutput(returning, feature, range, nullable = false) {
3041
+ const requestedType = returning?.databaseType.name ?? "json";
3042
+ const known = isKnownPostgresType(requestedType, this.#schema);
3043
+ if (!known) {
3044
+ this.#diagnostic("TSQ106", `Invalid or unknown PostgreSQL ${feature} return type ${requestedType}`, returning?.databaseType.range ?? range);
3045
+ return { resolved: { tsType: "unknown", nullable: true, databaseType: "unknown" }, valid: false };
3046
+ }
3047
+ const canonical = postgresCanonicalType(requestedType, this.#schema);
3048
+ const mapping = postgresCatalogTypeMapping(canonical, this.#schema);
3049
+ let valid = mapping === "string" || mapping === "bytes" || mapping === "json";
3050
+ if (!valid) {
3051
+ this.#diagnostic("TSQ203", `${feature} RETURNING requires a string, bytea, json, or jsonb type; received ${requestedType}`, returning?.databaseType.range ?? range);
3052
+ }
3053
+ if (returning?.format?.encoding !== undefined) {
3054
+ if (returning.format.encoding !== "UTF8") {
3055
+ this.#diagnostic("TSQ203", `PostgreSQL JSON ENCODING supports UTF8, received ${returning.format.encoding}`, returning.format.range);
3056
+ valid = false;
3057
+ }
3058
+ if (canonical !== "bytea") {
3059
+ this.#diagnostic("TSQ203", `JSON ENCODING requires bytea output, received ${requestedType}`, returning.format.range);
3060
+ valid = false;
3061
+ }
3062
+ }
3063
+ return {
3064
+ resolved: valid
3065
+ ? {
3066
+ tsType: mapPostgresType(canonical, this.#policy, this.#schema),
3067
+ nullable,
3068
+ databaseType: canonical,
3069
+ }
3070
+ : { tsType: "unknown", nullable: true, databaseType: "unknown" },
3071
+ valid,
3072
+ };
3073
+ }
3074
+ #resolveJsonQueryBehavior(behavior, expected, scope, ctes, allowEmptyCollections = true, scalarFeature = "JSON_VALUE") {
3075
+ if (behavior === undefined || behavior.kind === "null")
3076
+ return { nullable: true, valid: true };
3077
+ if (behavior.kind !== "default") {
3078
+ if (!allowEmptyCollections && (behavior.kind === "empty-array" || behavior.kind === "empty-object")) {
3079
+ this.#diagnostic("TSQ203", `Only ERROR, NULL, or DEFAULT behavior is valid for ${scalarFeature}`, behavior.range);
3080
+ return { nullable: true, valid: false };
3081
+ }
3082
+ return { nullable: false, valid: true };
3083
+ }
3084
+ const expression = behavior.expression;
3085
+ const resolved = this.#resolveExpression(expression, scope, ctes, expected);
3086
+ let valid = true;
3087
+ if (!this.#isSqlJsonDefaultExpression(expression)) {
3088
+ this.#diagnostic("TSQ203", "SQL/JSON DEFAULT must be a constant expression without parameters, columns, subqueries, aggregates, or windows", expression.range);
3089
+ valid = false;
3090
+ }
3091
+ if (expected?.databaseType !== undefined &&
3092
+ resolved.databaseType !== undefined &&
3093
+ !this.#isUnknownCastSource(expression) &&
3094
+ !postgresCanCoerce(resolved.databaseType, expected.databaseType, "assignment", this.#schema)) {
3095
+ this.#diagnostic("TSQ203", `SQL/JSON DEFAULT of type ${resolved.databaseType} cannot be coerced to ${expected.databaseType}`, expression.range);
3096
+ valid = false;
3097
+ }
3098
+ return { nullable: resolved.nullable, valid };
3099
+ }
3100
+ #isSqlJsonDefaultExpression(expression) {
3101
+ if (expression.kind === "column" ||
3102
+ expression.kind === "parameter" ||
3103
+ expression.kind === "star" ||
3104
+ expression.kind === "subquery" ||
3105
+ expression.kind === "exists") {
3106
+ return false;
3107
+ }
3108
+ if (expression.kind === "call" && (expression.over !== undefined || this.#isAggregateCall(expression))) {
3109
+ return false;
3110
+ }
3111
+ if (expression.kind === "json-object-aggregate" || expression.kind === "json-array-aggregate")
3112
+ return false;
3113
+ return this.#expressionChildren(expression).every((child) => this.#isSqlJsonDefaultExpression(child));
3114
+ }
3115
+ #isDefaultValue(expression) {
3116
+ return (expression?.kind === "column" &&
3117
+ expression.relation === undefined &&
3118
+ expression.column.quoted === false &&
3119
+ expression.column.name === "DEFAULT");
3120
+ }
3121
+ #matchConflictInference(table, target) {
3122
+ const relation = this.#index.relation(table);
3123
+ if (relation === undefined)
3124
+ return "unknown";
3125
+ const requested = target.elements.map((element) => ({
3126
+ ...(element.expression.kind === "column" && element.expression.relation === undefined
3127
+ ? {
3128
+ column: sqlName(element.expression.column),
3129
+ columnCaseSensitive: element.expression.column.quoted,
3130
+ }
3131
+ : { expressionHash: fingerprintPostgresExpression(element.expression) }),
3132
+ ...(element.operatorClass === undefined
3133
+ ? {}
3134
+ : {
3135
+ operatorClass: qualifiedSqlName(element.operatorClass),
3136
+ operatorClassCaseSensitive: qualifiedNameIsCaseSensitive(element.operatorClass),
3137
+ }),
3138
+ ...(element.collation === undefined
3139
+ ? {}
3140
+ : {
3141
+ collation: qualifiedSqlName(element.collation),
3142
+ collationCaseSensitive: qualifiedNameIsCaseSensitive(element.collation),
3143
+ }),
3144
+ }));
3145
+ const predicateHash = target.predicate === undefined ? undefined : fingerprintPostgresExpression(target.predicate);
3146
+ let incomplete = false;
3147
+ const matchesElements = (candidates) => {
3148
+ if (candidates.length !== requested.length)
3149
+ return false;
3150
+ const remaining = [...candidates];
3151
+ const matchesNamedEvidence = (candidate, name, caseSensitive) => {
3152
+ if (candidate === undefined)
3153
+ return false;
3154
+ const candidateName = name.includes(".") ? candidate : candidate.slice(candidate.lastIndexOf(".") + 1);
3155
+ return caseSensitive ? candidateName === name : candidateName.toLowerCase() === name;
3156
+ };
3157
+ for (const element of requested) {
3158
+ const index = remaining.findIndex((candidate) => {
3159
+ const identityMatches = element.column === undefined
3160
+ ? candidate.expressionHash === element.expressionHash
3161
+ : element.columnCaseSensitive
3162
+ ? candidate.column === element.column
3163
+ : candidate.column?.toLowerCase() === element.column;
3164
+ return (identityMatches &&
3165
+ (element.operatorClass === undefined ||
3166
+ matchesNamedEvidence(candidate.operatorClass, element.operatorClass, element.operatorClassCaseSensitive ?? false)) &&
3167
+ (element.collation === undefined ||
3168
+ matchesNamedEvidence(candidate.collation, element.collation, element.collationCaseSensitive ?? false)));
3169
+ });
3170
+ if (index < 0)
3171
+ return false;
3172
+ remaining.splice(index, 1);
3173
+ }
695
3174
  return true;
696
- return false;
3175
+ };
3176
+ if (relation.indexes === undefined)
3177
+ incomplete = true;
3178
+ for (const index of relation.indexes ?? []) {
3179
+ if (!index.unique)
3180
+ continue;
3181
+ if (index.valid !== true) {
3182
+ if (index.valid === "unknown")
3183
+ incomplete = true;
3184
+ continue;
3185
+ }
3186
+ if (target.predicate === undefined) {
3187
+ if (index.predicate === "unknown") {
3188
+ incomplete = true;
3189
+ continue;
3190
+ }
3191
+ if (index.predicate !== "none")
3192
+ continue;
3193
+ }
3194
+ else {
3195
+ if (index.predicate === "unknown") {
3196
+ incomplete = true;
3197
+ continue;
3198
+ }
3199
+ if (index.predicate === "present" && index.predicateHash === undefined) {
3200
+ incomplete = true;
3201
+ continue;
3202
+ }
3203
+ if (index.predicate === "present" && index.predicateHash !== predicateHash && matchesElements(index.columns)) {
3204
+ incomplete = true;
3205
+ continue;
3206
+ }
3207
+ }
3208
+ if (matchesElements(index.columns))
3209
+ return true;
3210
+ if (requested.some((element) => (element.operatorClass?.includes(".") ?? false) || (element.collation?.includes(".") ?? false)) &&
3211
+ index.columns.some((column) => (column.operatorClass !== undefined && !column.operatorClass.includes(".")) ||
3212
+ (column.collation !== undefined && !column.collation.includes("."))))
3213
+ incomplete = true;
3214
+ if (index.columns.some((column) => column.column === undefined && column.expressionHash === undefined)) {
3215
+ incomplete = true;
3216
+ }
3217
+ }
3218
+ if (requested.every((element) => element.column !== undefined)) {
3219
+ const requestedColumns = requested;
3220
+ for (const constraint of relation.constraints) {
3221
+ if ((constraint.kind !== "primary-key" && constraint.kind !== "unique") ||
3222
+ constraint.partial !== false ||
3223
+ constraint.expressionBased !== false ||
3224
+ constraint.columns.length !== requestedColumns.length ||
3225
+ !constraint.columns.every((column) => requestedColumns.some((requestedColumn) => requestedColumn.columnCaseSensitive
3226
+ ? column === requestedColumn.column
3227
+ : column.toLowerCase() === requestedColumn.column)))
3228
+ continue;
3229
+ if (constraint.deferrable === false)
3230
+ return true;
3231
+ if (constraint.deferrable === "unknown" || constraint.deferrable === undefined)
3232
+ incomplete = true;
3233
+ }
3234
+ }
3235
+ return incomplete ? "unknown" : false;
3236
+ }
3237
+ #validateWriteColumns(targets, sources, range, operation) {
3238
+ for (let index = 0; index < Math.min(targets.length, sources.length); index += 1) {
3239
+ this.#validateWriteValue(targets[index], sources[index], range, operation);
3240
+ }
3241
+ }
3242
+ #validateWriteValue(target, source, range, operation) {
3243
+ if (target === undefined ||
3244
+ source.databaseType === undefined ||
3245
+ source.tsType === "unknown" ||
3246
+ this.#typesCompatible(target.databaseType, source.databaseType, "assignment"))
3247
+ return;
3248
+ const expectedTsType = mapPostgresType(target.databaseType, this.#policy, this.#schema);
3249
+ const actualTsType = mapPostgresType(source.databaseType, this.#policy, this.#schema);
3250
+ if (expectedTsType === actualTsType)
3251
+ return;
3252
+ this.#diagnostic("TSQ229", `${operation} value of type ${source.databaseType} cannot be assigned to ${target.name} (${target.databaseType})`, range);
3253
+ }
3254
+ #isNumericType(databaseType) {
3255
+ const mapping = postgresCatalogTypeMapping(databaseType, this.#schema);
3256
+ return mapping === "bigint" || mapping === "number" || mapping === "numeric";
3257
+ }
3258
+ #validNumericLiteral(value, databaseType) {
3259
+ const integer = ["smallint", "integer", "bigint"].includes(normalizeDatabaseType(databaseType));
3260
+ return integer ? /^[+-]?\d+$/u.test(value) : /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/iu.test(value);
697
3261
  }
698
3262
  #functionType(value) {
699
3263
  return {