turbine-orm 0.81.0 → 0.90.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -25
- package/dist/checkout.d.ts +27 -2
- package/dist/checkout.js +75 -4
- package/dist/cjs/checkout.d.ts +27 -2
- package/dist/cjs/checkout.js +76 -3
- package/dist/cjs/cli/code-first-tags.d.ts +62 -0
- package/dist/cjs/cli/code-first-tags.js +124 -0
- package/dist/cjs/cli/compile-query.js +9 -0
- package/dist/cjs/cli/config.d.ts +35 -6
- package/dist/cjs/cli/config.js +17 -1
- package/dist/cjs/cli/destructive.d.ts +7 -4
- package/dist/cjs/cli/destructive.js +224 -20
- package/dist/cjs/cli/error-catalog.js +36 -0
- package/dist/cjs/cli/error-text.d.ts +15 -0
- package/dist/cjs/cli/error-text.js +40 -0
- package/dist/cjs/cli/index.d.ts +62 -4
- package/dist/cjs/cli/index.js +568 -108
- package/dist/cjs/cli/local-server-guard.d.ts +53 -0
- package/dist/cjs/cli/local-server-guard.js +119 -0
- package/dist/cjs/cli/local-server-output.d.ts +41 -0
- package/dist/cjs/cli/local-server-output.js +103 -0
- package/dist/cjs/cli/mcp.d.ts +8 -0
- package/dist/cjs/cli/mcp.js +121 -32
- package/dist/cjs/cli/migrate.d.ts +113 -5
- package/dist/cjs/cli/migrate.js +270 -21
- package/dist/cjs/cli/observe.d.ts +6 -1
- package/dist/cjs/cli/observe.js +85 -55
- package/dist/cjs/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cjs/cli/pii-predicate-guard.js +98 -37
- package/dist/cjs/cli/prisma-resolve.js +17 -13
- package/dist/cjs/cli/prisma-schema.js +12 -4
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/studio.d.ts +14 -2
- package/dist/cjs/cli/studio.js +244 -69
- package/dist/cjs/cli/ui.d.ts +1 -1
- package/dist/cjs/client.d.ts +558 -85
- package/dist/cjs/client.js +1270 -395
- package/dist/cjs/connection-guard.d.ts +13 -0
- package/dist/cjs/connection-guard.js +28 -0
- package/dist/cjs/connection-url.d.ts +51 -16
- package/dist/cjs/connection-url.js +67 -34
- package/dist/cjs/diagnostics.d.ts +81 -0
- package/dist/cjs/diagnostics.js +147 -0
- package/dist/cjs/dialect.d.ts +449 -20
- package/dist/cjs/dialect.js +117 -76
- package/dist/cjs/engine-config.d.ts +7 -6
- package/dist/cjs/errors.d.ts +126 -6
- package/dist/cjs/errors.js +256 -13
- package/dist/cjs/generate.d.ts +57 -7
- package/dist/cjs/generate.js +635 -279
- package/dist/cjs/global-filters-config.d.ts +59 -0
- package/dist/cjs/global-filters-config.js +367 -0
- package/dist/cjs/index.d.ts +9 -4
- package/dist/cjs/index.js +10 -2
- package/dist/cjs/introspect.d.ts +31 -6
- package/dist/cjs/introspect.js +108 -50
- package/dist/cjs/log-codes.d.ts +12 -0
- package/dist/cjs/log-codes.js +77 -0
- package/dist/cjs/logger.d.ts +87 -0
- package/dist/cjs/logger.js +98 -0
- package/dist/cjs/mssql.d.ts +8 -5
- package/dist/cjs/mssql.js +352 -65
- package/dist/cjs/mysql.d.ts +3 -5
- package/dist/cjs/mysql.js +420 -70
- package/dist/cjs/nested-write.d.ts +3 -0
- package/dist/cjs/nested-write.js +179 -114
- package/dist/cjs/pipeline-submittable.d.ts +12 -0
- package/dist/cjs/pipeline-submittable.js +118 -44
- package/dist/cjs/pipeline.js +21 -26
- package/dist/cjs/powdb-introspect.d.ts +5 -2
- package/dist/cjs/powdb-introspect.js +20 -15
- package/dist/cjs/powdb-shared.d.ts +36 -5
- package/dist/cjs/powdb-shared.js +67 -8
- package/dist/cjs/powdb.d.ts +4 -0
- package/dist/cjs/powdb.js +183 -51
- package/dist/cjs/powql.d.ts +394 -51
- package/dist/cjs/powql.js +1829 -284
- package/dist/cjs/prisma-compat.d.ts +23 -15
- package/dist/cjs/prisma-compat.js +507 -372
- package/dist/cjs/query/aggregates.d.ts +17 -1
- package/dist/cjs/query/aggregates.js +261 -29
- package/dist/cjs/query/batched-loader.d.ts +25 -6
- package/dist/cjs/query/batched-loader.js +103 -20
- package/dist/cjs/query/builder.d.ts +325 -184
- package/dist/cjs/query/builder.js +1452 -490
- package/dist/cjs/query/column-ref.d.ts +61 -0
- package/dist/cjs/query/column-ref.js +73 -0
- package/dist/cjs/query/compound-unique.d.ts +21 -0
- package/dist/cjs/query/compound-unique.js +98 -18
- package/dist/cjs/query/deferred.d.ts +53 -16
- package/dist/cjs/query/expr.d.ts +217 -0
- package/dist/cjs/query/expr.js +859 -0
- package/dist/cjs/query/filters.d.ts +155 -53
- package/dist/cjs/query/filters.js +613 -76
- package/dist/cjs/query/index.d.ts +2 -0
- package/dist/cjs/query/index.js +7 -1
- package/dist/cjs/query/json-value.d.ts +43 -0
- package/dist/cjs/query/json-value.js +59 -0
- package/dist/cjs/query/normalize-args.d.ts +87 -0
- package/dist/cjs/query/normalize-args.js +527 -0
- package/dist/cjs/query/option-surface.d.ts +16 -4
- package/dist/cjs/query/option-surface.js +57 -14
- package/dist/cjs/query/relation-names.d.ts +14 -0
- package/dist/cjs/query/relation-names.js +77 -18
- package/dist/cjs/query/relations.d.ts +30 -3
- package/dist/cjs/query/relations.js +219 -63
- package/dist/cjs/query/row-lock.d.ts +49 -0
- package/dist/cjs/query/row-lock.js +138 -0
- package/dist/cjs/query/sql-comment.d.ts +161 -0
- package/dist/cjs/query/sql-comment.js +381 -0
- package/dist/cjs/query/stream-idle.d.ts +132 -0
- package/dist/cjs/query/stream-idle.js +212 -0
- package/dist/cjs/query/table-client.d.ts +101 -0
- package/dist/cjs/query/table-client.js +74 -0
- package/dist/cjs/query/types.d.ts +677 -298
- package/dist/cjs/query/utils.d.ts +105 -6
- package/dist/cjs/query/utils.js +426 -19
- package/dist/cjs/query/warn-registry.d.ts +18 -0
- package/dist/cjs/query/warn-registry.js +18 -0
- package/dist/cjs/query/where-compile.d.ts +138 -14
- package/dist/cjs/query/where-compile.js +513 -29
- package/dist/cjs/query/where.d.ts +159 -59
- package/dist/cjs/query/where.js +716 -246
- package/dist/cjs/query/writes.d.ts +112 -50
- package/dist/cjs/query/writes.js +1498 -200
- package/dist/cjs/query-events.d.ts +4 -2
- package/dist/cjs/query-events.js +3 -1
- package/dist/cjs/realtime.d.ts +7 -3
- package/dist/cjs/realtime.js +13 -2
- package/dist/cjs/schema-builder.d.ts +55 -4
- package/dist/cjs/schema-builder.js +382 -18
- package/dist/cjs/schema-metadata.d.ts +21 -1
- package/dist/cjs/schema-metadata.js +29 -7
- package/dist/cjs/schema-sql.d.ts +75 -5
- package/dist/cjs/schema-sql.js +497 -45
- package/dist/cjs/schema.d.ts +26 -5
- package/dist/cjs/schema.js +110 -13
- package/dist/cjs/sql-placeholders.d.ts +56 -0
- package/dist/cjs/sql-placeholders.js +204 -0
- package/dist/cjs/sqlite.d.ts +75 -20
- package/dist/cjs/sqlite.js +593 -95
- package/dist/cjs/supplied-pool-types.d.ts +48 -0
- package/dist/cjs/supplied-pool-types.js +256 -0
- package/dist/cjs/transaction-control.d.ts +51 -0
- package/dist/cjs/transaction-control.js +316 -0
- package/dist/cjs/transaction-state.d.ts +342 -0
- package/dist/cjs/transaction-state.js +707 -0
- package/dist/cjs/typed-sql.d.ts +37 -3
- package/dist/cjs/typed-sql.js +102 -8
- package/dist/cli/code-first-tags.d.ts +62 -0
- package/dist/cli/code-first-tags.js +120 -0
- package/dist/cli/compile-query.js +9 -0
- package/dist/cli/config.d.ts +35 -6
- package/dist/cli/config.js +16 -1
- package/dist/cli/destructive.d.ts +7 -4
- package/dist/cli/destructive.js +224 -20
- package/dist/cli/error-catalog.js +36 -0
- package/dist/cli/error-text.d.ts +15 -0
- package/dist/cli/error-text.js +37 -0
- package/dist/cli/index.d.ts +62 -4
- package/dist/cli/index.js +569 -112
- package/dist/cli/local-server-guard.d.ts +53 -0
- package/dist/cli/local-server-guard.js +113 -0
- package/dist/cli/local-server-output.d.ts +41 -0
- package/dist/cli/local-server-output.js +98 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +121 -34
- package/dist/cli/migrate.d.ts +113 -5
- package/dist/cli/migrate.js +261 -21
- package/dist/cli/observe.d.ts +6 -1
- package/dist/cli/observe.js +84 -54
- package/dist/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cli/pii-predicate-guard.js +100 -35
- package/dist/cli/prisma-resolve.js +15 -11
- package/dist/cli/prisma-schema.js +12 -4
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/studio.d.ts +14 -2
- package/dist/cli/studio.js +246 -71
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +558 -85
- package/dist/client.js +1275 -400
- package/dist/connection-guard.d.ts +13 -0
- package/dist/connection-guard.js +27 -0
- package/dist/connection-url.d.ts +51 -16
- package/dist/connection-url.js +63 -34
- package/dist/diagnostics.d.ts +81 -0
- package/dist/diagnostics.js +144 -0
- package/dist/dialect.d.ts +449 -20
- package/dist/dialect.js +115 -43
- package/dist/engine-config.d.ts +7 -6
- package/dist/errors.d.ts +126 -6
- package/dist/errors.js +252 -12
- package/dist/generate.d.ts +57 -7
- package/dist/generate.js +635 -281
- package/dist/global-filters-config.d.ts +59 -0
- package/dist/global-filters-config.js +361 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +4 -2
- package/dist/introspect.d.ts +31 -6
- package/dist/introspect.js +106 -50
- package/dist/log-codes.d.ts +12 -0
- package/dist/log-codes.js +74 -0
- package/dist/logger.d.ts +87 -0
- package/dist/logger.js +93 -0
- package/dist/mssql.d.ts +8 -5
- package/dist/mssql.js +354 -67
- package/dist/mysql.d.ts +3 -5
- package/dist/mysql.js +423 -73
- package/dist/nested-write.d.ts +3 -0
- package/dist/nested-write.js +177 -112
- package/dist/pipeline-submittable.d.ts +12 -0
- package/dist/pipeline-submittable.js +117 -44
- package/dist/pipeline.js +21 -26
- package/dist/powdb-introspect.d.ts +5 -2
- package/dist/powdb-introspect.js +21 -16
- package/dist/powdb-shared.d.ts +36 -5
- package/dist/powdb-shared.js +65 -8
- package/dist/powdb.d.ts +4 -0
- package/dist/powdb.js +183 -51
- package/dist/powql.d.ts +394 -51
- package/dist/powql.js +1836 -291
- package/dist/prisma-compat.d.ts +23 -15
- package/dist/prisma-compat.js +508 -373
- package/dist/query/aggregates.d.ts +17 -1
- package/dist/query/aggregates.js +263 -32
- package/dist/query/batched-loader.d.ts +25 -6
- package/dist/query/batched-loader.js +101 -21
- package/dist/query/builder.d.ts +325 -184
- package/dist/query/builder.js +1458 -495
- package/dist/query/column-ref.d.ts +61 -0
- package/dist/query/column-ref.js +66 -0
- package/dist/query/compound-unique.d.ts +21 -0
- package/dist/query/compound-unique.js +97 -19
- package/dist/query/deferred.d.ts +53 -16
- package/dist/query/expr.d.ts +217 -0
- package/dist/query/expr.js +848 -0
- package/dist/query/filters.d.ts +155 -53
- package/dist/query/filters.js +597 -72
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +4 -0
- package/dist/query/json-value.d.ts +43 -0
- package/dist/query/json-value.js +54 -0
- package/dist/query/normalize-args.d.ts +87 -0
- package/dist/query/normalize-args.js +517 -0
- package/dist/query/option-surface.d.ts +16 -4
- package/dist/query/option-surface.js +56 -13
- package/dist/query/relation-names.d.ts +14 -0
- package/dist/query/relation-names.js +77 -19
- package/dist/query/relations.d.ts +30 -3
- package/dist/query/relations.js +220 -67
- package/dist/query/row-lock.d.ts +49 -0
- package/dist/query/row-lock.js +131 -0
- package/dist/query/sql-comment.d.ts +161 -0
- package/dist/query/sql-comment.js +374 -0
- package/dist/query/stream-idle.d.ts +132 -0
- package/dist/query/stream-idle.js +203 -0
- package/dist/query/table-client.d.ts +101 -0
- package/dist/query/table-client.js +70 -0
- package/dist/query/types.d.ts +677 -298
- package/dist/query/utils.d.ts +105 -6
- package/dist/query/utils.js +414 -18
- package/dist/query/warn-registry.d.ts +18 -0
- package/dist/query/warn-registry.js +18 -0
- package/dist/query/where-compile.d.ts +138 -14
- package/dist/query/where-compile.js +508 -31
- package/dist/query/where.d.ts +159 -59
- package/dist/query/where.js +711 -248
- package/dist/query/writes.d.ts +112 -50
- package/dist/query/writes.js +1496 -202
- package/dist/query-events.d.ts +4 -2
- package/dist/query-events.js +3 -1
- package/dist/realtime.d.ts +7 -3
- package/dist/realtime.js +13 -2
- package/dist/schema-builder.d.ts +55 -4
- package/dist/schema-builder.js +381 -18
- package/dist/schema-metadata.d.ts +21 -1
- package/dist/schema-metadata.js +28 -7
- package/dist/schema-sql.d.ts +75 -5
- package/dist/schema-sql.js +494 -44
- package/dist/schema.d.ts +26 -5
- package/dist/schema.js +109 -13
- package/dist/sql-placeholders.d.ts +56 -0
- package/dist/sql-placeholders.js +200 -0
- package/dist/sqlite.d.ts +75 -20
- package/dist/sqlite.js +597 -99
- package/dist/supplied-pool-types.d.ts +48 -0
- package/dist/supplied-pool-types.js +247 -0
- package/dist/transaction-control.d.ts +51 -0
- package/dist/transaction-control.js +311 -0
- package/dist/transaction-state.d.ts +342 -0
- package/dist/transaction-state.js +696 -0
- package/dist/typed-sql.d.ts +37 -3
- package/dist/typed-sql.js +100 -8
- package/package.json +7 -4
- package/skills/turbine-orm/SKILL.md +56 -6
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turbine-orm, Turbine's column readings on a pool the caller supplied.
|
|
3
|
+
*
|
|
4
|
+
* Turbine reads `bigint` (int8) as a number and, under `utcTimestamps`, the
|
|
5
|
+
* zone-less `timestamp` and `date` types (and their arrays) as UTC. On a pool
|
|
6
|
+
* Turbine opens, those parsers are registered on the pg module. A pool the
|
|
7
|
+
* caller supplies keeps the caller's parser table, so Turbine attaches its
|
|
8
|
+
* parsers to each of its OWN queries instead: node-postgres reads a
|
|
9
|
+
* query-level `types` in place of the connection's. The caller's own queries
|
|
10
|
+
* on the same pool keep the caller's parsers.
|
|
11
|
+
*
|
|
12
|
+
* Everything Turbine does not govern is read from the table the query would
|
|
13
|
+
* otherwise have used (the connection's, else the pool's `types` option, else
|
|
14
|
+
* the driver's), so a parser the caller installed for another type still
|
|
15
|
+
* applies to Turbine's reads.
|
|
16
|
+
*/
|
|
17
|
+
import type { PgCompatPool, PgCompatPoolClient } from './pg-types.js';
|
|
18
|
+
/** Where node-postgres reads a parser from: `pg.types`, a pool's `types` option, a client's table. */
|
|
19
|
+
export interface TypeParserTable {
|
|
20
|
+
getTypeParser(oid: number, format?: string): unknown;
|
|
21
|
+
}
|
|
22
|
+
/** int8 as a number while the number is exact, else the driver's string. */
|
|
23
|
+
export declare const parseInt8: (text: string) => number | string;
|
|
24
|
+
/**
|
|
25
|
+
* A parser table with Turbine's readings over `base`. `base` is read at lookup
|
|
26
|
+
* time, so a parser the caller registers later is still the fallback. Built
|
|
27
|
+
* parsers are cached per OID and rebuilt when what they were built over
|
|
28
|
+
* changes.
|
|
29
|
+
*/
|
|
30
|
+
export declare function turbineTypeTable(base: () => TypeParserTable, utcTimestamps: boolean): TypeParserTable;
|
|
31
|
+
/**
|
|
32
|
+
* The pool Turbine sends its own queries through, for a caller-supplied
|
|
33
|
+
* PostgreSQL pool: `query` and every client from `connect` carry Turbine's
|
|
34
|
+
* parsers. Everything else (`end`, counts, events, `instanceof`) is the pool's
|
|
35
|
+
* own.
|
|
36
|
+
*
|
|
37
|
+
* Returns `undefined`, meaning "use the pool as it is", for a pool that is not
|
|
38
|
+
* built on node-postgres (it may not read a query config at all) and for a
|
|
39
|
+
* build without the driver's parsers. A driver that accepts a config but
|
|
40
|
+
* ignores its `types` keeps its own readings.
|
|
41
|
+
*/
|
|
42
|
+
export declare function typedQueryPool(pool: PgCompatPool, utcTimestamps: boolean): PgCompatPool | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* The driver connection behind a client {@link typedQueryPool} handed out, or
|
|
45
|
+
* `client` itself. Used where the caller is given the connection to run their
|
|
46
|
+
* own statements on, which keep the driver's readings.
|
|
47
|
+
*/
|
|
48
|
+
export declare function rawClientOf(client: PgCompatPoolClient): PgCompatPoolClient;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turbine-orm, Turbine's column readings on a pool the caller supplied.
|
|
3
|
+
*
|
|
4
|
+
* Turbine reads `bigint` (int8) as a number and, under `utcTimestamps`, the
|
|
5
|
+
* zone-less `timestamp` and `date` types (and their arrays) as UTC. On a pool
|
|
6
|
+
* Turbine opens, those parsers are registered on the pg module. A pool the
|
|
7
|
+
* caller supplies keeps the caller's parser table, so Turbine attaches its
|
|
8
|
+
* parsers to each of its OWN queries instead: node-postgres reads a
|
|
9
|
+
* query-level `types` in place of the connection's. The caller's own queries
|
|
10
|
+
* on the same pool keep the caller's parsers.
|
|
11
|
+
*
|
|
12
|
+
* Everything Turbine does not govern is read from the table the query would
|
|
13
|
+
* otherwise have used (the connection's, else the pool's `types` option, else
|
|
14
|
+
* the driver's), so a parser the caller installed for another type still
|
|
15
|
+
* applies to Turbine's reads.
|
|
16
|
+
*/
|
|
17
|
+
import pg from '#pg';
|
|
18
|
+
import { createFastTimestamptzParser, createPgArrayParser, createUtcDateParser, createUtcTimestampParser, isDefaultTextParser, markTurbineParser, } from './query/utils.js';
|
|
19
|
+
/** Same key `markTurbineParser` sets (`Symbol.for`, so ESM and CJS copies agree). */
|
|
20
|
+
const TURBINE_PARSER = Symbol.for('turbine.typeParser');
|
|
21
|
+
/** Reads the driver connection back out of a typed client (see {@link rawClientOf}). */
|
|
22
|
+
const RAW_CLIENT = Symbol.for('turbine.rawClient');
|
|
23
|
+
function isTurbineParser(parser) {
|
|
24
|
+
return typeof parser === 'function' && parser[TURBINE_PARSER] === true;
|
|
25
|
+
}
|
|
26
|
+
/** int8 as a number while the number is exact, else the driver's string. */
|
|
27
|
+
export const parseInt8 = markTurbineParser((text) => {
|
|
28
|
+
const n = Number(text);
|
|
29
|
+
return Number.isSafeInteger(n) ? n : text;
|
|
30
|
+
});
|
|
31
|
+
/** The OIDs `utcTimestamps` governs (1184/1185 only for decode speed, as in `registerUtcTemporalParsers`). */
|
|
32
|
+
const UTC_OIDS = new Set([1082, 1114, 1115, 1182, 1184, 1185]);
|
|
33
|
+
/** Array OID to element OID, for the forms built over a scalar parser. */
|
|
34
|
+
const ARRAY_ELEMENT = { 1115: 1114, 1182: 1082, 1185: 1184 };
|
|
35
|
+
/**
|
|
36
|
+
* A parser table with Turbine's readings over `base`. `base` is read at lookup
|
|
37
|
+
* time, so a parser the caller registers later is still the fallback. Built
|
|
38
|
+
* parsers are cached per OID and rebuilt when what they were built over
|
|
39
|
+
* changes.
|
|
40
|
+
*/
|
|
41
|
+
export function turbineTypeTable(base, utcTimestamps) {
|
|
42
|
+
const built = new Map();
|
|
43
|
+
const parserFor = (oid) => {
|
|
44
|
+
const fallback = base().getTypeParser(oid, 'text');
|
|
45
|
+
if (oid !== 20 && !(utcTimestamps && UTC_OIDS.has(oid)))
|
|
46
|
+
return fallback;
|
|
47
|
+
const elementOid = ARRAY_ELEMENT[oid];
|
|
48
|
+
const element = elementOid === undefined ? undefined : parserFor(elementOid);
|
|
49
|
+
const cached = built.get(oid);
|
|
50
|
+
if (cached && cached.from === fallback && cached.element === element)
|
|
51
|
+
return cached.parser;
|
|
52
|
+
const parser = isTurbineParser(fallback) ? fallback : build(oid, fallback, element);
|
|
53
|
+
built.set(oid, { from: fallback, element, parser });
|
|
54
|
+
return parser;
|
|
55
|
+
};
|
|
56
|
+
const build = (oid, fallback, element) => {
|
|
57
|
+
switch (oid) {
|
|
58
|
+
case 20:
|
|
59
|
+
return parseInt8;
|
|
60
|
+
case 1082:
|
|
61
|
+
return markTurbineParser(createUtcDateParser(fallback));
|
|
62
|
+
case 1114:
|
|
63
|
+
return markTurbineParser(createUtcTimestampParser(fallback));
|
|
64
|
+
case 1115:
|
|
65
|
+
case 1182:
|
|
66
|
+
return markTurbineParser(createPgArrayParser(element));
|
|
67
|
+
case 1184:
|
|
68
|
+
// Speed only: a caller's own timestamptz parser is kept, as on owned pools.
|
|
69
|
+
return isDefaultTextParser(1184, fallback)
|
|
70
|
+
? markTurbineParser(createFastTimestamptzParser(fallback))
|
|
71
|
+
: fallback;
|
|
72
|
+
case 1185:
|
|
73
|
+
return isTurbineParser(element) && isDefaultTextParser(1185, fallback)
|
|
74
|
+
? markTurbineParser(createPgArrayParser(element))
|
|
75
|
+
: fallback;
|
|
76
|
+
default:
|
|
77
|
+
return fallback;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
getTypeParser(oid, format = 'text') {
|
|
82
|
+
return format === 'text' ? parserFor(oid) : base().getTypeParser(oid, format);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
let driverTypes;
|
|
87
|
+
/**
|
|
88
|
+
* Whether this build has the real driver's parsers to build from. Under the
|
|
89
|
+
* `edge-light` and `browser` conditions `#pg` is a stand-in with none, so
|
|
90
|
+
* Turbine leaves a supplied pool's parsing to that pool's driver there.
|
|
91
|
+
*/
|
|
92
|
+
function driverTypesAvailable() {
|
|
93
|
+
if (driverTypes === undefined) {
|
|
94
|
+
try {
|
|
95
|
+
const arrays = pg.types
|
|
96
|
+
.arrayParser;
|
|
97
|
+
driverTypes = Array.isArray(arrays.create('{}', (v) => v).parse());
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
driverTypes = false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return driverTypes;
|
|
104
|
+
}
|
|
105
|
+
function isTable(value) {
|
|
106
|
+
return (typeof value === 'object' &&
|
|
107
|
+
value !== null &&
|
|
108
|
+
typeof value.getTypeParser === 'function');
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The table a query on `pool` would use without Turbine: a connected client's
|
|
112
|
+
* (which already folds in the pool's `types` option and the driver copy the
|
|
113
|
+
* pool was built from), else the pool's `types` option, else this module's
|
|
114
|
+
* driver. pg-pool keeps every client it opened in `_clients`; a pool without
|
|
115
|
+
* that field falls through to the option and the driver.
|
|
116
|
+
*/
|
|
117
|
+
function poolTypeBase(pool) {
|
|
118
|
+
let fromClient;
|
|
119
|
+
return () => {
|
|
120
|
+
if (fromClient)
|
|
121
|
+
return fromClient;
|
|
122
|
+
const clients = pool._clients;
|
|
123
|
+
if (Array.isArray(clients)) {
|
|
124
|
+
for (const client of clients) {
|
|
125
|
+
const types = client?._types;
|
|
126
|
+
if (isTable(types)) {
|
|
127
|
+
fromClient = types;
|
|
128
|
+
return types;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const configured = pool.options?.types;
|
|
133
|
+
return isTable(configured) ? configured : pg.types;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* `(text, values)` or a query config, with `types` attached. A submittable
|
|
138
|
+
* (a cursor) carries its own result: it gets the table the same way
|
|
139
|
+
* node-postgres gives it the connection's, only when it has none.
|
|
140
|
+
*/
|
|
141
|
+
function withTypes(textOrConfig, values, types) {
|
|
142
|
+
if (typeof textOrConfig === 'string') {
|
|
143
|
+
return values === undefined ? { text: textOrConfig, types } : { text: textOrConfig, values, types };
|
|
144
|
+
}
|
|
145
|
+
if (typeof textOrConfig !== 'object' || textOrConfig === null)
|
|
146
|
+
return textOrConfig;
|
|
147
|
+
const config = textOrConfig;
|
|
148
|
+
if (typeof config.submit === 'function') {
|
|
149
|
+
if (config._result && !config._result._types)
|
|
150
|
+
config._result._types = types;
|
|
151
|
+
return config;
|
|
152
|
+
}
|
|
153
|
+
if (config.types !== undefined)
|
|
154
|
+
return config;
|
|
155
|
+
return values === undefined ? { ...config, types } : { ...config, values, types };
|
|
156
|
+
}
|
|
157
|
+
/** Forward every other member to the target, bound to it, so the driver's internals see the real object. */
|
|
158
|
+
function forwardingProxy(target, own) {
|
|
159
|
+
const bound = new Map();
|
|
160
|
+
return new Proxy(target, {
|
|
161
|
+
get(t, prop) {
|
|
162
|
+
const mine = own(prop);
|
|
163
|
+
if (mine !== undefined)
|
|
164
|
+
return mine;
|
|
165
|
+
const value = Reflect.get(t, prop, t);
|
|
166
|
+
if (typeof value !== 'function')
|
|
167
|
+
return value;
|
|
168
|
+
const hit = bound.get(prop);
|
|
169
|
+
if (hit && hit.fn === value)
|
|
170
|
+
return hit.bound;
|
|
171
|
+
const fn = value.bind(t);
|
|
172
|
+
bound.set(prop, { fn: value, bound: fn });
|
|
173
|
+
return fn;
|
|
174
|
+
},
|
|
175
|
+
set(t, prop, value) {
|
|
176
|
+
return Reflect.set(t, prop, value, t);
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The pool Turbine sends its own queries through, for a caller-supplied
|
|
182
|
+
* PostgreSQL pool: `query` and every client from `connect` carry Turbine's
|
|
183
|
+
* parsers. Everything else (`end`, counts, events, `instanceof`) is the pool's
|
|
184
|
+
* own.
|
|
185
|
+
*
|
|
186
|
+
* Returns `undefined`, meaning "use the pool as it is", for a pool that is not
|
|
187
|
+
* built on node-postgres (it may not read a query config at all) and for a
|
|
188
|
+
* build without the driver's parsers. A driver that accepts a config but
|
|
189
|
+
* ignores its `types` keeps its own readings.
|
|
190
|
+
*/
|
|
191
|
+
export function typedQueryPool(pool, utcTimestamps) {
|
|
192
|
+
if (typeof pool.Client !== 'function' || !driverTypesAvailable()) {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
const target = pool;
|
|
196
|
+
const table = turbineTypeTable(poolTypeBase(pool), utcTimestamps);
|
|
197
|
+
const clients = new WeakMap();
|
|
198
|
+
const typedClient = (client) => {
|
|
199
|
+
const known = clients.get(client);
|
|
200
|
+
if (known)
|
|
201
|
+
return known;
|
|
202
|
+
const own = client._types;
|
|
203
|
+
const clientTable = isTable(own) ? turbineTypeTable(() => own, utcTimestamps) : table;
|
|
204
|
+
const send = client.query;
|
|
205
|
+
const query = (textOrConfig, values, ...more) => more.length > 0 || typeof values === 'function'
|
|
206
|
+
? send.call(client, textOrConfig, values, ...more)
|
|
207
|
+
: send.call(client, withTypes(textOrConfig, values, clientTable));
|
|
208
|
+
const typed = forwardingProxy(client, (prop) => {
|
|
209
|
+
if (prop === 'query')
|
|
210
|
+
return query;
|
|
211
|
+
if (prop === '_types')
|
|
212
|
+
return clientTable;
|
|
213
|
+
if (prop === RAW_CLIENT)
|
|
214
|
+
return client;
|
|
215
|
+
return undefined;
|
|
216
|
+
});
|
|
217
|
+
clients.set(client, typed);
|
|
218
|
+
return typed;
|
|
219
|
+
};
|
|
220
|
+
const query = (textOrConfig, values, ...more) => {
|
|
221
|
+
const send = target.query;
|
|
222
|
+
return more.length > 0 || typeof values === 'function'
|
|
223
|
+
? send.call(pool, textOrConfig, values, ...more)
|
|
224
|
+
: send.call(pool, withTypes(textOrConfig, values, table));
|
|
225
|
+
};
|
|
226
|
+
const connect = (...args) => {
|
|
227
|
+
const open = target.connect;
|
|
228
|
+
if (args.length > 0)
|
|
229
|
+
return open.apply(pool, args);
|
|
230
|
+
return open.call(pool).then(typedClient);
|
|
231
|
+
};
|
|
232
|
+
return forwardingProxy(target, (prop) => {
|
|
233
|
+
if (prop === 'query')
|
|
234
|
+
return query;
|
|
235
|
+
if (prop === 'connect')
|
|
236
|
+
return connect;
|
|
237
|
+
return undefined;
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* The driver connection behind a client {@link typedQueryPool} handed out, or
|
|
242
|
+
* `client` itself. Used where the caller is given the connection to run their
|
|
243
|
+
* own statements on, which keep the driver's readings.
|
|
244
|
+
*/
|
|
245
|
+
export function rawClientOf(client) {
|
|
246
|
+
return client[RAW_CLIENT] ?? client;
|
|
247
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turbine-orm, finding transaction-control statements in SQL the caller wrote
|
|
3
|
+
*
|
|
4
|
+
* A `COMMIT`, `ROLLBACK`, `BEGIN`, `SAVEPOINT` (and the rest of the family) sent
|
|
5
|
+
* through a transaction's raw paths ends or reshapes the transaction behind the
|
|
6
|
+
* state that tracks it: later statements autocommit, a savepoint sits on a stack
|
|
7
|
+
* Turbine did not push, and the final COMMIT or ROLLBACK reports on a transaction
|
|
8
|
+
* that is no longer there. {@link TransactionState.runRaw} refuses them, and this
|
|
9
|
+
* leaf is how it recognises one.
|
|
10
|
+
*
|
|
11
|
+
* It reads statement STARTS only: the leading words of every statement in the
|
|
12
|
+
* text, found with the lexical rules of the engine that will run it, so a
|
|
13
|
+
* `COMMIT` inside a string literal, a quoted identifier, a dollar-quoted body or a
|
|
14
|
+
* comment is not mistaken for a statement. It is NOT a second statement
|
|
15
|
+
* splitter: `cli/sql-statements.ts` owns that job for migrations, and
|
|
16
|
+
* `src/test/fx-tx-control.test.ts` holds this file to it (same statement
|
|
17
|
+
* boundaries, same first word, over an adversarial corpus). It lives apart from
|
|
18
|
+
* that file because the CLI tokenizer is a CLI module (the library imports one
|
|
19
|
+
* file from `cli/`, `destructive.ts`, and nothing else), and importing it here
|
|
20
|
+
* would put it in every engine entry and the edge entry, whose size is a
|
|
21
|
+
* published claim.
|
|
22
|
+
*
|
|
23
|
+
* Where a reading is ambiguous the scan treats more text as quoted, never less:
|
|
24
|
+
* a statement it fails to see goes through exactly as it did before this guard
|
|
25
|
+
* existed, while one it invents inside a literal would refuse a valid query.
|
|
26
|
+
*
|
|
27
|
+
* Zero imports.
|
|
28
|
+
*/
|
|
29
|
+
/** Which engine's lexical rules to read the text with. */
|
|
30
|
+
export type ControlSyntax = 'postgresql' | 'mysql' | 'sqlite' | 'mssql';
|
|
31
|
+
export type ControlKind = 'begin' | 'commit' | 'rollback' | 'rollback-to' | 'savepoint' | 'release' | 'set-transaction' | 'prepare';
|
|
32
|
+
/** One transaction-control statement found in a piece of SQL. */
|
|
33
|
+
export interface ControlStatement {
|
|
34
|
+
readonly kind: ControlKind;
|
|
35
|
+
/** The statement's leading keyword(s) in upper case, for a message (`COMMIT`, `START TRANSACTION`). */
|
|
36
|
+
readonly label: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Every transaction-control statement in `sql`, in order. Empty when there is
|
|
40
|
+
* none, which is the answer for almost every statement and costs one pass.
|
|
41
|
+
*/
|
|
42
|
+
export declare function findTransactionControl(sql: string, syntax: ControlSyntax): ControlStatement[];
|
|
43
|
+
/**
|
|
44
|
+
* The leading words of every statement in `sql`, lower-cased (`?` stands for a
|
|
45
|
+
* token that is not a word: a literal, a quoted identifier, a symbol). This is
|
|
46
|
+
* the whole of what the scan decides, exported so the test that holds it to
|
|
47
|
+
* `cli/sql-statements.ts` can compare statement boundaries directly.
|
|
48
|
+
*/
|
|
49
|
+
export declare function leadingWords(sql: string, syntax: ControlSyntax): string[][];
|
|
50
|
+
/** Whether `text` could hold a transaction-control statement at all (a cheap test before the scan). */
|
|
51
|
+
export declare function mayHoldTransactionControl(text: string): boolean;
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turbine-orm, finding transaction-control statements in SQL the caller wrote
|
|
3
|
+
*
|
|
4
|
+
* A `COMMIT`, `ROLLBACK`, `BEGIN`, `SAVEPOINT` (and the rest of the family) sent
|
|
5
|
+
* through a transaction's raw paths ends or reshapes the transaction behind the
|
|
6
|
+
* state that tracks it: later statements autocommit, a savepoint sits on a stack
|
|
7
|
+
* Turbine did not push, and the final COMMIT or ROLLBACK reports on a transaction
|
|
8
|
+
* that is no longer there. {@link TransactionState.runRaw} refuses them, and this
|
|
9
|
+
* leaf is how it recognises one.
|
|
10
|
+
*
|
|
11
|
+
* It reads statement STARTS only: the leading words of every statement in the
|
|
12
|
+
* text, found with the lexical rules of the engine that will run it, so a
|
|
13
|
+
* `COMMIT` inside a string literal, a quoted identifier, a dollar-quoted body or a
|
|
14
|
+
* comment is not mistaken for a statement. It is NOT a second statement
|
|
15
|
+
* splitter: `cli/sql-statements.ts` owns that job for migrations, and
|
|
16
|
+
* `src/test/fx-tx-control.test.ts` holds this file to it (same statement
|
|
17
|
+
* boundaries, same first word, over an adversarial corpus). It lives apart from
|
|
18
|
+
* that file because the CLI tokenizer is a CLI module (the library imports one
|
|
19
|
+
* file from `cli/`, `destructive.ts`, and nothing else), and importing it here
|
|
20
|
+
* would put it in every engine entry and the edge entry, whose size is a
|
|
21
|
+
* published claim.
|
|
22
|
+
*
|
|
23
|
+
* Where a reading is ambiguous the scan treats more text as quoted, never less:
|
|
24
|
+
* a statement it fails to see goes through exactly as it did before this guard
|
|
25
|
+
* existed, while one it invents inside a literal would refuse a valid query.
|
|
26
|
+
*
|
|
27
|
+
* Zero imports.
|
|
28
|
+
*/
|
|
29
|
+
/** Words that may follow `BEGIN` when it opens a transaction (and not a block, `BEGIN TRY`, `BEGIN ATOMIC`). */
|
|
30
|
+
const BEGIN_NEXT = new Set([
|
|
31
|
+
'work',
|
|
32
|
+
'transaction',
|
|
33
|
+
'tran',
|
|
34
|
+
'isolation',
|
|
35
|
+
'read',
|
|
36
|
+
'not',
|
|
37
|
+
'deferrable',
|
|
38
|
+
'deferred',
|
|
39
|
+
'immediate',
|
|
40
|
+
'exclusive',
|
|
41
|
+
'distributed',
|
|
42
|
+
]);
|
|
43
|
+
/** Leading words kept per statement: enough for `ROLLBACK WORK TO SAVEPOINT name`. */
|
|
44
|
+
const LEADING_WORDS = 4;
|
|
45
|
+
const WORD_START = /[A-Za-z_\u0080-]/;
|
|
46
|
+
const WORD_PART = /[A-Za-z0-9_$\u0080-]/;
|
|
47
|
+
const RULES = {
|
|
48
|
+
postgresql: {
|
|
49
|
+
nestedBlockComments: true,
|
|
50
|
+
hashComment: false,
|
|
51
|
+
dashNeedsSpace: false,
|
|
52
|
+
backslashAlways: false,
|
|
53
|
+
dollarQuotes: true,
|
|
54
|
+
backtick: false,
|
|
55
|
+
bracket: false,
|
|
56
|
+
},
|
|
57
|
+
mysql: {
|
|
58
|
+
nestedBlockComments: false,
|
|
59
|
+
hashComment: true,
|
|
60
|
+
dashNeedsSpace: true,
|
|
61
|
+
backslashAlways: true,
|
|
62
|
+
dollarQuotes: false,
|
|
63
|
+
backtick: true,
|
|
64
|
+
bracket: false,
|
|
65
|
+
},
|
|
66
|
+
sqlite: {
|
|
67
|
+
nestedBlockComments: false,
|
|
68
|
+
hashComment: false,
|
|
69
|
+
dashNeedsSpace: false,
|
|
70
|
+
backslashAlways: false,
|
|
71
|
+
dollarQuotes: false,
|
|
72
|
+
backtick: true,
|
|
73
|
+
bracket: true,
|
|
74
|
+
},
|
|
75
|
+
mssql: {
|
|
76
|
+
nestedBlockComments: true,
|
|
77
|
+
hashComment: false,
|
|
78
|
+
dashNeedsSpace: false,
|
|
79
|
+
backslashAlways: false,
|
|
80
|
+
dollarQuotes: false,
|
|
81
|
+
backtick: false,
|
|
82
|
+
bracket: true,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
/** Offset just past the quoted run that opens at `i`, doubled closing quotes honoured. */
|
|
86
|
+
function skipQuoted(sql, i, close, backslash) {
|
|
87
|
+
const n = sql.length;
|
|
88
|
+
let j = i + 1;
|
|
89
|
+
while (j < n) {
|
|
90
|
+
const ch = sql[j];
|
|
91
|
+
if (backslash && ch === '\\') {
|
|
92
|
+
j += 2;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (ch === close) {
|
|
96
|
+
if (sql[j + 1] === close) {
|
|
97
|
+
j += 2;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
return j + 1;
|
|
101
|
+
}
|
|
102
|
+
j++;
|
|
103
|
+
}
|
|
104
|
+
return n;
|
|
105
|
+
}
|
|
106
|
+
/** Offset just past the block comment that opens at `i`. */
|
|
107
|
+
function skipBlockComment(sql, i, nests) {
|
|
108
|
+
const n = sql.length;
|
|
109
|
+
let depth = 1;
|
|
110
|
+
let j = i + 2;
|
|
111
|
+
while (j < n) {
|
|
112
|
+
if (nests && sql[j] === '/' && sql[j + 1] === '*') {
|
|
113
|
+
depth++;
|
|
114
|
+
j += 2;
|
|
115
|
+
}
|
|
116
|
+
else if (sql[j] === '*' && sql[j + 1] === '/') {
|
|
117
|
+
j += 2;
|
|
118
|
+
if (--depth === 0)
|
|
119
|
+
return j;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
j++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return n;
|
|
126
|
+
}
|
|
127
|
+
/** Offset just past the end of the line that `i` is on. */
|
|
128
|
+
function skipLine(sql, i) {
|
|
129
|
+
const end = sql.indexOf('\n', i);
|
|
130
|
+
return end === -1 ? sql.length : end + 1;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Every transaction-control statement in `sql`, in order. Empty when there is
|
|
134
|
+
* none, which is the answer for almost every statement and costs one pass.
|
|
135
|
+
*/
|
|
136
|
+
export function findTransactionControl(sql, syntax) {
|
|
137
|
+
const found = [];
|
|
138
|
+
for (const words of leadingWords(sql, syntax)) {
|
|
139
|
+
const statement = classify(words, syntax);
|
|
140
|
+
if (statement !== undefined)
|
|
141
|
+
found.push(statement);
|
|
142
|
+
}
|
|
143
|
+
return found;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The leading words of every statement in `sql`, lower-cased (`?` stands for a
|
|
147
|
+
* token that is not a word: a literal, a quoted identifier, a symbol). This is
|
|
148
|
+
* the whole of what the scan decides, exported so the test that holds it to
|
|
149
|
+
* `cli/sql-statements.ts` can compare statement boundaries directly.
|
|
150
|
+
*/
|
|
151
|
+
export function leadingWords(sql, syntax) {
|
|
152
|
+
const rules = RULES[syntax];
|
|
153
|
+
const statements = [];
|
|
154
|
+
const n = sql.length;
|
|
155
|
+
let words = [];
|
|
156
|
+
/** Where the previous word ended, and the word, so `E'...'` is read as one token. */
|
|
157
|
+
let lastWordEnd = -1;
|
|
158
|
+
let lastWord = '';
|
|
159
|
+
/** Inside a `BEGIN ATOMIC ... END` routine body, whose semicolons do not end the statement. */
|
|
160
|
+
let atomic = false;
|
|
161
|
+
let caseDepth = 0;
|
|
162
|
+
const finish = () => {
|
|
163
|
+
if (words.length > 0)
|
|
164
|
+
statements.push(words);
|
|
165
|
+
words = [];
|
|
166
|
+
};
|
|
167
|
+
const note = (word) => {
|
|
168
|
+
if (words.length < LEADING_WORDS)
|
|
169
|
+
words.push(word);
|
|
170
|
+
};
|
|
171
|
+
let i = 0;
|
|
172
|
+
while (i < n) {
|
|
173
|
+
const ch = sql[i];
|
|
174
|
+
if (ch === ';') {
|
|
175
|
+
if (!atomic)
|
|
176
|
+
finish();
|
|
177
|
+
i++;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r' || ch === '\f') {
|
|
181
|
+
i++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (ch === '-' && sql[i + 1] === '-') {
|
|
185
|
+
const after = sql[i + 2];
|
|
186
|
+
if (!rules.dashNeedsSpace || after === undefined || /\s/.test(after) || after.charCodeAt(0) < 32) {
|
|
187
|
+
i = skipLine(sql, i);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (ch === '#' && rules.hashComment) {
|
|
192
|
+
i = skipLine(sql, i);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (ch === '/' && sql[i + 1] === '*') {
|
|
196
|
+
i = skipBlockComment(sql, i, rules.nestedBlockComments);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (ch === "'") {
|
|
200
|
+
// PostgreSQL's `E'...'` honours backslash escapes; every other `'...'` there does not.
|
|
201
|
+
const escaped = rules.backslashAlways || (syntax === 'postgresql' && lastWordEnd === i && /^[eE]$/.test(lastWord));
|
|
202
|
+
i = skipQuoted(sql, i, "'", escaped);
|
|
203
|
+
note('?');
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (ch === '"') {
|
|
207
|
+
i = skipQuoted(sql, i, '"', rules.backslashAlways);
|
|
208
|
+
note('?');
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (ch === '`' && rules.backtick) {
|
|
212
|
+
i = skipQuoted(sql, i, '`', false);
|
|
213
|
+
note('?');
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (ch === '[' && rules.bracket) {
|
|
217
|
+
i = skipQuoted(sql, i, ']', false);
|
|
218
|
+
note('?');
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (ch === '$' && rules.dollarQuotes) {
|
|
222
|
+
const tag = /^\$([A-Za-z_\u0080-][A-Za-z0-9_\u0080-]*)?\$/.exec(sql.slice(i, i + 130));
|
|
223
|
+
if (tag) {
|
|
224
|
+
const close = sql.indexOf(tag[0], i + tag[0].length);
|
|
225
|
+
i = close === -1 ? n : close + tag[0].length;
|
|
226
|
+
note('?');
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (WORD_START.test(ch)) {
|
|
231
|
+
// A whole identifier, so a `$` inside it never opens a dollar quote.
|
|
232
|
+
let j = i + 1;
|
|
233
|
+
while (j < n && WORD_PART.test(sql[j]))
|
|
234
|
+
j++;
|
|
235
|
+
const word = sql.slice(i, j).toLowerCase();
|
|
236
|
+
if (syntax === 'postgresql') {
|
|
237
|
+
// PostgreSQL 14's SQL-standard function body: its inner semicolons are not statement ends.
|
|
238
|
+
// `CASE ... END` inside it is the only other END a body can hold.
|
|
239
|
+
if (!atomic && word === 'atomic' && lastWord === 'begin' && lastWordEnd <= i && sqlGap(sql, lastWordEnd, i)) {
|
|
240
|
+
atomic = true;
|
|
241
|
+
caseDepth = 0;
|
|
242
|
+
}
|
|
243
|
+
else if (atomic) {
|
|
244
|
+
if (word === 'case')
|
|
245
|
+
caseDepth++;
|
|
246
|
+
else if (word === 'end') {
|
|
247
|
+
if (caseDepth > 0)
|
|
248
|
+
caseDepth--;
|
|
249
|
+
else
|
|
250
|
+
atomic = false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
note(word);
|
|
255
|
+
lastWordEnd = j;
|
|
256
|
+
lastWord = word;
|
|
257
|
+
i = j;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
note('?');
|
|
261
|
+
lastWord = '';
|
|
262
|
+
i++;
|
|
263
|
+
}
|
|
264
|
+
finish();
|
|
265
|
+
return statements;
|
|
266
|
+
}
|
|
267
|
+
/** Whether only whitespace lies between `from` and `to` (comments count as something). */
|
|
268
|
+
function sqlGap(sql, from, to) {
|
|
269
|
+
return /^\s*$/.test(sql.slice(from, to));
|
|
270
|
+
}
|
|
271
|
+
/** The control statement whose leading `words` these are, if they are one. */
|
|
272
|
+
function classify(words, syntax) {
|
|
273
|
+
const [w0, w1, w2] = words;
|
|
274
|
+
switch (w0) {
|
|
275
|
+
case 'begin':
|
|
276
|
+
return w1 === undefined || BEGIN_NEXT.has(w1) ? { kind: 'begin', label: 'BEGIN' } : undefined;
|
|
277
|
+
case 'start':
|
|
278
|
+
return w1 === 'transaction' ? { kind: 'begin', label: 'START TRANSACTION' } : undefined;
|
|
279
|
+
case 'commit':
|
|
280
|
+
return { kind: 'commit', label: 'COMMIT' };
|
|
281
|
+
case 'end':
|
|
282
|
+
return (syntax === 'postgresql' || syntax === 'sqlite') &&
|
|
283
|
+
(w1 === undefined || w1 === 'work' || w1 === 'transaction' || w1 === 'and')
|
|
284
|
+
? { kind: 'commit', label: 'END' }
|
|
285
|
+
: undefined;
|
|
286
|
+
case 'rollback':
|
|
287
|
+
return w1 === 'to' || ((w1 === 'work' || w1 === 'transaction' || w1 === 'tran') && w2 === 'to')
|
|
288
|
+
? { kind: 'rollback-to', label: 'ROLLBACK TO SAVEPOINT' }
|
|
289
|
+
: { kind: 'rollback', label: 'ROLLBACK' };
|
|
290
|
+
case 'abort':
|
|
291
|
+
return syntax === 'postgresql' ? { kind: 'rollback', label: 'ABORT' } : undefined;
|
|
292
|
+
case 'savepoint':
|
|
293
|
+
return { kind: 'savepoint', label: 'SAVEPOINT' };
|
|
294
|
+
case 'save':
|
|
295
|
+
return syntax === 'mssql' && (w1 === 'tran' || w1 === 'transaction')
|
|
296
|
+
? { kind: 'savepoint', label: 'SAVE TRANSACTION' }
|
|
297
|
+
: undefined;
|
|
298
|
+
case 'release':
|
|
299
|
+
return { kind: 'release', label: 'RELEASE SAVEPOINT' };
|
|
300
|
+
case 'set':
|
|
301
|
+
return w1 === 'transaction' ? { kind: 'set-transaction', label: 'SET TRANSACTION' } : undefined;
|
|
302
|
+
case 'prepare':
|
|
303
|
+
return w1 === 'transaction' ? { kind: 'prepare', label: 'PREPARE TRANSACTION' } : undefined;
|
|
304
|
+
default:
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/** Whether `text` could hold a transaction-control statement at all (a cheap test before the scan). */
|
|
309
|
+
export function mayHoldTransactionControl(text) {
|
|
310
|
+
return /\b(?:begin|start|commit|end|rollback|abort|savepoint|save|release|set|prepare)\b/i.test(text);
|
|
311
|
+
}
|