@bockit/sequel 0.0.0-stage → 0.2.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 (104) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +227 -2
  3. package/THIRD_PARTY_NOTICES.md +268 -0
  4. package/binaries.json +106 -0
  5. package/dist/binaries/cache.d.ts +6 -0
  6. package/dist/binaries/cache.js +36 -0
  7. package/dist/binaries/ensure-binary.d.ts +10 -0
  8. package/dist/binaries/ensure-binary.js +85 -0
  9. package/dist/binaries/manifest.d.ts +30 -0
  10. package/dist/binaries/manifest.js +112 -0
  11. package/dist/binaries/platform.d.ts +3 -0
  12. package/dist/binaries/platform.js +10 -0
  13. package/dist/binaries/unpack.d.ts +2 -0
  14. package/dist/binaries/unpack.js +35 -0
  15. package/dist/binaries/verify.d.ts +2 -0
  16. package/dist/binaries/verify.js +16 -0
  17. package/dist/check.d.ts +9 -0
  18. package/dist/check.js +178 -0
  19. package/dist/cli.d.ts +2 -0
  20. package/dist/cli.js +339 -0
  21. package/dist/config/define-config.d.ts +24 -0
  22. package/dist/config/define-config.js +4 -0
  23. package/dist/config/load-config.d.ts +6 -0
  24. package/dist/config/load-config.js +34 -0
  25. package/dist/config/resolve-config.d.ts +35 -0
  26. package/dist/config/resolve-config.js +159 -0
  27. package/dist/db/connection.d.ts +6 -0
  28. package/dist/db/connection.js +38 -0
  29. package/dist/db/shadow.d.ts +11 -0
  30. package/dist/db/shadow.js +68 -0
  31. package/dist/engine/client.d.ts +24 -0
  32. package/dist/engine/client.js +123 -0
  33. package/dist/engine/diff.d.ts +12 -0
  34. package/dist/engine/diff.js +51 -0
  35. package/dist/errors.d.ts +3 -0
  36. package/dist/errors.js +5 -0
  37. package/dist/extras/snapshot.d.ts +3 -0
  38. package/dist/extras/snapshot.js +99 -0
  39. package/dist/files.d.ts +1 -0
  40. package/dist/files.js +15 -0
  41. package/dist/generate/generate-modules.d.ts +17 -0
  42. package/dist/generate/generate-modules.js +97 -0
  43. package/dist/generate/modules.d.ts +16 -0
  44. package/dist/generate/modules.js +118 -0
  45. package/dist/generate/pg-dump.d.ts +8 -0
  46. package/dist/generate/pg-dump.js +66 -0
  47. package/dist/generate/plugin-wrapper.d.ts +3 -0
  48. package/dist/generate/plugin-wrapper.js +20 -0
  49. package/dist/generate/sqlc.d.ts +27 -0
  50. package/dist/generate/sqlc.js +113 -0
  51. package/dist/generate/watch.d.ts +16 -0
  52. package/dist/generate/watch.js +73 -0
  53. package/dist/generator/generate.d.ts +21 -0
  54. package/dist/generator/generate.js +440 -0
  55. package/dist/generator/keys.d.ts +4 -0
  56. package/dist/generator/keys.js +24 -0
  57. package/dist/generator/lexer.d.ts +12 -0
  58. package/dist/generator/lexer.js +210 -0
  59. package/dist/generator/main.d.ts +1 -0
  60. package/dist/generator/main.js +12 -0
  61. package/dist/generator/pg-array-helper.d.ts +2 -0
  62. package/dist/generator/pg-array-helper.js +53 -0
  63. package/dist/generator/plugin.d.ts +6 -0
  64. package/dist/generator/plugin.js +26 -0
  65. package/dist/generator/request.d.ts +57 -0
  66. package/dist/generator/request.js +96 -0
  67. package/dist/generator/select-list.d.ts +27 -0
  68. package/dist/generator/select-list.js +580 -0
  69. package/dist/generator/strings.d.ts +2 -0
  70. package/dist/generator/strings.js +34 -0
  71. package/dist/generator/types.d.ts +23 -0
  72. package/dist/generator/types.js +216 -0
  73. package/dist/index.d.ts +2 -0
  74. package/dist/index.js +2 -0
  75. package/dist/migrate/adopt-prisma.d.ts +23 -0
  76. package/dist/migrate/adopt-prisma.js +119 -0
  77. package/dist/migrate/deploy.d.ts +7 -0
  78. package/dist/migrate/deploy.js +23 -0
  79. package/dist/migrate/drift.d.ts +10 -0
  80. package/dist/migrate/drift.js +36 -0
  81. package/dist/migrate/files.d.ts +19 -0
  82. package/dist/migrate/files.js +97 -0
  83. package/dist/migrate/history.d.ts +20 -0
  84. package/dist/migrate/history.js +54 -0
  85. package/dist/migrate/lint.d.ts +21 -0
  86. package/dist/migrate/lint.js +153 -0
  87. package/dist/migrate/new.d.ts +13 -0
  88. package/dist/migrate/new.js +41 -0
  89. package/dist/migrate/runner.d.ts +28 -0
  90. package/dist/migrate/runner.js +124 -0
  91. package/dist/migrate/sql-text.d.ts +33 -0
  92. package/dist/migrate/sql-text.js +233 -0
  93. package/dist/schema/schema.d.ts +16 -0
  94. package/dist/schema/schema.js +111 -0
  95. package/dist/schema/unsupported.d.ts +3 -0
  96. package/dist/schema/unsupported.js +36 -0
  97. package/dist/schema/wasm.d.ts +8 -0
  98. package/dist/schema/wasm.js +32 -0
  99. package/package.json +50 -4
  100. package/vendor/prisma-schema-wasm/LICENSE +201 -0
  101. package/vendor/prisma-schema-wasm/README.md +32 -0
  102. package/vendor/prisma-schema-wasm/package.json +3 -0
  103. package/vendor/prisma-schema-wasm/prisma_schema_build.js +539 -0
  104. package/vendor/prisma-schema-wasm/prisma_schema_build_bg.wasm +0 -0
