pg-sql2 4.13.0 → 5.0.0-0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,988 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.true = exports.query = exports.null = exports.isSQL = exports.fragment = exports.false = exports.sql = exports.replaceSymbol = exports.isEquivalent = exports.arraysMatch = exports.placeholder = exports.symbolAlias = exports.parens = exports.indentIf = exports.indent = exports.join = exports.literal = exports.dot = exports.blank = exports.value = exports.identifier = exports.raw = exports.compile = exports.escapeSqlIdentifier = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const lru_1 = tslib_1.__importDefault(require("@graphile/lru"));
6
+ const assert = tslib_1.__importStar(require("assert"));
7
+ const util_1 = require("util");
8
+ function exportAs(thing, exportName) {
9
+ const existingExport = thing.$$export;
10
+ if (existingExport) {
11
+ if (existingExport.exportName !== exportName) {
12
+ throw new Error(`Attempted to export same thing under multiple names '${existingExport.exportName}' and '${exportName}'`);
13
+ }
14
+ }
15
+ else {
16
+ Object.defineProperty(thing, "$$export", {
17
+ value: { moduleName: "pg-sql2", exportName },
18
+ });
19
+ }
20
+ return thing;
21
+ }
22
+ const isDev = process.env.GRAPHILE_ENV === "development";
23
+ /**
24
+ * This is the secret to our safety; since this is a symbol it cannot be faked
25
+ * in a JSON payload and it cannot be constructed with a new Symbol (even with
26
+ * the same argument), so external data cannot make itself trusted.
27
+ */
28
+ const $$type = Symbol("pg-sql2-type");
29
+ const FLAG_HAS_PARENS = 1 << 0;
30
+ /**
31
+ * This helps us to avoid GC overhead of allocating new raw nodes all the time
32
+ * when they're likely to be the same values over and over. The average raw
33
+ * string is likely to be around 20 bytes; allowing for 50 bytes once this has
34
+ * been turned into an object, 10000 would mean 500kB which seems an acceptable
35
+ * amount of memory to consume for this.
36
+ */
37
+ const CACHE_RAW_NODES = new lru_1.default({ maxLength: 10000 });
38
+ /**
39
+ * Given a string, this will return an SQL-identifier-safe version of the
40
+ * string using only the characters [0-9a-z_], at least 1 and at most 50
41
+ * characters in length, with no leading, trailing or consecutive underscores.
42
+ * Information loss is likely, this is only to aid with debugging.
43
+ */
44
+ function mangleName(str) {
45
+ return (str
46
+ // Keep the identifier simple so it doesn't need escaping.
47
+ .replace(/[A-Z]/g, (l) => `_${l.toLowerCase()}`)
48
+ .replace(/[^a-z0-9_]+/g, "_")
49
+ // Avoid double-underscores.
50
+ .replace(/__+/g, "_")
51
+ .replace(/^_/, "")
52
+ // Maximum identifier length in PostgreSQL is 63. We need 5 underscores
53
+ // (`__desc_number__`) and the number might be up to, say, 8 digits long.
54
+ // (It'll never be anywhere near this, surely?) This leaves 50 characters
55
+ // for description.
56
+ .substring(0, 50)
57
+ // Don't end in an underscore, since we'll be adding one anyway (and we
58
+ // don't want double-underscores). This must be done after length limiting
59
+ // otherwise we might prune to a length that ends in an underscore.
60
+ .replace(/_$/, "") || "local");
61
+ }
62
+ // Alas we cannot use WeakMap to cache this because symbols cannot be WeakMap
63
+ // keys (and we wouldn't want to open the user to memory exhaustion via a map).
64
+ // Symbol.prototype.description is available since Node 11.15.0, 12.4.0.
65
+ function getSymbolName(symbol) {
66
+ return mangleName(symbol.description || "local");
67
+ }
68
+ // Copied from https://github.com/brianc/node-postgres/blob/860cccd53105f7bc32fed8b1de69805f0ecd12eb/lib/client.js#L285-L302
69
+ // Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c
70
+ // Trivial performance optimizations by Benjie.
71
+ // Replaced with regexp because it's 11x faster by Benjie.
72
+ function escapeSqlIdentifier(str) {
73
+ return `"${str.replace(/["\0]/g, '""')}"`;
74
+ }
75
+ exports.escapeSqlIdentifier = escapeSqlIdentifier;
76
+ function makeRawNode(text, exportName) {
77
+ const n = CACHE_RAW_NODES.get(text);
78
+ if (n) {
79
+ return n;
80
+ }
81
+ if (typeof text !== "string") {
82
+ throw new Error(`[pg-sql2] Invalid argument to makeRawNode - expected string, but received '${(0, util_1.inspect)(text)}'`);
83
+ }
84
+ const newNode = {
85
+ [$$type]: "RAW",
86
+ t: text,
87
+ };
88
+ if (exportName) {
89
+ exportAs(newNode, exportName);
90
+ }
91
+ Object.freeze(newNode);
92
+ CACHE_RAW_NODES.set(text, newNode);
93
+ return newNode;
94
+ }
95
+ // Simple function to help V8 optimize it.
96
+ function makeIdentifierNode(s, n = getSymbolName(s)) {
97
+ return Object.freeze({
98
+ [$$type]: "IDENTIFIER",
99
+ s,
100
+ n,
101
+ });
102
+ }
103
+ // Simple function to help V8 optimize it.
104
+ function makeValueNode(rawValue) {
105
+ return Object.freeze({
106
+ [$$type]: "VALUE",
107
+ v: rawValue,
108
+ });
109
+ }
110
+ function makeIndentNode(content) {
111
+ const flags = content[$$type] === "QUERY" ? content.f : 0;
112
+ return Object.freeze({
113
+ [$$type]: "INDENT",
114
+ f: flags,
115
+ c: content[$$type] === "QUERY" ? content : makeQueryNode([content]),
116
+ });
117
+ }
118
+ function makeSymbolAliasNode(a, b) {
119
+ return Object.freeze({
120
+ [$$type]: "SYMBOL_ALIAS",
121
+ a: a,
122
+ b: b,
123
+ });
124
+ }
125
+ function makePlaceholderNode(symbol, fallback) {
126
+ return Object.freeze({
127
+ [$$type]: "PLACEHOLDER",
128
+ s: symbol,
129
+ k: fallback,
130
+ });
131
+ }
132
+ const CHARCODE_A = "A".charCodeAt(0);
133
+ function makeQueryNode(nodes, flags = 0) {
134
+ let checksum = 0;
135
+ for (const node of nodes) {
136
+ switch (node[$$type]) {
137
+ case "RAW": {
138
+ const { t } = node;
139
+ for (let i = 0, l = Math.min(t.length, 10000); i < l; i++) {
140
+ checksum += Math.min(t.charCodeAt(i) - CHARCODE_A, 100);
141
+ }
142
+ break;
143
+ }
144
+ case "VALUE": {
145
+ checksum += 211;
146
+ break;
147
+ }
148
+ case "IDENTIFIER": {
149
+ checksum -= 53;
150
+ break;
151
+ }
152
+ case "INDENT": {
153
+ checksum += node.c.c;
154
+ break;
155
+ }
156
+ case "SYMBOL_ALIAS": {
157
+ checksum += 93;
158
+ break;
159
+ }
160
+ case "PLACEHOLDER": {
161
+ checksum += 87;
162
+ break;
163
+ }
164
+ default: {
165
+ const never = node[$$type];
166
+ throw new Error(`Unrecognized node type ${never}`);
167
+ }
168
+ }
169
+ }
170
+ return Object.freeze({
171
+ [$$type]: "QUERY",
172
+ n: nodes,
173
+ f: flags,
174
+ c: checksum,
175
+ });
176
+ }
177
+ function isSQL(node) {
178
+ return (typeof node === "object" &&
179
+ node !== null &&
180
+ typeof node[$$type] === "string");
181
+ }
182
+ exports.isSQL = isSQL;
183
+ function enforceValidNode(node, where) {
184
+ if (isSQL(node)) {
185
+ return node;
186
+ }
187
+ throw new Error(`[pg-sql2] Invalid expression. Expected an SQL item${where ? ` at ${where}` : ""} but received '${(0, util_1.inspect)(node)}'. This may mean that there is an issue in the SQL expression where a dynamic value was not escaped via 'sql.value(...)', an identifier wasn't wrapped with 'sql.identifier(...)', or a SQL expression was added without using the \`sql\` tagged template literal.`);
188
+ }
189
+ /**
190
+ * Accepts an sql`...` expression and compiles it out to SQL text with
191
+ * placeholders, and the values to substitute for these values.
192
+ */
193
+ function compile(sql, options) {
194
+ const placeholderValues = options?.placeholderValues;
195
+ /**
196
+ * Values hold the JavaScript values that are represented in the query string
197
+ * by placeholders. They are eager because they were provided before compile
198
+ * time.
199
+ */
200
+ const values = [];
201
+ let valueCount = 0;
202
+ /**
203
+ * When we come across a symbol in our identifier, we create a unique alias
204
+ * for it that shouldn’t be in the users schema. This helps maintain sanity
205
+ * when constructing large SQL queries with many aliases.
206
+ */
207
+ const symbolToIdentifier = new Map();
208
+ /**
209
+ * When the same description is used more than once in different symbols we
210
+ * must generate different identifiers, so we keep a counter of each symbol
211
+ * usage here.
212
+ */
213
+ const descCounter = Object.create(null);
214
+ /**
215
+ * Makes a friendly name to use in the query for the given SymbolAndName.
216
+ */
217
+ function makeIdentifierForSymbol(symbol, safeDesc) {
218
+ if (!descCounter[safeDesc]) {
219
+ descCounter[safeDesc] = 0;
220
+ }
221
+ const number = ++descCounter[safeDesc];
222
+ // NOTE: we don't omit the suffix for the first instance because safeDesc
223
+ // might end in, e.g., `_1` and cause conflicts later; however we do
224
+ // replace `_1` with `__`.
225
+ const identifierForSymbol = `__${safeDesc}_${number === 1 ? "_" : number}`;
226
+ // Store so this symbol gets the same identifier next time
227
+ symbolToIdentifier.set(symbol, identifierForSymbol);
228
+ return identifierForSymbol;
229
+ }
230
+ function print(untrustedInput, indent = 0) {
231
+ /**
232
+ * Join this to generate the SQL query
233
+ */
234
+ const sqlFragments = [];
235
+ const trustedInput = enforceValidNode(untrustedInput, ``);
236
+ const items = trustedInput[$$type] === "QUERY"
237
+ ? expandQueryNodes(trustedInput)
238
+ : [trustedInput];
239
+ const itemCount = items.length;
240
+ for (let itemIndex = 0; itemIndex < itemCount; itemIndex++) {
241
+ const item = enforceValidNode(items[itemIndex], `item ${itemIndex}`);
242
+ switch (item[$$type]) {
243
+ case "RAW": {
244
+ if (item.t === "") {
245
+ // No need to add blank raw text!
246
+ break;
247
+ }
248
+ // Special-case `;` - remove the previous \n
249
+ if (isDev && itemIndex === itemCount - 1 && item.t === ";") {
250
+ const prevIndex = sqlFragments.length - 1;
251
+ const prev = sqlFragments[prevIndex];
252
+ if (prev.endsWith("\n")) {
253
+ sqlFragments[prevIndex] = prev.substring(0, prev.length - 1);
254
+ }
255
+ }
256
+ sqlFragments.push(isDev ? item.t.replace(/\n/g, "\n" + " ".repeat(indent)) : item.t);
257
+ break;
258
+ }
259
+ case "IDENTIFIER": {
260
+ // Get the correct identifier string for this symbol.
261
+ let identifierForSymbol = symbolToIdentifier.get(item.s);
262
+ // If there is no identifier, create one and set it.
263
+ if (!identifierForSymbol) {
264
+ identifierForSymbol = makeIdentifierForSymbol(item.s, item.n);
265
+ }
266
+ // Return the identifier. Since we create it, we won’t have to
267
+ // escape it because we know all of the characters are safe.
268
+ sqlFragments.push(identifierForSymbol);
269
+ break;
270
+ }
271
+ case "VALUE": {
272
+ valueCount++;
273
+ if (valueCount > 65535) {
274
+ throw new Error("[pg-sql2] This SQL statement would contain too many placeholders; PostgreSQL supports at most 65535 placeholders. To solve this, consider refactoring the query to use arrays/unnest where possible, or split it into multiple queries.");
275
+ }
276
+ values[valueCount - 1] = item.v;
277
+ sqlFragments.push(`$${valueCount}`);
278
+ break;
279
+ }
280
+ case "INDENT": {
281
+ assert.ok(isDev, "INDENT nodes only allowed in development mode");
282
+ sqlFragments.push("\n" +
283
+ " ".repeat(indent + 1) +
284
+ print(item.c, indent + 1) +
285
+ "\n" +
286
+ " ".repeat(indent));
287
+ break;
288
+ }
289
+ case "SYMBOL_ALIAS": {
290
+ const symbol1 = item.a;
291
+ const symbol2 = item.b;
292
+ const name1 = symbolToIdentifier.get(symbol1);
293
+ const name2 = symbolToIdentifier.get(symbol2);
294
+ if (name1 && name2 && name1 !== name2) {
295
+ throw new Error("ERROR: sql.symbolAlias was used after the symbols were used, they already have (non-matching) aliases");
296
+ }
297
+ if (name1) {
298
+ symbolToIdentifier.set(symbol2, name1);
299
+ }
300
+ else if (name2) {
301
+ symbolToIdentifier.set(symbol1, name2);
302
+ }
303
+ else {
304
+ const identifierForSymbol = makeIdentifierForSymbol(symbol1, getSymbolName(symbol1));
305
+ symbolToIdentifier.set(symbol1, identifierForSymbol);
306
+ symbolToIdentifier.set(symbol2, identifierForSymbol);
307
+ }
308
+ break;
309
+ }
310
+ case "PLACEHOLDER": {
311
+ // TODO: symbol substitutes?
312
+ const resolvedPlaceholder = placeholderValues?.get(item.s) ?? item.k;
313
+ if (!resolvedPlaceholder) {
314
+ throw new Error("ERROR: sql.placeholder was used in this query, but no value was supplied for it, and it has no fallback.");
315
+ }
316
+ sqlFragments.push(print(resolvedPlaceholder, indent));
317
+ break;
318
+ }
319
+ default: {
320
+ const never = item;
321
+ // This cannot happen
322
+ throw new Error(`Unsupported node found in SQL: ${(0, util_1.inspect)(never)}`);
323
+ }
324
+ }
325
+ }
326
+ return sqlFragments.join("");
327
+ }
328
+ const text = isDev ? print(sql).replace(/\n\s*\n/g, "\n") : print(sql);
329
+ return {
330
+ text,
331
+ values,
332
+ };
333
+ }
334
+ exports.compile = compile;
335
+ // LRU not necessary
336
+ const CACHE_SIMPLE_FRAGMENTS = new Map();
337
+ /**
338
+ * A template string tag that creates a `SQL` query out of some strings and
339
+ * some values. Use this to construct all PostgreSQL queries to avoid SQL
340
+ * injection.
341
+ *
342
+ * Note that using this function, the user *must* specify if they are injecting
343
+ * raw text. This makes a SQL injection vulnerability harder to create.
344
+ */
345
+ const sqlBase = function sql(strings, ...values) {
346
+ if (!Array.isArray(strings) || !strings.raw) {
347
+ throw new Error("[pg-sql2] sql should be used as a template literal, not a function call.");
348
+ }
349
+ const stringsLength = strings.length;
350
+ const first = strings[0];
351
+ // Reduce memory churn with a cache
352
+ if (stringsLength === 1) {
353
+ if (first === "") {
354
+ return exports.blank;
355
+ }
356
+ let node = CACHE_SIMPLE_FRAGMENTS.get(first);
357
+ if (!node) {
358
+ node = makeRawNode(first);
359
+ CACHE_SIMPLE_FRAGMENTS.set(first, node);
360
+ }
361
+ return node;
362
+ }
363
+ // Special case sql`${...}` - just return the node directly
364
+ if (stringsLength === 2 && strings[0] === "" && strings[1] === "") {
365
+ return values[0];
366
+ }
367
+ const items = [];
368
+ let currentText = "";
369
+ for (let i = 0, l = stringsLength; i < l; i++) {
370
+ const text = strings[i];
371
+ if (typeof text !== "string") {
372
+ throw new Error("[pg-sql2] sql.query must be invoked as a template literal, not a function call.");
373
+ }
374
+ currentText += text;
375
+ if (i < l - 1) {
376
+ const rawVal = values[i];
377
+ const valid = enforceValidNode(rawVal, `template literal placeholder ${i}`);
378
+ if (valid[$$type] === "RAW") {
379
+ currentText += valid.t;
380
+ }
381
+ else if (valid[$$type] === "QUERY") {
382
+ // NOTE: this clears the flags
383
+ const nodes = expandQueryNodes(valid);
384
+ const nodeCount = nodes.length;
385
+ for (let nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) {
386
+ const node = nodes[nodeIndex];
387
+ if (node[$$type] === "RAW") {
388
+ currentText += node.t;
389
+ }
390
+ else {
391
+ if (currentText !== "") {
392
+ items.push(makeRawNode(currentText));
393
+ currentText = "";
394
+ }
395
+ items.push(node);
396
+ }
397
+ }
398
+ }
399
+ else {
400
+ if (currentText !== "") {
401
+ items.push(makeRawNode(currentText));
402
+ currentText = "";
403
+ }
404
+ items.push(valid);
405
+ }
406
+ }
407
+ }
408
+ if (currentText !== "") {
409
+ items.push(makeRawNode(currentText));
410
+ currentText = "";
411
+ }
412
+ return items.length === 1 ? items[0] : makeQueryNode(items);
413
+ };
414
+ let sqlRawWarningOutput = false;
415
+ /**
416
+ * Creates a SQL item for some raw SQL text. Just plain ol‘ raw SQL. This
417
+ * method is dangerous though because it involves no escaping, so proceed with
418
+ * caution! It's very very rarely warranted - there is likely a safer way of
419
+ * achieving your goal.
420
+ */
421
+ function raw(text) {
422
+ if (!sqlRawWarningOutput) {
423
+ sqlRawWarningOutput = true;
424
+ try {
425
+ throw new Error("sql.raw first invoked here");
426
+ }
427
+ catch (e) {
428
+ console.warn(`[pg-sql2] WARNING: you're using the sql.raw escape hatch, usage of this API is rarely required and is highly discouraged. Please be sure this is what you intend. ${e.stack}`);
429
+ }
430
+ }
431
+ if (typeof text !== "string") {
432
+ throw new Error(`[pg-sql2] sql.raw must be passed a string, but it was passed '${(0, util_1.inspect)(text)}'.`);
433
+ }
434
+ return makeRawNode(text);
435
+ }
436
+ exports.raw = raw;
437
+ /**
438
+ * Creates a SQL item for a SQL identifier. A SQL identifier is anything like
439
+ * a table, schema, or column name. An identifier may also have a namespace,
440
+ * thus why many names are accepted.
441
+ */
442
+ function identifier(...names) {
443
+ // By doing the validation here rather than at compile time we can reduce
444
+ // redundant work - the same sql.identifier can be used in multiple queries.
445
+ const nameCount = names.length;
446
+ if (nameCount === 0) {
447
+ throw new Error("[pg-sql2] Invalid call to sql.identifier() - you must pass at least one argument.");
448
+ }
449
+ const items = [];
450
+ let currentText = "";
451
+ for (let i = 0; i < nameCount; i++) {
452
+ if (i > 0) {
453
+ currentText += ".";
454
+ }
455
+ const name = names[i];
456
+ if (typeof name === "string") {
457
+ // By escaping here rather than during compile we can reduce redundant computation.
458
+ const escaped = escapeSqlIdentifier(name);
459
+ currentText += escaped;
460
+ }
461
+ else if (typeof name === "symbol") {
462
+ if (currentText !== "") {
463
+ items.push(makeRawNode(currentText));
464
+ currentText = "";
465
+ }
466
+ // The final name has to be evaluated at compile-time, but we can at least mangle it up front.
467
+ items.push(makeIdentifierNode(name));
468
+ }
469
+ else {
470
+ throw new Error(`[pg-sql2] Invalid argument to sql.identifier - argument ${i} (0-indexed) should be a string or a symbol, but was '${(0, util_1.inspect)(name)}'`);
471
+ }
472
+ }
473
+ if (currentText !== "") {
474
+ items.push(makeRawNode(currentText));
475
+ currentText = "";
476
+ }
477
+ return items.length === 1 ? items[0] : makeQueryNode(items);
478
+ }
479
+ exports.identifier = identifier;
480
+ /**
481
+ * Creates a SQL item for a value that will be included in our final query.
482
+ * This value will be added in a way which avoids SQL injection.
483
+ */
484
+ function value(val) {
485
+ return makeValueNode(val);
486
+ }
487
+ exports.value = value;
488
+ const trueNode = makeRawNode(`TRUE`, "true");
489
+ exports.true = trueNode;
490
+ const falseNode = makeRawNode(`FALSE`, "false");
491
+ exports.false = falseNode;
492
+ const nullNode = makeRawNode(`NULL`, "null");
493
+ exports.null = nullNode;
494
+ exports.blank = makeRawNode(``, "blank");
495
+ exports.dot = makeRawNode(`.`, "dot");
496
+ const OPEN_PARENS = makeRawNode(`(`);
497
+ const CLOSE_PARENS = makeRawNode(`)`);
498
+ /**
499
+ * If the value is simple will inline it into the query, otherwise will defer
500
+ * to `sql.value`.
501
+ */
502
+ function literal(val) {
503
+ if (typeof val === "string" && val.match(/^[-a-zA-Z0-9_@!$ :".]*$/)) {
504
+ // Examples of things we'd like to be included raw:
505
+ // - 1
506
+ // - myFieldName
507
+ // - @@myFieldName
508
+ // - $myField$
509
+ // - YYYY-MM-DD"T"HH24:MI:SS.USTZHTZM
510
+ return makeRawNode(`'${val}'`);
511
+ }
512
+ else if (typeof val === "number" && Number.isFinite(val)) {
513
+ if (Number.isInteger(val)) {
514
+ return makeRawNode("" + val);
515
+ }
516
+ else {
517
+ return makeRawNode(`'${0 + val}'::float`);
518
+ }
519
+ }
520
+ else if (typeof val === "boolean") {
521
+ return val ? trueNode : falseNode;
522
+ }
523
+ else if (val == null) {
524
+ return nullNode;
525
+ }
526
+ else {
527
+ return value(val);
528
+ }
529
+ }
530
+ exports.literal = literal;
531
+ /**
532
+ * Join some SQL items together, optionally separated by a string. Useful when
533
+ * dealing with lists of SQL items, for example a dynamic list of columns or
534
+ * variadic SQL function arguments.
535
+ */
536
+ function join(items, separator = "") {
537
+ if (!Array.isArray(items)) {
538
+ throw new Error(`[pg-sql2] Invalid sql.join call - the first argument should be an array, but it was '${(0, util_1.inspect)(items)}'.`);
539
+ }
540
+ if (typeof separator !== "string") {
541
+ throw new Error(`[pg-sql2] Invalid separator passed to sql.join - must be a string, but we received '${(0, util_1.inspect)(separator)}'`);
542
+ }
543
+ // Short circuit joins of size <= 1
544
+ if (items.length === 0) {
545
+ return exports.blank;
546
+ }
547
+ else if (items.length === 1) {
548
+ const rawNode = items[0];
549
+ const node = enforceValidNode(rawNode, `join item ${0}`);
550
+ return node;
551
+ }
552
+ const hasSeparator = separator.length > 0;
553
+ let currentText = "";
554
+ const currentItems = [];
555
+ for (let i = 0, l = items.length; i < l; i++) {
556
+ const rawNode = items[i];
557
+ const addSeparator = i > 0 && hasSeparator;
558
+ const node = enforceValidNode(rawNode, `join item ${i}`);
559
+ if (addSeparator) {
560
+ currentText += separator;
561
+ }
562
+ if (node[$$type] === "QUERY") {
563
+ for (const innerNode of expandQueryNodes(node)) {
564
+ if (innerNode[$$type] === "RAW") {
565
+ currentText += innerNode.t;
566
+ }
567
+ else {
568
+ if (currentText !== "") {
569
+ currentItems.push(makeRawNode(currentText));
570
+ currentText = "";
571
+ }
572
+ currentItems.push(innerNode);
573
+ }
574
+ }
575
+ }
576
+ else if (node[$$type] === "RAW") {
577
+ currentText += node.t;
578
+ }
579
+ else {
580
+ if (currentText !== "") {
581
+ currentItems.push(makeRawNode(currentText));
582
+ currentText = "";
583
+ }
584
+ currentItems.push(node);
585
+ }
586
+ }
587
+ if (currentText !== "") {
588
+ currentItems.push(makeRawNode(currentText));
589
+ currentText = "";
590
+ }
591
+ return currentItems.length === 1
592
+ ? currentItems[0]
593
+ : makeQueryNode(currentItems);
594
+ }
595
+ exports.join = join;
596
+ function expandQueryNodes(node) {
597
+ const parens = (node.f & FLAG_HAS_PARENS) === FLAG_HAS_PARENS;
598
+ if (parens) {
599
+ return [OPEN_PARENS, ...node.n, CLOSE_PARENS];
600
+ }
601
+ else {
602
+ return node.n;
603
+ }
604
+ }
605
+ function indent(fragmentOrStrings, ...values) {
606
+ const fragment = "raw" in fragmentOrStrings
607
+ ? (0, exports.sql)(fragmentOrStrings, ...values)
608
+ : fragmentOrStrings;
609
+ if (!isDev) {
610
+ return fragment;
611
+ }
612
+ return makeIndentNode(fragment);
613
+ }
614
+ exports.indent = indent;
615
+ function indentIf(condition, fragment) {
616
+ return isDev && condition ? makeIndentNode(fragment) : fragment;
617
+ }
618
+ exports.indentIf = indentIf;
619
+ const QUOTED_IDENTIFIER_REGEX = /^"[^"]+"$/;
620
+ const UNQUOTED_IDENTIFIER_REGEX = /^[a-zA-Z0-9_]+$/;
621
+ const NUMBER_REGEX_1 = /^[0-9]+(?:\.[0-9]+)?$/;
622
+ const NUMBER_REGEX_2 = /^\.[0-9]+$/;
623
+ const STRING_REGEX = /^'[^']+'$/;
624
+ /** For determining if a raw SQL string looks like an identifier (for parens reasons) */
625
+ function isIdentifierLike(p) {
626
+ return p.match(QUOTED_IDENTIFIER_REGEX) || p.match(UNQUOTED_IDENTIFIER_REGEX);
627
+ }
628
+ function parenthesize(fragment, inFlags = 0) {
629
+ const flags = inFlags | FLAG_HAS_PARENS;
630
+ if (fragment[$$type] === "QUERY") {
631
+ return makeQueryNode(fragment.n, flags);
632
+ }
633
+ else {
634
+ return makeQueryNode([fragment], flags);
635
+ }
636
+ }
637
+ /**
638
+ * Wraps the given fragment in parens if necessary (or if forced, e.g. for a
639
+ * subquery or maybe stylistically a join condition).
640
+ *
641
+ * Returns the input SQL fragment if it does not need parenthesis to be
642
+ * inserted into another expression, otherwise a parenthesised fragment if not
643
+ * doing so could cause ambiguity. We're relying on the user to be sensible
644
+ * here, this is not fool-proof.
645
+ *
646
+ * @remarks The following are all parens safe:
647
+ *
648
+ * - A placeholder `$1`
649
+ * - A number `0.123456`
650
+ * - A string `'Foo bar'` / `E'Foo bar'`
651
+ * - An identifier `table.column` / `"MyTaBlE"."MyCoLuMn"`
652
+ *
653
+ * The following might seem but are not parens safe:
654
+ *
655
+ * - A function call `schema.func(param)` - reason: `schema.func(param).*`
656
+ * should be `(schema.func(param)).*`
657
+ * - A simple expression `1 = 2` - reason: `1 = 2 = false` is invalid; whereas
658
+ * `(1 = 2) = false` is fine. Similarly `1 = 2::text` differs from `(1 = 2)::text`.
659
+ * - An identifier `table.column.attribute` / `"MyTaBlE"."MyCoLuMn"."MyAtTrIbUtE"` (this needs to be `(table.column).attribute`)
660
+ */
661
+ function parens(frag, force) {
662
+ if (frag[$$type] === "QUERY") {
663
+ if ((frag.f & FLAG_HAS_PARENS) === FLAG_HAS_PARENS) {
664
+ return frag;
665
+ }
666
+ const { n: nodes } = frag;
667
+ const nodeCount = nodes.length;
668
+ if (nodeCount === 0) {
669
+ throw new Error(`You're wrapping an empty fragment in parens; this is likely an error. If this is deliberate, please explicitly use parenthesis.`);
670
+ }
671
+ else if (nodeCount === 1) {
672
+ return parens(nodes[0], force);
673
+ }
674
+ else if (force) {
675
+ return parenthesize(frag);
676
+ }
677
+ else if (nodeCount === 2) {
678
+ // Check for `IDENTIFIER.rawtext`
679
+ // TODO: check for 'rawtext.IDENTIFIER' too
680
+ const [identifier, rawtext] = nodes;
681
+ if (identifier[$$type] !== "IDENTIFIER" ||
682
+ rawtext[$$type] !== "RAW" ||
683
+ !rawtext.t.startsWith(".")) {
684
+ return parenthesize(frag);
685
+ }
686
+ const parts = rawtext.t.slice(1).split(".");
687
+ if (!parts.every(isIdentifierLike)) {
688
+ return parenthesize(frag);
689
+ }
690
+ return frag;
691
+ }
692
+ else if (nodeCount === 3) {
693
+ // Check for `IDENTIFIER.IDENTIFIER`
694
+ for (let i = 0; i < nodeCount; i++) {
695
+ const node = nodes[i];
696
+ if (i % 2 === 0) {
697
+ if (node[$$type] !== "IDENTIFIER" &&
698
+ (node[$$type] !== "RAW" || !isIdentifierLike(node.t))) {
699
+ return parenthesize(frag);
700
+ }
701
+ }
702
+ else {
703
+ if (node[$$type] !== "RAW" || node.t !== ".") {
704
+ return parenthesize(frag);
705
+ }
706
+ }
707
+ }
708
+ return frag;
709
+ }
710
+ else {
711
+ return parenthesize(frag);
712
+ }
713
+ }
714
+ else if (frag[$$type] === "INDENT") {
715
+ const inner = parens(frag.c, force);
716
+ if (inner[$$type] === "QUERY" &&
717
+ (inner.f & FLAG_HAS_PARENS) === FLAG_HAS_PARENS) {
718
+ // Move the parens to outside
719
+ return parenthesize(indent(makeQueryNode(inner.n)), inner.f);
720
+ }
721
+ else if (inner === frag.c) {
722
+ return frag;
723
+ }
724
+ else {
725
+ return makeIndentNode(inner);
726
+ }
727
+ }
728
+ else if (force) {
729
+ return parenthesize(frag);
730
+ }
731
+ else if (frag[$$type] === "VALUE") {
732
+ return frag;
733
+ }
734
+ else if (frag[$$type] === "IDENTIFIER") {
735
+ return frag;
736
+ }
737
+ else if (frag[$$type] === "RAW") {
738
+ const expr = frag.t;
739
+ if (expr.match(NUMBER_REGEX_1) || expr.match(NUMBER_REGEX_2)) {
740
+ return frag;
741
+ }
742
+ else if (expr.match(STRING_REGEX)) {
743
+ return frag;
744
+ }
745
+ else {
746
+ // Identifiers
747
+ const parts = expr.split(".");
748
+ if (parts.every(isIdentifierLike)) {
749
+ return frag;
750
+ }
751
+ }
752
+ }
753
+ return parenthesize(frag);
754
+ }
755
+ exports.parens = parens;
756
+ function symbolAlias(symbol1, symbol2) {
757
+ return makeSymbolAliasNode(symbol1, symbol2);
758
+ }
759
+ exports.symbolAlias = symbolAlias;
760
+ function placeholder(symbol, fallback) {
761
+ return makePlaceholderNode(symbol, fallback);
762
+ }
763
+ exports.placeholder = placeholder;
764
+ function arraysMatch(array1, array2, comparator) {
765
+ if (array1 === array2)
766
+ return true;
767
+ const l = array1.length;
768
+ if (l !== array2.length) {
769
+ return false;
770
+ }
771
+ for (let i = 0; i < l; i++) {
772
+ if (comparator ? !comparator(array1[i], array2[i]) : array1[i] !== array2[i]) {
773
+ return false;
774
+ }
775
+ }
776
+ return true;
777
+ }
778
+ exports.arraysMatch = arraysMatch;
779
+ /*
780
+ export function isEquivalentSymbol(
781
+ sql1: symbol,
782
+ sql2: symbol,
783
+ options?: {
784
+ symbolSubstitutes?: Map<symbol, symbol>;
785
+ },
786
+ ): boolean {
787
+ if (sql1 === sql2) {
788
+ return true;
789
+ }
790
+ const symbolSubstitutes = options?.symbolSubstitutes;
791
+ return symbolSubstitutes?.get(sql1) === sql2;
792
+ }
793
+ */
794
+ function isEquivalent(sql1, sql2, options) {
795
+ if (sql1 === sql2) {
796
+ return true;
797
+ }
798
+ else if (sql1[$$type] === "QUERY") {
799
+ if (sql2[$$type] !== "QUERY" || sql2.f !== sql1.f || sql2.c !== sql1.c) {
800
+ return false;
801
+ }
802
+ return arraysMatch(sql1.n, sql2.n, (a, b) => isEquivalent(a, b, options));
803
+ }
804
+ else if (sql2[$$type] === "QUERY") {
805
+ return false;
806
+ }
807
+ else {
808
+ switch (sql1[$$type]) {
809
+ case "RAW": {
810
+ if (sql2[$$type] !== sql1[$$type]) {
811
+ return false;
812
+ }
813
+ return sql1.t === sql2.t;
814
+ }
815
+ case "VALUE": {
816
+ if (sql2[$$type] !== sql1[$$type]) {
817
+ return false;
818
+ }
819
+ return sql1.v === sql2.v;
820
+ }
821
+ case "INDENT": {
822
+ if (sql2[$$type] !== sql1[$$type]) {
823
+ return false;
824
+ }
825
+ return isEquivalent(sql1.c, sql2.c, options);
826
+ }
827
+ case "IDENTIFIER": {
828
+ if (sql2[$$type] !== sql1[$$type]) {
829
+ return false;
830
+ }
831
+ const { n: ids1n, s: ids1s } = sql1;
832
+ const { n: ids2n, s: ids2s } = sql2;
833
+ const namesMatch = ids1n === ids2n;
834
+ const symbolSubstitutes = options?.symbolSubstitutes;
835
+ const symbol1 = getSubstitute(ids1s, symbolSubstitutes);
836
+ const symbol2 = getSubstitute(ids2s, symbolSubstitutes);
837
+ const symbolsMatch = symbol1 === symbol2;
838
+ return namesMatch && symbolsMatch;
839
+ }
840
+ case "PLACEHOLDER": {
841
+ if (sql2[$$type] !== sql1[$$type]) {
842
+ return false;
843
+ }
844
+ const symbolSubstitutes = options?.symbolSubstitutes;
845
+ const symbol1 = getSubstitute(sql1.s, symbolSubstitutes);
846
+ const symbol2 = getSubstitute(sql2.s, symbolSubstitutes);
847
+ return symbol1 === symbol2;
848
+ }
849
+ case "SYMBOL_ALIAS": {
850
+ // TODO
851
+ return false;
852
+ }
853
+ default: {
854
+ const never = sql1;
855
+ console.error(`Unhandled node type: ${(0, util_1.inspect)(never)}`);
856
+ return false;
857
+ }
858
+ }
859
+ }
860
+ }
861
+ exports.isEquivalent = isEquivalent;
862
+ function replaceSymbolInNode(frag, needle, replacement) {
863
+ switch (frag[$$type]) {
864
+ case "RAW": {
865
+ return frag;
866
+ }
867
+ case "IDENTIFIER": {
868
+ if (frag.s === needle) {
869
+ return makeIdentifierNode(replacement);
870
+ }
871
+ else {
872
+ return frag;
873
+ }
874
+ }
875
+ case "VALUE": {
876
+ return frag.v === needle
877
+ ? makeValueNode(replacement)
878
+ : frag;
879
+ }
880
+ case "INDENT": {
881
+ return makeIndentNode(replaceSymbol(frag.c, needle, replacement));
882
+ }
883
+ case "SYMBOL_ALIAS": {
884
+ const { a, b } = frag;
885
+ const newA = a === needle ? replacement : a;
886
+ const newB = b === needle ? replacement : b;
887
+ if (newA !== a || newB !== b) {
888
+ return makeSymbolAliasNode(newA, newB);
889
+ }
890
+ else {
891
+ return frag;
892
+ }
893
+ }
894
+ case "PLACEHOLDER": {
895
+ if (frag.s === needle) {
896
+ return makePlaceholderNode(replacement, frag.k);
897
+ }
898
+ else {
899
+ return frag;
900
+ }
901
+ }
902
+ default: {
903
+ const never = frag;
904
+ throw new Error(`Unhandled SQL type ${never[$$type]}`);
905
+ }
906
+ }
907
+ }
908
+ /**
909
+ * @experimental
910
+ */
911
+ function replaceSymbol(frag, needle, replacement) {
912
+ if (frag[$$type] === "QUERY") {
913
+ let changed = false;
914
+ const newNodes = frag.n.map((node) => {
915
+ const newNode = replaceSymbolInNode(node, needle, replacement);
916
+ if (newNode !== node) {
917
+ changed = true;
918
+ }
919
+ return newNode;
920
+ });
921
+ return changed ? makeQueryNode(newNodes, frag.f) : frag;
922
+ }
923
+ else {
924
+ return replaceSymbolInNode(frag, needle, replacement);
925
+ }
926
+ }
927
+ exports.replaceSymbol = replaceSymbol;
928
+ /**
929
+ * @internal
930
+ */
931
+ function getSubstitute(initialSymbol, symbolSubstitutes) {
932
+ const path = [];
933
+ let symbol = initialSymbol;
934
+ for (let i = 0; i < 1000; i++) {
935
+ if (path.includes(symbol)) {
936
+ throw new Error(`symbolSubstitute cycle detected: ${path
937
+ .map((s) => (0, util_1.inspect)(s))
938
+ .join(" -> ")} -> ${(0, util_1.inspect)(symbol)}`);
939
+ }
940
+ const sub = symbolSubstitutes?.get(symbol);
941
+ if (sub === symbol) {
942
+ throw new Error(`Invalid symbolSubstitutes - a symbol cannot be an alias for itself! Symbol: ${(0, util_1.inspect)(symbol)}`);
943
+ }
944
+ else if (sub) {
945
+ path.push(symbol);
946
+ symbol = sub;
947
+ }
948
+ else {
949
+ return symbol;
950
+ }
951
+ }
952
+ throw new Error("symbolSubstitutes depth too deep");
953
+ }
954
+ exports.sql = sqlBase;
955
+ exports.fragment = exports.sql;
956
+ exports.query = exports.sql;
957
+ exports.default = exports.sql;
958
+ const attributes = {
959
+ sql: exports.sql,
960
+ escapeSqlIdentifier,
961
+ compile,
962
+ isEquivalent,
963
+ query: exports.sql,
964
+ raw,
965
+ identifier,
966
+ value,
967
+ literal,
968
+ join,
969
+ indent,
970
+ indentIf,
971
+ parens,
972
+ symbolAlias,
973
+ placeholder,
974
+ blank: exports.blank,
975
+ fragment: exports.sql,
976
+ true: trueNode,
977
+ false: falseNode,
978
+ null: nullNode,
979
+ replaceSymbol,
980
+ isSQL,
981
+ };
982
+ Object.entries(attributes).forEach(([exportName, value]) => {
983
+ if (!value.$$export) {
984
+ exportAs(value, exportName);
985
+ }
986
+ });
987
+ Object.assign(sqlBase, attributes);
988
+ //# sourceMappingURL=index.js.map