ormbridge 0.3.0 → 0.5.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 (58) hide show
  1. package/README.md +239 -37
  2. package/dist/cliRunner.js +14 -1
  3. package/dist/cliRunner.js.map +1 -1
  4. package/dist/config.d.ts +3 -0
  5. package/dist/config.js +9 -0
  6. package/dist/config.js.map +1 -1
  7. package/dist/emitters/drizzle.d.ts +19 -0
  8. package/dist/emitters/drizzle.js +1337 -0
  9. package/dist/emitters/drizzle.js.map +1 -0
  10. package/dist/emitters/jsonSchema.d.ts +20 -0
  11. package/dist/emitters/jsonSchema.js +503 -0
  12. package/dist/emitters/jsonSchema.js.map +1 -0
  13. package/dist/emitters/sqlDdl.d.ts +29 -0
  14. package/dist/emitters/sqlDdl.js +1741 -0
  15. package/dist/emitters/sqlDdl.js.map +1 -0
  16. package/dist/emitters/sqlalchemy.d.ts +30 -0
  17. package/dist/emitters/sqlalchemy.js +1725 -0
  18. package/dist/emitters/sqlalchemy.js.map +1 -0
  19. package/dist/emitters/typeorm.js +53 -1
  20. package/dist/emitters/typeorm.js.map +1 -1
  21. package/dist/emitters/zod.d.ts +40 -0
  22. package/dist/emitters/zod.js +498 -0
  23. package/dist/emitters/zod.js.map +1 -0
  24. package/dist/formats.d.ts +11 -1
  25. package/dist/formats.js +84 -0
  26. package/dist/formats.js.map +1 -1
  27. package/dist/index.d.ts +18 -0
  28. package/dist/index.js +9 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/io.js +82 -8
  31. package/dist/io.js.map +1 -1
  32. package/dist/parsers/drizzle.d.ts +12 -0
  33. package/dist/parsers/drizzle.js +2040 -0
  34. package/dist/parsers/drizzle.js.map +1 -0
  35. package/dist/parsers/jsonSchema.d.ts +21 -0
  36. package/dist/parsers/jsonSchema.js +1432 -0
  37. package/dist/parsers/jsonSchema.js.map +1 -0
  38. package/dist/parsers/pythonSyntax.d.ts +7 -1
  39. package/dist/parsers/pythonSyntax.js +47 -8
  40. package/dist/parsers/pythonSyntax.js.map +1 -1
  41. package/dist/parsers/sqlDdl.d.ts +18 -0
  42. package/dist/parsers/sqlDdl.js +2302 -0
  43. package/dist/parsers/sqlDdl.js.map +1 -0
  44. package/dist/parsers/sqlSyntax.d.ts +79 -0
  45. package/dist/parsers/sqlSyntax.js +698 -0
  46. package/dist/parsers/sqlSyntax.js.map +1 -0
  47. package/dist/parsers/sqlTypes.d.ts +53 -0
  48. package/dist/parsers/sqlTypes.js +742 -0
  49. package/dist/parsers/sqlTypes.js.map +1 -0
  50. package/dist/parsers/sqlalchemy.d.ts +12 -0
  51. package/dist/parsers/sqlalchemy.js +3191 -0
  52. package/dist/parsers/sqlalchemy.js.map +1 -0
  53. package/dist/parsers/typeorm.js +22 -7
  54. package/dist/parsers/typeorm.js.map +1 -1
  55. package/dist/prismaOnlyConstructs.d.ts +2 -2
  56. package/dist/prismaOnlyConstructs.js +5 -5
  57. package/dist/prismaOnlyConstructs.js.map +1 -1
  58. package/package.json +2 -1