@@ -0,0 +1,580 @@
1
+ import { scan } from './lexer.js';
2
+ // R60: the select list of a query's text, split into items, and whether it lines up with
3
+ // the columns sqlc reports. Item `i` then describes column `i`.
4
+ const STATEMENTS = new Set(['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'MERGE']);
5
+ const SELECT_LIST_END = new Set([
6
+ 'FROM',
7
+ 'WHERE',
8
+ 'GROUP',
9
+ 'HAVING',
10
+ 'WINDOW',
11
+ 'UNION',
12
+ 'INTERSECT',
13
+ 'EXCEPT',
14
+ 'ORDER',
15
+ 'LIMIT',
16
+ 'OFFSET',
17
+ 'FETCH',
18
+ 'FOR',
19
+ ]);
20
+ const FROM_END = new Set([
21
+ 'WHERE',
22
+ 'GROUP',
23
+ 'HAVING',
24
+ 'WINDOW',
25
+ 'UNION',
26
+ 'INTERSECT',
27
+ 'EXCEPT',
28
+ 'ORDER',
29
+ 'LIMIT',
30
+ 'OFFSET',
31
+ 'FETCH',
32
+ 'FOR',
33
+ ]);
34
+ const SET_OPERATIONS = new Set(['UNION', 'INTERSECT', 'EXCEPT']);
35
+ export function selectList(text, columnCount) {
36
+ const { tokens, closed } = scan(text);
37
+ const notLinedUp = { items: null, from: null };
38
+ if (!closed)
39
+ return notLinedUp;
40
+ if (tokens.some((t) => t.depth === 0 && t.kind === 'word' && SET_OPERATIONS.has(t.upper))) {
41
+ return notLinedUp;
42
+ }
43
+ const start = tokens.findIndex((t) => t.depth === 0 && t.kind === 'word' && STATEMENTS.has(t.upper));
44
+ if (start === -1)
45
+ return notLinedUp;
46
+ const statement = tokens[start];
47
+ let listStart;
48
+ let listEnd;
49
+ let from = null;
50
+ if (statement.upper === 'SELECT') {
51
+ listStart = start + 1;
52
+ if (isWord(tokens[listStart], 'ALL')) {
53
+ listStart++;
54
+ }
55
+ else if (isWord(tokens[listStart], 'DISTINCT')) {
56
+ listStart++;
57
+ if (isWord(tokens[listStart], 'ON') && isPunct(tokens[listStart + 1], '(')) {
58
+ listStart = closing(tokens, listStart + 1) + 1;
59
+ }
60
+ }
61
+ listEnd = tokens.length;
62
+ for (let i = listStart; i < tokens.length; i++) {
63
+ const token = tokens[i];
64
+ if (token.depth !== 0 || token.kind !== 'word' || !SELECT_LIST_END.has(token.upper))
65
+ continue;
66
+ if (token.upper === 'GROUP' && isWord(tokens[i - 1], 'WITHIN'))
67
+ continue;
68
+ // `IS [NOT] DISTINCT FROM` is a null test (R63), not the end of the list.
69
+ if (token.upper === 'FROM' &&
70
+ isWord(tokens[i - 1], 'DISTINCT') &&
71
+ (isWord(tokens[i - 2], 'IS') || isWord(tokens[i - 2], 'NOT'))) {
72
+ continue;
73
+ }
74
+ listEnd = i;
75
+ break;
76
+ }
77
+ if (isWord(tokens[listEnd], 'FROM')) {
78
+ from = readFrom(tokens, listEnd + 1, withNames(tokens, start));
79
+ }
80
+ }
81
+ else {
82
+ const returning = tokens.findIndex((t, i) => i > start && t.depth === 0 && isWord(t, 'RETURNING'));
83
+ if (returning === -1)
84
+ return notLinedUp;
85
+ if (isWord(tokens[returning + 1], 'WITH'))
86
+ return notLinedUp;
87
+ listStart = returning + 1;
88
+ listEnd = tokens.length;
89
+ }
90
+ const items = [];
91
+ let current = [];
92
+ for (let i = listStart; i < listEnd; i++) {
93
+ const token = tokens[i];
94
+ if (token.depth === 0 && isPunct(token, ',')) {
95
+ items.push(current);
96
+ current = [];
97
+ }
98
+ else if (!(token.depth === 0 && isPunct(token, ';'))) {
99
+ current.push(token);
100
+ }
101
+ }
102
+ items.push(current);
103
+ for (const item of items) {
104
+ const last = item[item.length - 1];
105
+ if (last !== undefined && last.kind === 'op' && last.text === '*') {
106
+ if (item.length === 1 || isPunct(item[item.length - 2], '.'))
107
+ return notLinedUp;
108
+ }
109
+ }
110
+ if (items.length !== columnCount)
111
+ return notLinedUp;
112
+ return { items, from };
113
+ }
114
+ // --- Shapes -------------------------------------------------------------------------------
115
+ // Words that end an expression, so are never a bare alias.
116
+ const NEVER_ALIAS = new Set([
117
+ 'NULL',
118
+ 'TRUE',
119
+ 'FALSE',
120
+ 'UNKNOWN',
121
+ 'END',
122
+ 'ZONE',
123
+ 'PRECISION',
124
+ 'VARYING',
125
+ 'CURRENT_DATE',
126
+ 'CURRENT_TIME',
127
+ 'CURRENT_TIMESTAMP',
128
+ 'LOCALTIME',
129
+ 'LOCALTIMESTAMP',
130
+ 'CURRENT_USER',
131
+ 'CURRENT_ROLE',
132
+ 'CURRENT_CATALOG',
133
+ 'CURRENT_SCHEMA',
134
+ 'SESSION_USER',
135
+ 'USER',
136
+ 'DOCUMENT',
137
+ ]);
138
+ // Words after which an operand follows, so the next word isn't an alias.
139
+ const BEFORE_OPERAND = new Set([
140
+ 'AS',
141
+ 'FROM',
142
+ 'IS',
143
+ 'NOT',
144
+ 'AND',
145
+ 'OR',
146
+ 'DISTINCT',
147
+ 'BY',
148
+ 'THEN',
149
+ 'ELSE',
150
+ 'WHEN',
151
+ 'CASE',
152
+ 'IN',
153
+ 'LIKE',
154
+ 'ILIKE',
155
+ 'SIMILAR',
156
+ 'TO',
157
+ 'BETWEEN',
158
+ 'SYMMETRIC',
159
+ 'ASYMMETRIC',
160
+ 'ESCAPE',
161
+ 'AT',
162
+ 'OVER',
163
+ 'COLLATE',
164
+ 'ANY',
165
+ 'ALL',
166
+ 'SOME',
167
+ 'SELECT',
168
+ 'RETURNING',
169
+ 'OPERATOR',
170
+ 'ARRAY',
171
+ 'ROW',
172
+ 'INTERVAL',
173
+ 'OVERLAPS',
174
+ ]);
175
+ export function bare(item) {
176
+ return stripParens({ tokens: stripAlias(item), depth: 0 });
177
+ }
178
+ function stripAlias(tokens) {
179
+ const n = tokens.length;
180
+ const last = tokens[n - 1];
181
+ const previous = tokens[n - 2];
182
+ if (last === undefined || previous === undefined || last.depth !== 0)
183
+ return tokens;
184
+ const name = last.kind === 'qident' || (last.kind === 'word' && !NEVER_ALIAS.has(last.upper));
185
+ if (!name)
186
+ return tokens;
187
+ if (isWord(previous, 'AS'))
188
+ return tokens.slice(0, n - 2);
189
+ const endsExpression = isPunct(previous, ')') ||
190
+ isPunct(previous, ']') ||
191
+ previous.kind === 'string' ||
192
+ previous.kind === 'number' ||
193
+ previous.kind === 'param' ||
194
+ previous.kind === 'qident' ||
195
+ (previous.kind === 'word' && !BEFORE_OPERAND.has(previous.upper));
196
+ return endsExpression ? tokens.slice(0, n - 1) : tokens;
197
+ }
198
+ function stripParens(expression) {
199
+ let { tokens, depth } = expression;
200
+ while (tokens.length >= 2 &&
201
+ isPunct(tokens[0], '(') &&
202
+ closing(tokens, 0) === tokens.length - 1) {
203
+ tokens = tokens.slice(1, -1);
204
+ depth++;
205
+ }
206
+ return { tokens, depth };
207
+ }
208
+ const NEVER_NULL_FUNCTIONS = new Set([
209
+ 'count',
210
+ 'row_number',
211
+ 'rank',
212
+ 'dense_rank',
213
+ 'ntile',
214
+ 'percent_rank',
215
+ 'cume_dist',
216
+ 'now',
217
+ 'gen_random_uuid',
218
+ ]);
219
+ // R61: `[pg_catalog.]name ( … )`, followed by nothing but `FILTER ( … )`,
220
+ // `WITHIN GROUP ( … )` or `OVER …`, where `name` can't return null.
221
+ export function isNeverNullCall(item) {
222
+ const { tokens } = bare(item);
223
+ let i = 0;
224
+ const first = tokens[0];
225
+ if (first !== undefined &&
226
+ ((first.kind === 'word' && first.upper === 'PG_CATALOG') ||
227
+ (first.kind === 'qident' && first.text === 'pg_catalog')) &&
228
+ isPunct(tokens[1], '.')) {
229
+ i = 2;
230
+ }
231
+ const name = tokens[i];
232
+ if (name === undefined || !isPunct(tokens[i + 1], '('))
233
+ return false;
234
+ const known = name.kind === 'word'
235
+ ? NEVER_NULL_FUNCTIONS.has(name.text.toLowerCase())
236
+ : name.kind === 'qident' && NEVER_NULL_FUNCTIONS.has(name.text);
237
+ if (!known)
238
+ return false;
239
+ let j = closing(tokens, i + 1) + 1;
240
+ while (j < tokens.length) {
241
+ if (isWord(tokens[j], 'FILTER') && isPunct(tokens[j + 1], '(')) {
242
+ j = closing(tokens, j + 1) + 1;
243
+ }
244
+ else if (isWord(tokens[j], 'WITHIN') &&
245
+ isWord(tokens[j + 1], 'GROUP') &&
246
+ isPunct(tokens[j + 2], '(')) {
247
+ j = closing(tokens, j + 2) + 1;
248
+ }
249
+ else if (isWord(tokens[j], 'OVER')) {
250
+ if (isPunct(tokens[j + 1], '('))
251
+ j = closing(tokens, j + 1) + 1;
252
+ else if (isName(tokens[j + 1]))
253
+ j += 2;
254
+ else
255
+ return false;
256
+ }
257
+ else {
258
+ return false;
259
+ }
260
+ }
261
+ return true;
262
+ }
263
+ // R62: whether the item is a `coalesce ( … )` call, and if so whether its last argument is
264
+ // a literal, which makes it never null.
265
+ export function coalesce(item) {
266
+ const { tokens, depth } = bare(item);
267
+ if (!isWord(tokens[0], 'COALESCE') || !isPunct(tokens[1], '('))
268
+ return null;
269
+ if (closing(tokens, 1) !== tokens.length - 1)
270
+ return null;
271
+ const inner = depth + 1;
272
+ let lastComma = 1;
273
+ for (let i = 2; i < tokens.length - 1; i++) {
274
+ const token = tokens[i];
275
+ if (token.depth === inner && isPunct(token, ','))
276
+ lastComma = i;
277
+ }
278
+ const argument = tokens.slice(lastComma + 1, -1);
279
+ return isLiteral(argument, inner) ? 'never-null' : 'nullable';
280
+ }
281
+ // A string, a number with an optional sign, TRUE or FALSE, each with any `::type` after it.
282
+ function isLiteral(tokens, depth) {
283
+ let i;
284
+ const first = tokens[0];
285
+ if (first === undefined)
286
+ return false;
287
+ if (first.kind === 'op' && (first.text === '-' || first.text === '+')) {
288
+ if (tokens[1]?.kind !== 'number')
289
+ return false;
290
+ i = 2;
291
+ }
292
+ else if (first.kind === 'number' || first.kind === 'string') {
293
+ i = 1;
294
+ }
295
+ else if (isWord(first, 'TRUE') || isWord(first, 'FALSE')) {
296
+ i = 1;
297
+ }
298
+ else {
299
+ return false;
300
+ }
301
+ while (i < tokens.length) {
302
+ if (!isPunct(tokens[i], '::'))
303
+ return false;
304
+ i++;
305
+ let typeTokens = 0;
306
+ while (i < tokens.length && !(tokens[i]?.depth === depth && isPunct(tokens[i], '::'))) {
307
+ const token = tokens[i];
308
+ const allowed = token.depth > depth ||
309
+ token.kind === 'word' ||
310
+ token.kind === 'qident' ||
311
+ (token.kind === 'punct' && ['.', '(', ')', '[', ']'].includes(token.text));
312
+ if (!allowed)
313
+ return false;
314
+ typeTokens++;
315
+ i++;
316
+ }
317
+ if (typeTokens === 0)
318
+ return false;
319
+ }
320
+ return true;
321
+ }
322
+ // R63: after removing leading NOTs and surrounding parentheses, a depth-0 `IS [NOT] NULL` or
323
+ // `IS [NOT] DISTINCT FROM`, with no depth-0 AND, OR or CASE.
324
+ export function isNullTest(item) {
325
+ let expression = bare(item);
326
+ while (isWord(expression.tokens[0], 'NOT') && expression.tokens[0]?.depth === expression.depth) {
327
+ expression = stripParens({ tokens: expression.tokens.slice(1), depth: expression.depth });
328
+ }
329
+ const { tokens, depth } = expression;
330
+ let test = false;
331
+ for (let i = 0; i < tokens.length; i++) {
332
+ const token = tokens[i];
333
+ if (token.depth !== depth || token.kind !== 'word')
334
+ continue;
335
+ if (token.upper === 'AND' || token.upper === 'OR' || token.upper === 'CASE')
336
+ return false;
337
+ if (token.upper === 'IS') {
338
+ const j = isWord(tokens[i + 1], 'NOT') ? i + 2 : i + 1;
339
+ if (isWord(tokens[j], 'NULL'))
340
+ test = true;
341
+ if (isWord(tokens[j], 'DISTINCT') && isWord(tokens[j + 1], 'FROM'))
342
+ test = true;
343
+ }
344
+ }
345
+ return test;
346
+ }
347
+ // R64: whether the elements of an `array_agg` item can't be null.
348
+ export function arrayAggElementsNotNull(item, from, lookup) {
349
+ const { tokens, depth } = bare(item);
350
+ if (!isWord(tokens[0], 'ARRAY_AGG') || !isPunct(tokens[1], '('))
351
+ return false;
352
+ const close = closing(tokens, 1);
353
+ const inner = depth + 1;
354
+ let i = 2;
355
+ if (isWord(tokens[i], 'DISTINCT') && tokens[i]?.depth === inner)
356
+ i++;
357
+ let end = close;
358
+ for (let j = i; j < close; j++) {
359
+ if (tokens[j]?.depth === inner && isWord(tokens[j], 'ORDER') && isWord(tokens[j + 1], 'BY')) {
360
+ end = j;
361
+ break;
362
+ }
363
+ }
364
+ const aggregated = tokens.slice(i, end);
365
+ if (aggregated.length === 0)
366
+ return false;
367
+ const rest = tokens.slice(close + 1);
368
+ if (rest.length > 0) {
369
+ // Only `FILTER ( WHERE <col> IS NOT NULL [AND …] )` may follow.
370
+ if (!isWord(rest[0], 'FILTER') || !isPunct(rest[1], '('))
371
+ return false;
372
+ if (closing(rest, 1) !== rest.length - 1)
373
+ return false;
374
+ const condition = rest.slice(2, -1);
375
+ if (!isWord(condition[0], 'WHERE'))
376
+ return false;
377
+ const n = aggregated.length;
378
+ const filtered = aggregated.every((token, k) => sameToken(token, condition[1 + k])) &&
379
+ isWord(condition[1 + n], 'IS') &&
380
+ isWord(condition[2 + n], 'NOT') &&
381
+ isWord(condition[3 + n], 'NULL') &&
382
+ (condition.length === 4 + n || isWord(condition[4 + n], 'AND')) &&
383
+ !condition.some((token) => token.depth === inner && isWord(token, 'OR'));
384
+ if (filtered)
385
+ return true;
386
+ }
387
+ return resolvesToNotNullColumn(aggregated, from, lookup);
388
+ }
389
+ function resolvesToNotNullColumn(tokens, from, lookup) {
390
+ if (from === null || from.length === 0)
391
+ return false;
392
+ let qualifier = null;
393
+ let column;
394
+ if (tokens.length === 1 && isName(tokens[0])) {
395
+ column = identifier(tokens[0]);
396
+ }
397
+ else if (tokens.length === 3 &&
398
+ isName(tokens[0]) &&
399
+ isPunct(tokens[1], '.') &&
400
+ isName(tokens[2])) {
401
+ qualifier = identifier(tokens[0]);
402
+ column = identifier(tokens[2]);
403
+ }
404
+ else {
405
+ return false;
406
+ }
407
+ let item;
408
+ if (qualifier !== null) {
409
+ item = from.find((f) => f.alias === qualifier || (f.alias === null && f.table?.name === qualifier));
410
+ }
411
+ else {
412
+ // A bare name must belong to exactly one item, and every item must be a known table.
413
+ const found = from.map((f) => (f.table === null ? null : lookup(f.table, column)));
414
+ if (found.some((result) => result === null))
415
+ return false;
416
+ const owners = from.filter((_, k) => found[k] !== 'missing');
417
+ if (owners.length !== 1)
418
+ return false;
419
+ item = owners[0];
420
+ }
421
+ if (item === undefined || item.table === null || item.nullable)
422
+ return false;
423
+ return lookup(item.table, column) === 'not-null';
424
+ }
425
+ // --- FROM ---------------------------------------------------------------------------------
426
+ const JOIN_WORDS = new Set(['JOIN', 'INNER', 'CROSS', 'LEFT', 'RIGHT', 'FULL', 'OUTER', 'NATURAL']);
427
+ const NOT_TABLE_ALIAS = new Set([...JOIN_WORDS, ...FROM_END, 'ON', 'USING', 'TABLESAMPLE', 'AS']);
428
+ // Names declared by the statement's leading WITH: a name followed by `AS` or `(` at depth 0.
429
+ function withNames(tokens, statementStart) {
430
+ const names = new Set();
431
+ for (let i = 0; i < statementStart; i++) {
432
+ const token = tokens[i];
433
+ const next = tokens[i + 1];
434
+ if (token.depth === 0 &&
435
+ isName(token) &&
436
+ !isWord(token, 'WITH') &&
437
+ !isWord(token, 'RECURSIVE')) {
438
+ if (isWord(next, 'AS') || isPunct(next, '('))
439
+ names.add(identifier(token));
440
+ }
441
+ }
442
+ return names;
443
+ }
444
+ // Reads `[schema.]table [[AS] alias]` items separated by commas or joins, from `start` to
445
+ // the end of the depth-0 FROM clause. Anything that isn't a table is an item with no table.
446
+ function readFrom(tokens, start, ctes) {
447
+ let end = tokens.length;
448
+ for (let i = start; i < tokens.length; i++) {
449
+ const token = tokens[i];
450
+ if (token.depth === 0 &&
451
+ (isPunct(token, ';') || (token.kind === 'word' && FROM_END.has(token.upper)))) {
452
+ end = i;
453
+ break;
454
+ }
455
+ }
456
+ const items = [];
457
+ let i = start;
458
+ let join = null;
459
+ while (i < end) {
460
+ // One item.
461
+ let table = null;
462
+ let alias = null;
463
+ const first = tokens[i];
464
+ if (first === undefined)
465
+ return null;
466
+ if (isPunct(first, '(')) {
467
+ i = closing(tokens, i) + 1;
468
+ }
469
+ else if (isWord(first, 'LATERAL') || isWord(first, 'ONLY')) {
470
+ i++;
471
+ while (i < end &&
472
+ !(tokens[i]?.depth === 0 && (isPunct(tokens[i], ',') || isJoinStart(tokens[i])))) {
473
+ if (isPunct(tokens[i], '('))
474
+ i = closing(tokens, i);
475
+ i++;
476
+ }
477
+ }
478
+ else if (isName(first)) {
479
+ let schema = null;
480
+ let name = identifier(first);
481
+ i++;
482
+ if (isPunct(tokens[i], '.') && isName(tokens[i + 1])) {
483
+ schema = name;
484
+ name = identifier(tokens[i + 1]);
485
+ i += 2;
486
+ }
487
+ if (isPunct(tokens[i], '(')) {
488
+ i = closing(tokens, i) + 1;
489
+ }
490
+ else if (!(schema === null && ctes.has(name))) {
491
+ table = { schema, name };
492
+ }
493
+ }
494
+ else {
495
+ return null;
496
+ }
497
+ // Its alias.
498
+ if (isWord(tokens[i], 'AS') && isName(tokens[i + 1])) {
499
+ alias = identifier(tokens[i + 1]);
500
+ i += 2;
501
+ }
502
+ else if (tokens[i] !== undefined &&
503
+ isName(tokens[i]) &&
504
+ !(tokens[i]?.kind === 'word' && NOT_TABLE_ALIAS.has(tokens[i].upper))) {
505
+ alias = identifier(tokens[i]);
506
+ i++;
507
+ }
508
+ if (alias !== null && isPunct(tokens[i], '('))
509
+ i = closing(tokens, i) + 1;
510
+ const nullable = join === 'LEFT' || join === 'FULL';
511
+ if (join === 'RIGHT' || join === 'FULL')
512
+ for (const item of items)
513
+ item.nullable = true;
514
+ items.push({ table, alias, nullable });
515
+ // Skip to the next separator, past any ON or USING condition.
516
+ join = null;
517
+ while (i < end) {
518
+ const token = tokens[i];
519
+ if (token.depth === 0 && isPunct(token, ',')) {
520
+ i++;
521
+ join = 'COMMA';
522
+ break;
523
+ }
524
+ if (token.depth === 0 && isJoinStart(token)) {
525
+ let kind = 'INNER';
526
+ while (i < end && isWord(tokens[i], 'JOIN') === false) {
527
+ const word = tokens[i].upper;
528
+ if (word === 'LEFT' || word === 'RIGHT' || word === 'FULL')
529
+ kind = word;
530
+ if (!JOIN_WORDS.has(word))
531
+ return null;
532
+ i++;
533
+ }
534
+ i++;
535
+ join = kind;
536
+ break;
537
+ }
538
+ i++;
539
+ }
540
+ if (join === null && i < end)
541
+ return null;
542
+ }
543
+ return items;
544
+ }
545
+ function isJoinStart(token) {
546
+ return (token !== undefined &&
547
+ token.kind === 'word' &&
548
+ JOIN_WORDS.has(token.upper) &&
549
+ token.upper !== 'OUTER');
550
+ }
551
+ // --- Tokens -------------------------------------------------------------------------------
552
+ function isWord(token, upper) {
553
+ return token !== undefined && token.kind === 'word' && token.upper === upper;
554
+ }
555
+ function isPunct(token, text) {
556
+ return token !== undefined && token.kind === 'punct' && token.text === text;
557
+ }
558
+ function isName(token) {
559
+ return token !== undefined && (token.kind === 'word' || token.kind === 'qident');
560
+ }
561
+ // Unquoted names fold to lower case, as Postgres folds them.
562
+ function identifier(token) {
563
+ return token.kind === 'word' ? token.text.toLowerCase() : token.text;
564
+ }
565
+ function sameToken(a, b) {
566
+ if (b === undefined || a.kind !== b.kind)
567
+ return false;
568
+ return a.kind === 'word' ? a.upper === b.upper : a.text === b.text;
569
+ }
570
+ // The index of the bracket that closes the one at `open`, or the last index if none does.
571
+ function closing(tokens, open) {
572
+ const opener = tokens[open];
573
+ const close = opener.text === '(' ? ')' : ']';
574
+ for (let i = open + 1; i < tokens.length; i++) {
575
+ const token = tokens[i];
576
+ if (token.depth === opener.depth && token.kind === 'punct' && token.text === close)
577
+ return i;
578
+ }
579
+ return tokens.length - 1;
580
+ }
@@ -0,0 +1,2 @@
1
+ export declare function singleQuoted(value: string): string;
2
+ export declare function templateLiteral(text: string): string;
@@ -0,0 +1,34 @@
1
+ // R53: every string literal except the SQL text is single-quoted.
2
+ export function singleQuoted(value) {
3
+ let out = "'";
4
+ for (const char of value) {
5
+ const code = char.codePointAt(0) ?? 0;
6
+ if (char === '\\')
7
+ out += '\\\\';
8
+ else if (char === "'")
9
+ out += "\\'";
10
+ else if (char === '\n')
11
+ out += '\\n';
12
+ else if (char === '\r')
13
+ out += '\\r';
14
+ else if (code === 0x2028)
15
+ out += '\\u2028';
16
+ else if (code === 0x2029)
17
+ out += '\\u2029';
18
+ else if (code < 0x20)
19
+ out += `\\x${code.toString(16).padStart(2, '0')}`;
20
+ else
21
+ out += char;
22
+ }
23
+ return `${out}'`;
24
+ }
25
+ // R25: the SQL text as a template literal whose cooked value is `text`. Line feeds stay
26
+ // literal, so the SQL reads as written; a carriage return is escaped so CRLF survives.
27
+ export function templateLiteral(text) {
28
+ const body = text
29
+ .replaceAll('\\', '\\\\')
30
+ .replaceAll('`', '\\`')
31
+ .replaceAll('${', '\\${')
32
+ .replaceAll('\r', '\\r');
33
+ return `\`${body}\``;
34
+ }
@@ -0,0 +1,23 @@
1
+ import type { BigintMode, Catalog, Column } from './request.ts';
2
+ type Builtin = {
3
+ row: string[];
4
+ rowArray: string[] | 'parse' | 'unknown';
5
+ param: string[];
6
+ json?: true;
7
+ bigint?: true;
8
+ };
9
+ export type ResolvedType = {
10
+ base: Builtin;
11
+ dims: number;
12
+ };
13
+ export declare function normaliseType(column: Pick<Column, 'type' | 'is_array' | 'array_dims'>): {
14
+ schema: string;
15
+ name: string;
16
+ dims: number;
17
+ };
18
+ export declare function resolveType(column: Pick<Column, 'type' | 'is_array' | 'array_dims'>, catalog: Catalog | null): ResolvedType;
19
+ export declare function isJson(type: ResolvedType): boolean;
20
+ export declare function needsParse(type: ResolvedType): boolean;
21
+ export declare function rowType(type: ResolvedType, nullable: boolean, elementsNotNull: boolean, bigint: BigintMode): string;
22
+ export declare function paramType(type: ResolvedType, nullable: boolean): string;
23
+ export {};