@@ -0,0 +1,698 @@
1
+ /**
2
+ * A small static SQL tokenizer and token cursor for the DDL parser.
3
+ *
4
+ * `tree-sitter-wasms` ships no SQL grammar, so DDL is read with this scanner and the recursive-descent
5
+ * statement parser in `sqlDdl.ts`. Nothing here executes or evaluates SQL, nothing recurses (so deeply
6
+ * nested input cannot overflow the stack) and every loop advances, so any input is scanned in linear time.
7
+ */
8
+ export const SQL_DIALECTS = [
9
+ 'postgresql',
10
+ 'mysql',
11
+ 'sqlite',
12
+ 'sqlserver',
13
+ ];
14
+ /** Returned by the cursor when it reads past the end, so callers never see `undefined`. */
15
+ export const END_TOKEN = {
16
+ kind: 'punct',
17
+ text: '',
18
+ value: '',
19
+ up: '',
20
+ start: 0,
21
+ end: 0,
22
+ line: 0,
23
+ };
24
+ /** Hard limits that keep hostile input from exhausting memory. */
25
+ export const MAX_SOURCE_CHARACTERS = 32_000_000;
26
+ export const MAX_TOKENS = 6_000_000;
27
+ /**
28
+ * Every unclosed dollar tag costs one scan to the end of the text, so after this many distinct ones the
29
+ * remaining `$tag$` openers are read as punctuation (a real script has a handful of tags at most).
30
+ */
31
+ const MAX_FAILED_DOLLAR_TAGS = 16;
32
+ const BACKSLASH_ESCAPES = {
33
+ n: '\n',
34
+ t: '\t',
35
+ r: '\r',
36
+ '0': '\0',
37
+ b: '\b',
38
+ Z: '\x1a',
39
+ };
40
+ function isWordStart(code) {
41
+ return ((code >= 65 && code <= 90) ||
42
+ (code >= 97 && code <= 122) ||
43
+ code === 95 ||
44
+ code > 127);
45
+ }
46
+ function isWordPart(code) {
47
+ return isWordStart(code) || (code >= 48 && code <= 57) || code === 36;
48
+ }
49
+ function isDigit(code) {
50
+ return code >= 48 && code <= 57;
51
+ }
52
+ /**
53
+ * Splits SQL text into tokens. Comments are dropped. Statement terminators become `semi` tokens: `;`, the
54
+ * custom `DELIMITER` of a MySQL dump, and the `GO` batch separator of SQL Server. Quoting follows the dialect:
55
+ * `"x"` and `[x]` are identifiers in SQL Server, `"x"` is a string in MySQL, `$tag$ ... $tag$` is a string in
56
+ * PostgreSQL. Row data of `COPY ... FROM stdin` blocks is skipped.
57
+ */
58
+ export function tokenize(text, dialect) {
59
+ const tokens = [];
60
+ const length = text.length;
61
+ const failedDollarTags = new Set();
62
+ let delimiter = ';';
63
+ let index = 0;
64
+ let line = 1;
65
+ let atLineStart = true;
66
+ let statementStart = 0;
67
+ let truncated = false;
68
+ const push = (kind, start, end, startLine, value) => {
69
+ const sourceText = text.slice(start, end);
70
+ tokens.push({
71
+ kind,
72
+ text: sourceText,
73
+ value,
74
+ up: kind === 'word' ? value.toUpperCase() : '',
75
+ start,
76
+ end,
77
+ line: startLine,
78
+ });
79
+ atLineStart = false;
80
+ };
81
+ /** Scans a quoted run that ends at `quote`, where a doubled quote is an escaped quote. */
82
+ const scanQuoted = (from, quote, backslash) => {
83
+ let position = from + 1;
84
+ let value = '';
85
+ let chunkStart = position;
86
+ let lines = 0;
87
+ while (position < length) {
88
+ const character = text.charAt(position);
89
+ if (character === '\n') {
90
+ lines += 1;
91
+ }
92
+ if (backslash && character === '\\' && position + 1 < length) {
93
+ value += text.slice(chunkStart, position);
94
+ const escaped = text.charAt(position + 1);
95
+ if (escaped === '\n') {
96
+ lines += 1;
97
+ }
98
+ value += BACKSLASH_ESCAPES[escaped] ?? escaped;
99
+ position += 2;
100
+ chunkStart = position;
101
+ continue;
102
+ }
103
+ if (character === quote) {
104
+ if (text.charAt(position + 1) === quote) {
105
+ value += text.slice(chunkStart, position + 1);
106
+ position += 2;
107
+ chunkStart = position;
108
+ continue;
109
+ }
110
+ value += text.slice(chunkStart, position);
111
+ return { end: position + 1, value, lines };
112
+ }
113
+ position += 1;
114
+ }
115
+ value += text.slice(chunkStart, length);
116
+ return { end: length, value, lines };
117
+ };
118
+ while (index < length) {
119
+ if (tokens.length >= MAX_TOKENS) {
120
+ truncated = true;
121
+ break;
122
+ }
123
+ const code = text.charCodeAt(index);
124
+ if (code === 10) {
125
+ line += 1;
126
+ index += 1;
127
+ atLineStart = true;
128
+ continue;
129
+ }
130
+ if (code === 32 ||
131
+ code === 9 ||
132
+ code === 13 ||
133
+ code === 12 ||
134
+ code === 0xfeff ||
135
+ code === 0xa0) {
136
+ index += 1;
137
+ continue;
138
+ }
139
+ // Comments.
140
+ if (code === 45 && text.charCodeAt(index + 1) === 45) {
141
+ const newline = text.indexOf('\n', index);
142
+ index = newline === -1 ? length : newline;
143
+ continue;
144
+ }
145
+ if (code === 35 && dialect === 'mysql') {
146
+ const newline = text.indexOf('\n', index);
147
+ index = newline === -1 ? length : newline;
148
+ continue;
149
+ }
150
+ if (code === 47 && text.charCodeAt(index + 1) === 42) {
151
+ const nested = dialect === 'postgresql' || dialect === 'sqlserver';
152
+ let depth = 1;
153
+ let position = index + 2;
154
+ while (position < length && depth > 0) {
155
+ const current = text.charCodeAt(position);
156
+ if (current === 10) {
157
+ line += 1;
158
+ }
159
+ else if (current === 42 && text.charCodeAt(position + 1) === 47) {
160
+ depth -= 1;
161
+ position += 1;
162
+ }
163
+ else if (nested &&
164
+ current === 47 &&
165
+ text.charCodeAt(position + 1) === 42) {
166
+ depth += 1;
167
+ position += 1;
168
+ }
169
+ position += 1;
170
+ }
171
+ index = position;
172
+ continue;
173
+ }
174
+ // psql meta commands (`\connect db`) occupy a whole line.
175
+ if (code === 92 && atLineStart && dialect === 'postgresql') {
176
+ const newline = text.indexOf('\n', index);
177
+ index = newline === -1 ? length : newline;
178
+ continue;
179
+ }
180
+ // A custom statement delimiter (MySQL `DELIMITER //`).
181
+ if (delimiter !== ';' &&
182
+ text.startsWith(delimiter, index) &&
183
+ delimiter.length > 0) {
184
+ push('semi', index, index + delimiter.length, line, delimiter);
185
+ index += delimiter.length;
186
+ statementStart = tokens.length;
187
+ continue;
188
+ }
189
+ const startLine = line;
190
+ // Strings.
191
+ if (code === 39) {
192
+ const scanned = scanQuoted(index, "'", dialect === 'mysql');
193
+ push('string', index, scanned.end, startLine, scanned.value);
194
+ line += scanned.lines;
195
+ index = scanned.end;
196
+ continue;
197
+ }
198
+ // Quoted identifiers.
199
+ if (code === 34) {
200
+ const scanned = scanQuoted(index, '"', dialect === 'mysql');
201
+ push(dialect === 'mysql' ? 'string' : 'ident', index, scanned.end, startLine, scanned.value);
202
+ line += scanned.lines;
203
+ index = scanned.end;
204
+ continue;
205
+ }
206
+ if (code === 96) {
207
+ const scanned = scanQuoted(index, '`', false);
208
+ push('ident', index, scanned.end, startLine, scanned.value);
209
+ line += scanned.lines;
210
+ index = scanned.end;
211
+ continue;
212
+ }
213
+ if (code === 91 && (dialect === 'sqlserver' || dialect === 'sqlite')) {
214
+ let position = index + 1;
215
+ let value = '';
216
+ let closed = false;
217
+ const limit = Math.min(length, index + 258);
218
+ while (position < limit) {
219
+ const character = text.charAt(position);
220
+ if (character === ']') {
221
+ if (text.charAt(position + 1) === ']') {
222
+ value += ']';
223
+ position += 2;
224
+ continue;
225
+ }
226
+ closed = true;
227
+ break;
228
+ }
229
+ if (character === '\n') {
230
+ break;
231
+ }
232
+ value += character;
233
+ position += 1;
234
+ }
235
+ if (closed && value.length > 0) {
236
+ push('ident', index, position + 1, startLine, value);
237
+ index = position + 1;
238
+ continue;
239
+ }
240
+ }
241
+ // Dollar quoting and positional parameters (PostgreSQL).
242
+ if (code === 36 && dialect === 'postgresql') {
243
+ let position = index + 1;
244
+ while (position < length &&
245
+ text.charCodeAt(position) !== 36 &&
246
+ isWordPart(text.charCodeAt(position))) {
247
+ position += 1;
248
+ }
249
+ if (position > index + 1 && isDigit(text.charCodeAt(index + 1))) {
250
+ push('word', index, position, startLine, text.slice(index, position));
251
+ index = position;
252
+ continue;
253
+ }
254
+ if (text.charCodeAt(position) === 36) {
255
+ const tag = text.slice(index, position + 1);
256
+ if (!tag.slice(1, -1).includes('$') &&
257
+ !failedDollarTags.has(tag) &&
258
+ failedDollarTags.size < MAX_FAILED_DOLLAR_TAGS) {
259
+ const close = text.indexOf(tag, position + 1);
260
+ if (close === -1) {
261
+ failedDollarTags.add(tag);
262
+ }
263
+ else {
264
+ const body = text.slice(position + 1, close);
265
+ push('string', index, close + tag.length, startLine, body);
266
+ for (let at = 0; at < body.length; at += 1) {
267
+ if (body.charCodeAt(at) === 10) {
268
+ line += 1;
269
+ }
270
+ }
271
+ index = close + tag.length;
272
+ continue;
273
+ }
274
+ }
275
+ }
276
+ }
277
+ // Numbers.
278
+ if (isDigit(code) || (code === 46 && isDigit(text.charCodeAt(index + 1)))) {
279
+ let position = index;
280
+ if (code === 48 &&
281
+ (text.charCodeAt(index + 1) | 32) === 120 &&
282
+ index + 2 < length) {
283
+ position = index + 2;
284
+ while (position < length && /[0-9a-fA-F]/.test(text.charAt(position))) {
285
+ position += 1;
286
+ }
287
+ }
288
+ else {
289
+ while (position < length && isDigit(text.charCodeAt(position))) {
290
+ position += 1;
291
+ }
292
+ if (text.charCodeAt(position) === 46) {
293
+ position += 1;
294
+ while (position < length && isDigit(text.charCodeAt(position))) {
295
+ position += 1;
296
+ }
297
+ }
298
+ if ((text.charCodeAt(position) | 32) === 101) {
299
+ let exponent = position + 1;
300
+ const sign = text.charCodeAt(exponent);
301
+ if (sign === 43 || sign === 45) {
302
+ exponent += 1;
303
+ }
304
+ if (isDigit(text.charCodeAt(exponent))) {
305
+ while (exponent < length && isDigit(text.charCodeAt(exponent))) {
306
+ exponent += 1;
307
+ }
308
+ position = exponent;
309
+ }
310
+ }
311
+ }
312
+ push('number', index, position, startLine, text.slice(index, position));
313
+ index = position;
314
+ continue;
315
+ }
316
+ // Words (keywords and unquoted identifiers).
317
+ if (isWordStart(code) ||
318
+ (code === 64 && (dialect === 'sqlserver' || dialect === 'mysql')) ||
319
+ (code === 35 && dialect === 'sqlserver')) {
320
+ let position = index + 1;
321
+ while (position < length) {
322
+ const current = text.charCodeAt(position);
323
+ if (isWordPart(current) || current === 64 || current === 35) {
324
+ position += 1;
325
+ }
326
+ else {
327
+ break;
328
+ }
329
+ }
330
+ const word = text.slice(index, position);
331
+ const next = text.charCodeAt(position);
332
+ // Prefixed strings: E'..' (backslash escapes), N'..', B'..', X'..'.
333
+ if (next === 39 && word.length === 1 && /[EeNnBbXx]/.test(word)) {
334
+ const scanned = scanQuoted(position, "'", dialect === 'mysql' || word === 'E' || word === 'e');
335
+ push('string', index, scanned.end, startLine, scanned.value);
336
+ line += scanned.lines;
337
+ index = scanned.end;
338
+ continue;
339
+ }
340
+ // SQL Server batch separator: GO on a line of its own.
341
+ if (dialect === 'sqlserver' &&
342
+ atLineStart &&
343
+ word.toUpperCase() === 'GO') {
344
+ let rest = position;
345
+ while (rest < length &&
346
+ text.charCodeAt(rest) !== 10 &&
347
+ /[ \t\r0-9]/.test(text.charAt(rest))) {
348
+ rest += 1;
349
+ }
350
+ if (rest >= length || text.charCodeAt(rest) === 10) {
351
+ push('semi', index, position, startLine, 'GO');
352
+ index = rest;
353
+ statementStart = tokens.length;
354
+ continue;
355
+ }
356
+ }
357
+ // MySQL DELIMITER command.
358
+ if (dialect === 'mysql' &&
359
+ atLineStart &&
360
+ word.toUpperCase() === 'DELIMITER') {
361
+ const newline = text.indexOf('\n', position);
362
+ const lineEnd = newline === -1 ? length : newline;
363
+ const argument = text.slice(position, lineEnd).trim();
364
+ const firstSpace = argument.search(/\s/);
365
+ delimiter =
366
+ argument === ''
367
+ ? ';'
368
+ : firstSpace === -1
369
+ ? argument
370
+ : argument.slice(0, firstSpace);
371
+ index = lineEnd;
372
+ continue;
373
+ }
374
+ push('word', index, position, startLine, word);
375
+ index = position;
376
+ continue;
377
+ }
378
+ // Statement terminator.
379
+ if (code === 59) {
380
+ push('semi', index, index + 1, startLine, ';');
381
+ index += 1;
382
+ const first = tokens[statementStart];
383
+ if (dialect === 'postgresql' &&
384
+ first !== undefined &&
385
+ first.up === 'COPY') {
386
+ let readsStdin = false;
387
+ for (let at = statementStart; at < tokens.length; at += 1) {
388
+ if (tokens[at]?.up === 'STDIN') {
389
+ readsStdin = true;
390
+ break;
391
+ }
392
+ }
393
+ if (readsStdin) {
394
+ // Skip the data rows, up to the line that holds only `\.`.
395
+ let position = index;
396
+ let found = false;
397
+ while (position < length) {
398
+ const newline = text.indexOf('\n', position);
399
+ const lineEnd = newline === -1 ? length : newline;
400
+ if (text.slice(position, lineEnd).trim() === '\\.') {
401
+ index = Math.min(length, lineEnd + 1);
402
+ line += 1;
403
+ found = true;
404
+ break;
405
+ }
406
+ line += 1;
407
+ position = lineEnd + 1;
408
+ }
409
+ if (!found) {
410
+ index = length;
411
+ }
412
+ atLineStart = true;
413
+ }
414
+ }
415
+ statementStart = tokens.length;
416
+ continue;
417
+ }
418
+ // Everything else is punctuation; `::` is one token.
419
+ if (code === 58 && text.charCodeAt(index + 1) === 58) {
420
+ push('punct', index, index + 2, startLine, '::');
421
+ index += 2;
422
+ continue;
423
+ }
424
+ push('punct', index, index + 1, startLine, text.charAt(index));
425
+ index += 1;
426
+ }
427
+ return { tokens, truncated };
428
+ }
429
+ /** Words that open a routine whose body can contain `;` before the closing `END`. */
430
+ const ROUTINE_KINDS = new Set([
431
+ 'TRIGGER',
432
+ 'PROCEDURE',
433
+ 'FUNCTION',
434
+ 'EVENT',
435
+ ]);
436
+ /** Splits a token list into statements at `semi` tokens, keeping `BEGIN ... END` routine bodies whole. */
437
+ export function splitStatements(tokens) {
438
+ const statements = [];
439
+ let current = [];
440
+ let isRoutine = false;
441
+ let depth = 0;
442
+ for (let at = 0; at < tokens.length; at += 1) {
443
+ const token = tokens[at];
444
+ if (token === undefined) {
445
+ break;
446
+ }
447
+ if (token.kind === 'semi') {
448
+ if (isRoutine && depth > 0) {
449
+ current.push({ ...token, kind: 'punct' });
450
+ continue;
451
+ }
452
+ if (current.length > 0) {
453
+ statements.push(current);
454
+ }
455
+ current = [];
456
+ isRoutine = false;
457
+ depth = 0;
458
+ continue;
459
+ }
460
+ current.push(token);
461
+ if (current.length <= 12 &&
462
+ token.up !== '' &&
463
+ ROUTINE_KINDS.has(token.up)) {
464
+ if ((current[0]?.up ?? '') === 'CREATE') {
465
+ isRoutine = true;
466
+ }
467
+ }
468
+ if (isRoutine && token.kind === 'word') {
469
+ if (token.up === 'BEGIN' || token.up === 'CASE') {
470
+ depth += 1;
471
+ }
472
+ else if (token.up === 'END') {
473
+ const following = tokens[at + 1]?.up ?? '';
474
+ if (following === 'IF' ||
475
+ following === 'LOOP' ||
476
+ following === 'WHILE' ||
477
+ following === 'REPEAT' ||
478
+ following === 'FOR') {
479
+ at += 1;
480
+ current.push(tokens[at] ?? END_TOKEN);
481
+ }
482
+ else if (depth > 0) {
483
+ depth -= 1;
484
+ if (following === 'CASE') {
485
+ at += 1;
486
+ current.push(tokens[at] ?? END_TOKEN);
487
+ }
488
+ }
489
+ }
490
+ }
491
+ }
492
+ if (current.length > 0) {
493
+ statements.push(current);
494
+ }
495
+ return statements;
496
+ }
497
+ /**
498
+ * Guesses the dialect of a file from characteristic syntax. Every pattern is a simple linear regular
499
+ * expression. Ties and files without any hint are read as PostgreSQL.
500
+ */
501
+ export function detectDialect(text) {
502
+ const score = (patterns) => patterns.reduce((total, pattern) => total + (pattern.test(text) ? 1 : 0), 0);
503
+ const scores = {
504
+ sqlserver: score([
505
+ /\bIDENTITY\s*\(\s*\d+\s*,\s*\d+\s*\)/i,
506
+ /^\s*GO\s*$/im,
507
+ /\bn?varchar\s*\(\s*max\s*\)/i,
508
+ /\bNVARCHAR\b/i,
509
+ /\bUNIQUEIDENTIFIER\b/i,
510
+ /\bDATETIME2\b/i,
511
+ /\bNONCLUSTERED\b/i,
512
+ /\bCLUSTERED\b/i,
513
+ /\bSET\s+ANSI_NULLS\b/i,
514
+ /\[dbo\]/i,
515
+ /\bGETDATE\s*\(/i,
516
+ /\bNEWID\s*\(/i,
517
+ ]),
518
+ mysql: score([
519
+ /`/,
520
+ /\bENGINE\s*=/i,
521
+ /\bAUTO_INCREMENT\b/i,
522
+ /\bUNSIGNED\b/i,
523
+ /\bDEFAULT\s+CHARSET\b/i,
524
+ /\bCHARACTER\s+SET\b/i,
525
+ /\bLOCK\s+TABLES\b/i,
526
+ /\bON\s+UPDATE\s+CURRENT_TIMESTAMP\b/i,
527
+ /\bTINYINT\b/i,
528
+ /\bDELIMITER\b/i,
529
+ /\bUNLOCK\s+TABLES\b/i,
530
+ /\bMEDIUMTEXT\b|\bLONGTEXT\b/i,
531
+ ]),
532
+ sqlite: score([
533
+ /\bAUTOINCREMENT\b/i,
534
+ /\bWITHOUT\s+ROWID\b/i,
535
+ /\bPRAGMA\b/i,
536
+ /\bsqlite_/i,
537
+ /\bdatetime\s*\(\s*'now'/i,
538
+ /\bSTRICT\s*;/i,
539
+ ]),
540
+ postgresql: score([
541
+ /\bBIGSERIAL\b|\bSERIAL\b|\bSMALLSERIAL\b/i,
542
+ /::\s*[a-z_]/i,
543
+ /\bAS\s+ENUM\b/i,
544
+ /\bgen_random_uuid\s*\(/i,
545
+ /\$\$/,
546
+ /\bTIMESTAMPTZ\b/i,
547
+ /\bJSONB\b/i,
548
+ /\bGENERATED\s+(ALWAYS|BY\s+DEFAULT)\s+AS\s+IDENTITY\b/i,
549
+ /\bCOMMENT\s+ON\b/i,
550
+ /\bnextval\s*\(/i,
551
+ /\bSET\s+search_path\b/i,
552
+ /\bCREATE\s+EXTENSION\b/i,
553
+ /\bBYTEA\b/i,
554
+ ]),
555
+ };
556
+ let best = 'postgresql';
557
+ for (const dialect of ['sqlserver', 'mysql', 'sqlite']) {
558
+ if (scores[dialect] > scores[best]) {
559
+ best = dialect;
560
+ }
561
+ }
562
+ return best;
563
+ }
564
+ /**
565
+ * Reads a token list without ever failing: reading past the end gives the end token, so the statement
566
+ * parser needs no bounds checks.
567
+ */
568
+ export class Cursor {
569
+ tokens;
570
+ end;
571
+ position;
572
+ constructor(tokens, start = 0, end = tokens.length) {
573
+ this.tokens = tokens;
574
+ this.end = end;
575
+ this.position = start;
576
+ }
577
+ get atEnd() {
578
+ return this.position >= this.end;
579
+ }
580
+ peek(offset = 0) {
581
+ const at = this.position + offset;
582
+ if (at >= this.end) {
583
+ return END_TOKEN;
584
+ }
585
+ return this.tokens[at] ?? END_TOKEN;
586
+ }
587
+ next() {
588
+ const token = this.peek();
589
+ if (this.position < this.end) {
590
+ this.position += 1;
591
+ }
592
+ return token;
593
+ }
594
+ /** True when the token at `offset` is the keyword (a plain word, never a quoted identifier). */
595
+ isKeyword(keyword, offset = 0) {
596
+ const token = this.peek(offset);
597
+ return token.kind === 'word' && token.up === keyword;
598
+ }
599
+ acceptKeyword(keyword) {
600
+ if (this.isKeyword(keyword)) {
601
+ this.position += 1;
602
+ return true;
603
+ }
604
+ return false;
605
+ }
606
+ /** Consumes the keywords in order, or nothing when they do not all match. */
607
+ acceptKeywords(...keywords) {
608
+ for (let at = 0; at < keywords.length; at += 1) {
609
+ if (!this.isKeyword(keywords[at] ?? '', at)) {
610
+ return false;
611
+ }
612
+ }
613
+ this.position += keywords.length;
614
+ return true;
615
+ }
616
+ isPunct(character, offset = 0) {
617
+ const token = this.peek(offset);
618
+ return token.kind === 'punct' && token.text === character;
619
+ }
620
+ acceptPunct(character) {
621
+ if (this.isPunct(character)) {
622
+ this.position += 1;
623
+ return true;
624
+ }
625
+ return false;
626
+ }
627
+ /**
628
+ * When the next token is `(`, returns the tokens inside the matching `)` and moves past it. An unclosed
629
+ * group runs to the end. Returns undefined (and moves nowhere) when the next token is not `(`.
630
+ */
631
+ readGroup() {
632
+ if (!this.isPunct('(')) {
633
+ return undefined;
634
+ }
635
+ this.position += 1;
636
+ const inner = [];
637
+ let depth = 1;
638
+ while (this.position < this.end) {
639
+ const token = this.tokens[this.position] ?? END_TOKEN;
640
+ this.position += 1;
641
+ if (token.kind === 'punct') {
642
+ if (token.text === '(') {
643
+ depth += 1;
644
+ }
645
+ else if (token.text === ')') {
646
+ depth -= 1;
647
+ if (depth === 0) {
648
+ return inner;
649
+ }
650
+ }
651
+ }
652
+ inner.push(token);
653
+ }
654
+ return inner;
655
+ }
656
+ /** Skips every remaining token of the current element. */
657
+ skipToEnd() {
658
+ this.position = this.end;
659
+ }
660
+ }
661
+ /** Splits tokens at commas that are not inside parentheses. Empty parts are dropped. */
662
+ export function splitTopLevel(tokens) {
663
+ const parts = [];
664
+ let current = [];
665
+ let depth = 0;
666
+ for (const token of tokens) {
667
+ if (token.kind === 'punct') {
668
+ if (token.text === '(') {
669
+ depth += 1;
670
+ }
671
+ else if (token.text === ')') {
672
+ depth = Math.max(0, depth - 1);
673
+ }
674
+ else if (token.text === ',' && depth === 0) {
675
+ if (current.length > 0) {
676
+ parts.push(current);
677
+ }
678
+ current = [];
679
+ continue;
680
+ }
681
+ }
682
+ current.push(token);
683
+ }
684
+ if (current.length > 0) {
685
+ parts.push(current);
686
+ }
687
+ return parts;
688
+ }
689
+ /** The source text covered by a token list, with line breaks collapsed. */
690
+ export function sourceOf(text, tokens) {
691
+ const first = tokens[0];
692
+ const last = tokens[tokens.length - 1];
693
+ if (first === undefined || last === undefined) {
694
+ return '';
695
+ }
696
+ return text.slice(first.start, last.end).replace(/\s+/g, ' ').trim();
697
+ }
698
+ //# sourceMappingURL=sqlSyntax.js.map