@suss/sql 0.33.0 → 0.34.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 +23 -23
- package/dist/index.cjs +144 -14
- package/dist/index.d.cts +48 -32
- package/dist/index.d.ts +48 -32
- package/dist/index.js +144 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Reads what a SQL statement touches: which tables, which fields, and what it pick
|
|
|
6
6
|
|
|
7
7
|
## What this package is
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
This package is a reader and does not register as a pack. When a pattern pack finds a query written as SQL instead of through an ORM, it passes the text here and gets back the same shape its own recognizer would have produced.
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
12
|
import { readSqlAccess } from "@suss/sql";
|
|
@@ -15,44 +15,44 @@ readSqlAccess("SELECT id, email FROM users WHERE tenant_id = $1");
|
|
|
15
15
|
// [{ table: "users", qualifier: [], kind: "read", fields: ["id", "email"], selector: ["tenant_id"] }]
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
`qualifier`
|
|
18
|
+
`qualifier` lists the namespaces written in front of the table, outermost first. For Postgres and MySQL the parser returns the schema separately and it never reaches the table, so `qualifier` is always empty there. BigQuery addresses a table as `` `project.dataset.table` ``, and the parser returns the whole quoted string, so this package splits it. The table is the one a provider declares, and the dataset is the namespace the access is recorded under.
|
|
19
19
|
|
|
20
20
|
```ts
|
|
21
21
|
readSqlAccess("SELECT id FROM `analytics.core.dim_account`", { dialect: "bigquery" });
|
|
22
22
|
// [{ table: "dim_account", qualifier: ["analytics", "core"], ... }]
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Some packs read a table off a call's argument, as in `client.get_table("analytics.core.dim_account")`, and not out of a statement. They split it through `splitQualifiedTable`, so both spellings land on the same table and the same namespace. It returns nothing for a table that came through as a parameter.
|
|
26
26
|
|
|
27
27
|
```ts
|
|
28
28
|
splitQualifiedTable("analytics.core.dim_account");
|
|
29
29
|
// { table: "dim_account", qualifier: ["analytics", "core"] }
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
The statement is parsed
|
|
32
|
+
The statement is parsed with a grammar, so a join contributes every table it reads:
|
|
33
33
|
|
|
34
34
|
```ts
|
|
35
35
|
readSqlAccess("SELECT u.email, o.total FROM users u JOIN orders o ON o.user_id = u.id");
|
|
36
36
|
// [{ table: "users", fields: ["email"], ... }, { table: "orders", fields: ["total"], ... }]
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
A query written in place of a table is read for its own tables. The alias
|
|
39
|
+
A query written in place of a table is read for its own tables. The alias refers to that query's columns and not to a base table, so a column read through the alias is dropped instead of being attributed to a table:
|
|
40
40
|
|
|
41
41
|
```ts
|
|
42
42
|
readSqlAccess("SELECT dc.account_id FROM (SELECT account_id FROM dim_contact) dc JOIN dim_account a ON a.id = dc.account_id");
|
|
43
43
|
// [{ table: "dim_account", fields: [], ... }, { table: "dim_contact", fields: ["account_id"], ... }]
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
A write
|
|
46
|
+
A write can read too. An insert can take its rows from a query, and an update or a delete can reach another table to pick the rows it changes:
|
|
47
47
|
|
|
48
48
|
```ts
|
|
49
49
|
readSqlAccess("INSERT INTO runs (id) SELECT id FROM jobs");
|
|
50
50
|
// [{ table: "runs", kind: "write", fields: ["id"], ... }, { table: "jobs", kind: "read", fields: ["id"], ... }]
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
Every branch of a set operation is read, whether the statement writes it on its own, inside a `WITH` clause, or in place of a table. `SELECT id FROM orders UNION SELECT id FROM refunds`
|
|
53
|
+
Every branch of a set operation is read, whether the statement writes it on its own, inside a `WITH` clause, or in place of a table. `SELECT id FROM orders UNION SELECT id FROM refunds` returns a read on `orders` and one on `refunds`, each with the field `id`.
|
|
54
54
|
|
|
55
|
-
Wherever a field
|
|
55
|
+
Wherever a field appears, it goes on the table the statement qualified it with. A field in a `WHERE` goes in the selector and not in the fields. So the table an update reads from lists its join key, the same way a table joined in a select would:
|
|
56
56
|
|
|
57
57
|
```ts
|
|
58
58
|
readSqlAccess("UPDATE runs SET state = $1 FROM jobs WHERE runs.job_id = jobs.id");
|
|
@@ -60,24 +60,24 @@ readSqlAccess("UPDATE runs SET state = $1 FROM jobs WHERE runs.job_id = jobs.id"
|
|
|
60
60
|
// { table: "jobs", kind: "read", fields: [], selector: ["id"] }]
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
The columns a `RETURNING` returns, and the columns an `ON CONFLICT` matches on and then sets, all belong to the one table the statement writes. So they go on that write, and no second access is created. A consumer pairs one access per table against the schema, and a field the statement both writes and returns is listed once. `INSERT INTO users (email) VALUES ($1) ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name RETURNING id` writes `users` with the fields `email`, `id` and `name`.
|
|
64
64
|
|
|
65
|
-
Not every grammar
|
|
65
|
+
Not every grammar accepts every spelling of these. Only Postgres accepts `UPDATE ... FROM`. BigQuery parses the statement but drops the clause, so there it returns the write by itself. No grammar here accepts `DELETE ... USING`. `INTERSECT` and `EXCEPT` work in Postgres and MySQL, and `RETURNING` in Postgres and SQLite. `ON CONFLICT` is Postgres only, and the other three grammars accept `ON DUPLICATE KEY UPDATE` instead.
|
|
66
66
|
|
|
67
|
-
The grammar
|
|
67
|
+
The grammar rejects two spellings that have nothing to do with which table is touched, so a statement it rejects is tried again with them removed. One is a parameter written with the type it is read as, such as `$1::text` or `$1::int[]`. The other is a select that locks the rows it picks, such as `FOR UPDATE SKIP LOCKED`. Neither is rewritten inside a literal, a quoted name or a comment.
|
|
68
68
|
|
|
69
|
-
The grammar also
|
|
69
|
+
The grammar also accepts a `WITH` clause only in front of a select. In front of an insert, an update or a delete, the clause is split off and each query is read on its own. That is as much as can be read of a statement the grammar will not accept whole:
|
|
70
70
|
|
|
71
71
|
```ts
|
|
72
72
|
readSqlAccess("WITH due AS (SELECT id FROM jobs FOR UPDATE SKIP LOCKED) INSERT INTO job_runs (job_id) SELECT id FROM due");
|
|
73
73
|
// [{ table: "job_runs", kind: "write", fields: ["job_id"], ... }, { table: "jobs", kind: "read", fields: ["id"], ... }]
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
`sqlFromParts` makes a query written as a tagged template readable, by writing each interpolation as a parameter. A query interpolates a value nearly every time, and a parameter is how the statement would supply a value anyway.
|
|
77
77
|
|
|
78
|
-
Two
|
|
78
|
+
Two options override that. A caller who knows a hole is a table passes it in `substitutions`. That is how a Drizzle query that interpolates a schema object reaches the statement as a table name. A caller who ran the source's own evaluator over each hole passes the results in `settled`, and they are used where the statement writes a name.
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
The statement writes a name in two places. One is inside a quoted name, which is how BigQuery addresses a table:
|
|
81
81
|
|
|
82
82
|
```ts
|
|
83
83
|
// `SELECT id FROM \`${TABLE}\`` with TABLE = "analytics.core.dim_account"
|
|
@@ -85,7 +85,7 @@ sqlFromParts(["SELECT id FROM `", "`"], [], ["analytics.core.dim_account"]);
|
|
|
85
85
|
// SELECT id FROM `analytics.core.dim_account`
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
The other is
|
|
88
|
+
The other is directly after `FROM`, `JOIN`, `INTO`, `UPDATE` or `TABLE`, in any case. Postgres code nearly always leaves the table unquoted, so checking for the quote alone would miss the most common way a project interpolates a table:
|
|
89
89
|
|
|
90
90
|
```ts
|
|
91
91
|
// `SELECT id FROM ${TABLE} WHERE id = $1` with TABLE = "users"
|
|
@@ -93,9 +93,9 @@ sqlFromParts(["SELECT id FROM ", " WHERE id = $1"], [], ["users"]);
|
|
|
93
93
|
// SELECT id FROM users WHERE id = $1
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
A hole anywhere else is left alone even when the evaluator settled it. `WHERE tier = ${TIER}` with `TIER = "gold"` would parse as a column called `gold` and put it in the selector, which is worse than the parameter the reader would otherwise see. Where the statement writes a name there is no such ambiguity
|
|
96
|
+
A hole anywhere else is left alone, even when the evaluator settled it. `WHERE tier = ${TIER}` with `TIER = "gold"` would parse as a column called `gold` and put it in the selector, which is worse than the parameter the reader would otherwise see. Where the statement writes a name, there is no such ambiguity, because whatever the hole came to is part of the name.
|
|
97
97
|
|
|
98
|
-
A part of a name nothing settled stays a parameter
|
|
98
|
+
A part of a name that nothing settled stays a parameter. The qualifier is then read from the table outward, and stops at that parameter. The part next to the table is the one a scope comes from, so reading a project as if it were a dataset would place the access somewhere it never went:
|
|
99
99
|
|
|
100
100
|
```
|
|
101
101
|
analytics.core.dim_account → dim_account, ["analytics", "core"]
|
|
@@ -104,9 +104,9 @@ analytics.$1.dim_account → dim_account, []
|
|
|
104
104
|
$1 → nothing
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
A whole table nothing settled produces nothing at all.
|
|
107
|
+
A whole table that nothing settled produces nothing at all.
|
|
108
108
|
|
|
109
|
-
It reads Postgres, MySQL, SQLite, and BigQuery. Pass the dialect the way a pack
|
|
109
|
+
It reads Postgres, MySQL, SQLite, and BigQuery. Pass the dialect the same way a pack declares its store:
|
|
110
110
|
|
|
111
111
|
```ts
|
|
112
112
|
readSqlAccess("SELECT `id` FROM `users`", { dialect: "mysql" });
|
|
@@ -114,12 +114,12 @@ readSqlAccess("SELECT `id` FROM `users`", { dialect: "mysql" });
|
|
|
114
114
|
|
|
115
115
|
## What it will not tell you
|
|
116
116
|
|
|
117
|
-
- **A dialect it does not read
|
|
118
|
-
- **An unqualified field in a join is left out.** `SELECT id FROM users u JOIN orders o ON ...`
|
|
117
|
+
- **A dialect it does not read returns nothing.** So does a statement it cannot parse. In both cases it does not guess from whatever words the text happens to contain.
|
|
118
|
+
- **An unqualified field in a join is left out.** `SELECT id FROM users u JOIN orders o ON ...` does not show which table `id` is on, so the field is dropped and not attributed to both. A query written in place of a table counts as one of the sides, since it could have supplied the field too.
|
|
119
119
|
- **An interpolated table or clause makes a statement unreadable.** ``sql`SELECT * FROM ${table}` `` cannot be settled without running it, so it produces nothing.
|
|
120
120
|
|
|
121
121
|
## Where it fits in suss
|
|
122
122
|
|
|
123
123
|
`@suss/framework-drizzle` uses it for ``db.execute(sql`...`)``. Nothing else depends on it, and it depends on nothing inside suss, so it stays a plain function over a string.
|
|
124
124
|
|
|
125
|
-
The grammars come from `node-sql-parser
|
|
125
|
+
The grammars come from `node-sql-parser`. They are bundled into this package's build and not installed, which is why the package has no runtime dependencies. See THIRD-PARTY-NOTICES.md.
|
package/dist/index.cjs
CHANGED
|
@@ -22008,8 +22008,16 @@ var import_postgresql = __toESM(require_postgresql(), 1);
|
|
|
22008
22008
|
var import_sqlite = __toESM(require_sqlite(), 1);
|
|
22009
22009
|
var DIALECTS = {
|
|
22010
22010
|
postgresql: { module: import_postgresql.default, database: "postgresql" },
|
|
22011
|
-
mysql: {
|
|
22012
|
-
|
|
22011
|
+
mysql: {
|
|
22012
|
+
module: import_mysql.default,
|
|
22013
|
+
database: "mysql",
|
|
22014
|
+
bindsByPosition: true
|
|
22015
|
+
},
|
|
22016
|
+
sqlite: {
|
|
22017
|
+
module: import_sqlite.default,
|
|
22018
|
+
database: "sqlite",
|
|
22019
|
+
bindsByPosition: true
|
|
22020
|
+
},
|
|
22013
22021
|
bigquery: { module: import_bigquery.default, database: "bigquery" }
|
|
22014
22022
|
};
|
|
22015
22023
|
function readSqlAccess(sql, options = {}) {
|
|
@@ -22017,7 +22025,27 @@ function readSqlAccess(sql, options = {}) {
|
|
|
22017
22025
|
if (grammar === void 0) {
|
|
22018
22026
|
return [];
|
|
22019
22027
|
}
|
|
22020
|
-
|
|
22028
|
+
const numbered = grammar.bindsByPosition === true ? numberedBinds(sql) : sql;
|
|
22029
|
+
return accessesInSql(numbered, grammar).map(qualified).filter((access) => access !== null).map(
|
|
22030
|
+
(access) => options.placeholders === true ? access : withoutPlaceholders(access)
|
|
22031
|
+
);
|
|
22032
|
+
}
|
|
22033
|
+
function numberedBinds(sql) {
|
|
22034
|
+
let count = 0;
|
|
22035
|
+
return sql.replace(
|
|
22036
|
+
/'(?:[^']|'')*'|"(?:[^"]|"")*"|`[^`]*`|-{2}[^\n]*|\/\*[\s\S]*?\*\/|\?/g,
|
|
22037
|
+
(token) => {
|
|
22038
|
+
if (token !== "?") {
|
|
22039
|
+
return token;
|
|
22040
|
+
}
|
|
22041
|
+
count += 1;
|
|
22042
|
+
return `$${count}`;
|
|
22043
|
+
}
|
|
22044
|
+
);
|
|
22045
|
+
}
|
|
22046
|
+
function withoutPlaceholders(access) {
|
|
22047
|
+
const { placeholders: _left, ...rest } = access;
|
|
22048
|
+
return rest;
|
|
22021
22049
|
}
|
|
22022
22050
|
function accessesInSql(sql, grammar) {
|
|
22023
22051
|
const written = parsedAccesses(sql, grammar);
|
|
@@ -22317,7 +22345,8 @@ function insertAccesses(node, defined) {
|
|
|
22317
22345
|
...refsIn(node.returning),
|
|
22318
22346
|
...conflict.fields
|
|
22319
22347
|
],
|
|
22320
|
-
selector: conflict.selector
|
|
22348
|
+
selector: conflict.selector,
|
|
22349
|
+
bound: boundInRows(namesOf(node.columns), node.values)
|
|
22321
22350
|
};
|
|
22322
22351
|
const sources = sourcesIn(node.table, defined);
|
|
22323
22352
|
return [
|
|
@@ -22329,7 +22358,8 @@ function updateAccesses(node, defined) {
|
|
|
22329
22358
|
const sources = sourcesAcross([node.table, node.from], defined);
|
|
22330
22359
|
const stated = {
|
|
22331
22360
|
fields: [...setTargets(node.set), ...refsIn(node.returning)],
|
|
22332
|
-
selector: refsIn(node.where)
|
|
22361
|
+
selector: refsIn(node.where),
|
|
22362
|
+
bound: [...boundInSet(node.set), ...boundInWhere(node.where)]
|
|
22333
22363
|
};
|
|
22334
22364
|
return accessesAcross(sources, firstTable(node.table), stated, defined);
|
|
22335
22365
|
}
|
|
@@ -22337,7 +22367,8 @@ function deleteAccesses(node, defined) {
|
|
|
22337
22367
|
const written = deletedTable(node);
|
|
22338
22368
|
const stated = {
|
|
22339
22369
|
fields: refsIn(node.returning),
|
|
22340
|
-
selector: refsIn(node.where)
|
|
22370
|
+
selector: refsIn(node.where),
|
|
22371
|
+
bound: boundInWhere(node.where)
|
|
22341
22372
|
};
|
|
22342
22373
|
const sources = withTable(sourcesIn(node.from, defined), written);
|
|
22343
22374
|
return accessesAcross(sources, written, stated, defined);
|
|
@@ -22382,7 +22413,8 @@ function deletedTable(node) {
|
|
|
22382
22413
|
function selectAccesses(statement, names) {
|
|
22383
22414
|
const stated = {
|
|
22384
22415
|
fields: refsIn(statement.columns),
|
|
22385
|
-
selector: refsIn(statement.where)
|
|
22416
|
+
selector: refsIn(statement.where),
|
|
22417
|
+
bound: boundInWhere(statement.where)
|
|
22386
22418
|
};
|
|
22387
22419
|
return accessesAcross(sourcesIn(statement.from, names), null, stated, names);
|
|
22388
22420
|
}
|
|
@@ -22391,15 +22423,40 @@ function accessesAcross(sources, written, stated, defined) {
|
|
|
22391
22423
|
const only = named.length === 1 && !sources.derived ? named[0] ?? null : null;
|
|
22392
22424
|
const fields = byTable(stated.fields, sources.tables, written ?? only);
|
|
22393
22425
|
const selector = byTable(stated.selector, sources.tables, only);
|
|
22394
|
-
const
|
|
22395
|
-
|
|
22396
|
-
|
|
22397
|
-
|
|
22398
|
-
|
|
22399
|
-
|
|
22400
|
-
|
|
22426
|
+
const bound = boundByTable(stated.bound, sources.tables, {
|
|
22427
|
+
fields: written ?? only,
|
|
22428
|
+
selector: only
|
|
22429
|
+
});
|
|
22430
|
+
const own = named.filter((table) => !defined.has(table)).map((table) => {
|
|
22431
|
+
const placeholders = bound.get(table) ?? [];
|
|
22432
|
+
return {
|
|
22433
|
+
table,
|
|
22434
|
+
qualifier: [],
|
|
22435
|
+
kind: table === written ? "write" : "read",
|
|
22436
|
+
fields: fields.get(table) ?? [],
|
|
22437
|
+
selector: selector.get(table) ?? [],
|
|
22438
|
+
...placeholders.length > 0 ? { placeholders } : {}
|
|
22439
|
+
};
|
|
22440
|
+
});
|
|
22401
22441
|
return [...own, ...sources.inside];
|
|
22402
22442
|
}
|
|
22443
|
+
function boundByTable(refs, tables, only) {
|
|
22444
|
+
const found = /* @__PURE__ */ new Map();
|
|
22445
|
+
for (const ref of refs) {
|
|
22446
|
+
const table = ref.table === void 0 ? only[ref.clause] : tables.get(ref.table) ?? null;
|
|
22447
|
+
if (table === null) {
|
|
22448
|
+
continue;
|
|
22449
|
+
}
|
|
22450
|
+
const already = found.get(table) ?? [];
|
|
22451
|
+
already.push({
|
|
22452
|
+
field: ref.field,
|
|
22453
|
+
clause: ref.clause,
|
|
22454
|
+
placeholder: ref.placeholder
|
|
22455
|
+
});
|
|
22456
|
+
found.set(table, already);
|
|
22457
|
+
}
|
|
22458
|
+
return found;
|
|
22459
|
+
}
|
|
22403
22460
|
function byTable(refs, tables, only) {
|
|
22404
22461
|
const found = /* @__PURE__ */ new Map();
|
|
22405
22462
|
for (const ref of refs) {
|
|
@@ -22496,6 +22553,79 @@ function refsIn(value) {
|
|
|
22496
22553
|
walk(value);
|
|
22497
22554
|
return found;
|
|
22498
22555
|
}
|
|
22556
|
+
function placeholderOf(value) {
|
|
22557
|
+
const node = asNode(value);
|
|
22558
|
+
if (node?.type === "var" && node.prefix === "$") {
|
|
22559
|
+
return typeof node.name === "number" ? node.name : null;
|
|
22560
|
+
}
|
|
22561
|
+
return node?.type === "param" ? stringOf(node.value) : null;
|
|
22562
|
+
}
|
|
22563
|
+
function boundInWhere(value) {
|
|
22564
|
+
const found = [];
|
|
22565
|
+
const walk = (node) => {
|
|
22566
|
+
if (Array.isArray(node)) {
|
|
22567
|
+
node.forEach(walk);
|
|
22568
|
+
return;
|
|
22569
|
+
}
|
|
22570
|
+
const record = asNode(node);
|
|
22571
|
+
if (record === null) {
|
|
22572
|
+
return;
|
|
22573
|
+
}
|
|
22574
|
+
if (record.type === "binary_expr" && record.operator === "=") {
|
|
22575
|
+
const pair = columnAndPlaceholder(record.left, record.right) ?? columnAndPlaceholder(record.right, record.left);
|
|
22576
|
+
if (pair !== null) {
|
|
22577
|
+
found.push({ ...pair, clause: "selector" });
|
|
22578
|
+
return;
|
|
22579
|
+
}
|
|
22580
|
+
}
|
|
22581
|
+
for (const entry of Object.values(record)) {
|
|
22582
|
+
walk(entry);
|
|
22583
|
+
}
|
|
22584
|
+
};
|
|
22585
|
+
walk(value);
|
|
22586
|
+
return found;
|
|
22587
|
+
}
|
|
22588
|
+
function columnAndPlaceholder(column, placeholder) {
|
|
22589
|
+
const ref = asNode(column);
|
|
22590
|
+
const field = ref?.type === "column_ref" ? columnName(ref.column) : null;
|
|
22591
|
+
const given = placeholderOf(placeholder);
|
|
22592
|
+
if (ref === null || field === null || given === null) {
|
|
22593
|
+
return null;
|
|
22594
|
+
}
|
|
22595
|
+
return { table: stringOf(ref.table) ?? void 0, field, placeholder: given };
|
|
22596
|
+
}
|
|
22597
|
+
function boundInSet(value) {
|
|
22598
|
+
const found = [];
|
|
22599
|
+
for (const entry of Array.isArray(value) ? value : []) {
|
|
22600
|
+
const node = asNode(entry);
|
|
22601
|
+
const field = node === null ? null : columnName(node.column);
|
|
22602
|
+
const given = placeholderOf(node?.value);
|
|
22603
|
+
if (node !== null && field !== null && given !== null) {
|
|
22604
|
+
const table = stringOf(node.table) ?? void 0;
|
|
22605
|
+
found.push({ table, field, clause: "fields", placeholder: given });
|
|
22606
|
+
}
|
|
22607
|
+
}
|
|
22608
|
+
return found;
|
|
22609
|
+
}
|
|
22610
|
+
function boundInRows(columns, values) {
|
|
22611
|
+
const rows = asNode(values)?.values;
|
|
22612
|
+
const found = [];
|
|
22613
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
22614
|
+
const items = asNode(row)?.value;
|
|
22615
|
+
columns.forEach((field, at) => {
|
|
22616
|
+
const given = placeholderOf(Array.isArray(items) ? items[at] : null);
|
|
22617
|
+
if (given !== null) {
|
|
22618
|
+
found.push({
|
|
22619
|
+
table: void 0,
|
|
22620
|
+
field,
|
|
22621
|
+
clause: "fields",
|
|
22622
|
+
placeholder: given
|
|
22623
|
+
});
|
|
22624
|
+
}
|
|
22625
|
+
});
|
|
22626
|
+
}
|
|
22627
|
+
return found;
|
|
22628
|
+
}
|
|
22499
22629
|
function unqualified(names) {
|
|
22500
22630
|
return names.map((field) => ({ table: void 0, field }));
|
|
22501
22631
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,40 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* picks rows by.
|
|
2
|
+
* Reads what a SQL statement touches: which tables, which fields, and
|
|
3
|
+
* what it picks rows by.
|
|
4
4
|
*
|
|
5
|
-
* A pack that
|
|
6
|
-
*
|
|
7
|
-
* have produced. The statement is parsed
|
|
8
|
-
* so a join contributes every table it reads and a `WHERE`
|
|
9
|
-
* a selector.
|
|
5
|
+
* A pack that finds a query written as SQL instead of through an ORM
|
|
6
|
+
* passes the text here and gets back the same `SqlAccess` records its
|
|
7
|
+
* own recognizer would have produced. The statement is parsed with a
|
|
8
|
+
* grammar, so a join contributes every table it reads and a `WHERE`
|
|
9
|
+
* contributes a selector.
|
|
10
10
|
*
|
|
11
|
-
* A statement
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* grammar turns down is rewritten and tried again rather than given up.
|
|
11
|
+
* A statement the grammar rejects is rewritten and tried again. If it
|
|
12
|
+
* still fails the result is empty, and nothing is guessed from the
|
|
13
|
+
* words in the text. The README lists the dialects and what is left out.
|
|
15
14
|
*/
|
|
16
15
|
/** One table a statement touches, and what it does to it. */
|
|
17
16
|
interface SqlAccess {
|
|
18
17
|
table: string;
|
|
19
18
|
/**
|
|
20
|
-
* The namespaces
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
19
|
+
* The namespaces written in front of the table, outermost first.
|
|
20
|
+
* BigQuery writes `project.dataset.table`, and keeping the whole
|
|
21
|
+
* string as the table would record a name no provider declares. A
|
|
22
|
+
* part the caller could not resolve, and every part outside it, is
|
|
23
|
+
* left out.
|
|
25
24
|
*/
|
|
26
25
|
qualifier: string[];
|
|
27
26
|
kind: "read" | "write";
|
|
28
|
-
/** The fields the statement
|
|
27
|
+
/** The fields the statement lists, or `["*"]` for a whole row. */
|
|
29
28
|
fields: string[];
|
|
30
29
|
/** The fields it picks rows by. */
|
|
31
30
|
selector: string[];
|
|
31
|
+
/**
|
|
32
|
+
* The placeholder that gives a column its value, where the statement
|
|
33
|
+
* writes one: `tenant_id = $1` in a `WHERE`, a column of an `INSERT`
|
|
34
|
+
* and its place under `VALUES`, an assignment in a `SET`. Present only
|
|
35
|
+
* when the caller asked for it and a column is given one.
|
|
36
|
+
*/
|
|
37
|
+
placeholders?: SqlPlaceholder[];
|
|
38
|
+
}
|
|
39
|
+
/** One column the statement gives a placeholder's value. */
|
|
40
|
+
interface SqlPlaceholder {
|
|
41
|
+
field: string;
|
|
42
|
+
/** Which of the access's lists the column is in. */
|
|
43
|
+
clause: "fields" | "selector";
|
|
44
|
+
/** `$1` is 1, counted the way the driver counts; `:tenant` is `tenant`. */
|
|
45
|
+
placeholder: number | string;
|
|
32
46
|
}
|
|
33
47
|
interface SqlReadOptions {
|
|
34
48
|
/** Which dialect the statement is written in. */
|
|
35
49
|
dialect?: string;
|
|
50
|
+
/** Whether to say which placeholder gives each column its value. */
|
|
51
|
+
placeholders?: boolean;
|
|
36
52
|
}
|
|
37
|
-
/** Every table a statement touches
|
|
53
|
+
/** Every table a statement touches. Empty when the dialect is unknown or the statement cannot be parsed. */
|
|
38
54
|
declare function readSqlAccess(sql: string, options?: SqlReadOptions): SqlAccess[];
|
|
39
55
|
/** A table name, split from the namespaces written in front of it. */
|
|
40
56
|
interface QualifiedTable {
|
|
@@ -43,24 +59,24 @@ interface QualifiedTable {
|
|
|
43
59
|
qualifier: string[];
|
|
44
60
|
}
|
|
45
61
|
/**
|
|
46
|
-
*
|
|
47
|
-
* when the table itself came through as a parameter. A
|
|
48
|
-
* a table off
|
|
49
|
-
*
|
|
62
|
+
* Splits a table name from the namespaces written in front of it.
|
|
63
|
+
* Returns null when the table itself came through as a parameter. A
|
|
64
|
+
* pack that reads a table off a call's argument splits it here too, so
|
|
65
|
+
* the result matches the same table read out of a statement.
|
|
50
66
|
*/
|
|
51
67
|
declare function splitQualifiedTable(name: string): QualifiedTable | null;
|
|
52
68
|
/**
|
|
53
|
-
*
|
|
54
|
-
* a parameter.
|
|
55
|
-
* and a parameter is how the statement would
|
|
56
|
-
* text parses
|
|
69
|
+
* Joins a tagged template's parts into SQL, writing each interpolation
|
|
70
|
+
* as a parameter (`$1`, `$2`). A query interpolates a value nearly every
|
|
71
|
+
* time, and a parameter is how the statement would pass a value anyway,
|
|
72
|
+
* so the text parses with the meaning it has at run time.
|
|
57
73
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
74
|
+
* `substitutions` replaces a hole wherever it is, for a caller that
|
|
75
|
+
* knows the hole is a table, such as an interpolated schema object.
|
|
76
|
+
* `settled` replaces a hole only where the statement writes a name.
|
|
77
|
+
* Any other hole stays a parameter, and a statement with a parameter
|
|
78
|
+
* where the grammar does not allow one parses to no accesses.
|
|
63
79
|
*/
|
|
64
80
|
declare function sqlFromParts(parts: readonly string[], substitutions?: ReadonlyArray<string | null>, settled?: ReadonlyArray<string | null>): string;
|
|
65
81
|
|
|
66
|
-
export { type QualifiedTable, type SqlAccess, type SqlReadOptions, readSqlAccess, splitQualifiedTable, sqlFromParts };
|
|
82
|
+
export { type QualifiedTable, type SqlAccess, type SqlPlaceholder, type SqlReadOptions, readSqlAccess, splitQualifiedTable, sqlFromParts };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,40 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* picks rows by.
|
|
2
|
+
* Reads what a SQL statement touches: which tables, which fields, and
|
|
3
|
+
* what it picks rows by.
|
|
4
4
|
*
|
|
5
|
-
* A pack that
|
|
6
|
-
*
|
|
7
|
-
* have produced. The statement is parsed
|
|
8
|
-
* so a join contributes every table it reads and a `WHERE`
|
|
9
|
-
* a selector.
|
|
5
|
+
* A pack that finds a query written as SQL instead of through an ORM
|
|
6
|
+
* passes the text here and gets back the same `SqlAccess` records its
|
|
7
|
+
* own recognizer would have produced. The statement is parsed with a
|
|
8
|
+
* grammar, so a join contributes every table it reads and a `WHERE`
|
|
9
|
+
* contributes a selector.
|
|
10
10
|
*
|
|
11
|
-
* A statement
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* grammar turns down is rewritten and tried again rather than given up.
|
|
11
|
+
* A statement the grammar rejects is rewritten and tried again. If it
|
|
12
|
+
* still fails the result is empty, and nothing is guessed from the
|
|
13
|
+
* words in the text. The README lists the dialects and what is left out.
|
|
15
14
|
*/
|
|
16
15
|
/** One table a statement touches, and what it does to it. */
|
|
17
16
|
interface SqlAccess {
|
|
18
17
|
table: string;
|
|
19
18
|
/**
|
|
20
|
-
* The namespaces
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
19
|
+
* The namespaces written in front of the table, outermost first.
|
|
20
|
+
* BigQuery writes `project.dataset.table`, and keeping the whole
|
|
21
|
+
* string as the table would record a name no provider declares. A
|
|
22
|
+
* part the caller could not resolve, and every part outside it, is
|
|
23
|
+
* left out.
|
|
25
24
|
*/
|
|
26
25
|
qualifier: string[];
|
|
27
26
|
kind: "read" | "write";
|
|
28
|
-
/** The fields the statement
|
|
27
|
+
/** The fields the statement lists, or `["*"]` for a whole row. */
|
|
29
28
|
fields: string[];
|
|
30
29
|
/** The fields it picks rows by. */
|
|
31
30
|
selector: string[];
|
|
31
|
+
/**
|
|
32
|
+
* The placeholder that gives a column its value, where the statement
|
|
33
|
+
* writes one: `tenant_id = $1` in a `WHERE`, a column of an `INSERT`
|
|
34
|
+
* and its place under `VALUES`, an assignment in a `SET`. Present only
|
|
35
|
+
* when the caller asked for it and a column is given one.
|
|
36
|
+
*/
|
|
37
|
+
placeholders?: SqlPlaceholder[];
|
|
38
|
+
}
|
|
39
|
+
/** One column the statement gives a placeholder's value. */
|
|
40
|
+
interface SqlPlaceholder {
|
|
41
|
+
field: string;
|
|
42
|
+
/** Which of the access's lists the column is in. */
|
|
43
|
+
clause: "fields" | "selector";
|
|
44
|
+
/** `$1` is 1, counted the way the driver counts; `:tenant` is `tenant`. */
|
|
45
|
+
placeholder: number | string;
|
|
32
46
|
}
|
|
33
47
|
interface SqlReadOptions {
|
|
34
48
|
/** Which dialect the statement is written in. */
|
|
35
49
|
dialect?: string;
|
|
50
|
+
/** Whether to say which placeholder gives each column its value. */
|
|
51
|
+
placeholders?: boolean;
|
|
36
52
|
}
|
|
37
|
-
/** Every table a statement touches
|
|
53
|
+
/** Every table a statement touches. Empty when the dialect is unknown or the statement cannot be parsed. */
|
|
38
54
|
declare function readSqlAccess(sql: string, options?: SqlReadOptions): SqlAccess[];
|
|
39
55
|
/** A table name, split from the namespaces written in front of it. */
|
|
40
56
|
interface QualifiedTable {
|
|
@@ -43,24 +59,24 @@ interface QualifiedTable {
|
|
|
43
59
|
qualifier: string[];
|
|
44
60
|
}
|
|
45
61
|
/**
|
|
46
|
-
*
|
|
47
|
-
* when the table itself came through as a parameter. A
|
|
48
|
-
* a table off
|
|
49
|
-
*
|
|
62
|
+
* Splits a table name from the namespaces written in front of it.
|
|
63
|
+
* Returns null when the table itself came through as a parameter. A
|
|
64
|
+
* pack that reads a table off a call's argument splits it here too, so
|
|
65
|
+
* the result matches the same table read out of a statement.
|
|
50
66
|
*/
|
|
51
67
|
declare function splitQualifiedTable(name: string): QualifiedTable | null;
|
|
52
68
|
/**
|
|
53
|
-
*
|
|
54
|
-
* a parameter.
|
|
55
|
-
* and a parameter is how the statement would
|
|
56
|
-
* text parses
|
|
69
|
+
* Joins a tagged template's parts into SQL, writing each interpolation
|
|
70
|
+
* as a parameter (`$1`, `$2`). A query interpolates a value nearly every
|
|
71
|
+
* time, and a parameter is how the statement would pass a value anyway,
|
|
72
|
+
* so the text parses with the meaning it has at run time.
|
|
57
73
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
74
|
+
* `substitutions` replaces a hole wherever it is, for a caller that
|
|
75
|
+
* knows the hole is a table, such as an interpolated schema object.
|
|
76
|
+
* `settled` replaces a hole only where the statement writes a name.
|
|
77
|
+
* Any other hole stays a parameter, and a statement with a parameter
|
|
78
|
+
* where the grammar does not allow one parses to no accesses.
|
|
63
79
|
*/
|
|
64
80
|
declare function sqlFromParts(parts: readonly string[], substitutions?: ReadonlyArray<string | null>, settled?: ReadonlyArray<string | null>): string;
|
|
65
81
|
|
|
66
|
-
export { type QualifiedTable, type SqlAccess, type SqlReadOptions, readSqlAccess, splitQualifiedTable, sqlFromParts };
|
|
82
|
+
export { type QualifiedTable, type SqlAccess, type SqlPlaceholder, type SqlReadOptions, readSqlAccess, splitQualifiedTable, sqlFromParts };
|
package/dist/index.js
CHANGED
|
@@ -21995,8 +21995,16 @@ var import_postgresql = __toESM(require_postgresql(), 1);
|
|
|
21995
21995
|
var import_sqlite = __toESM(require_sqlite(), 1);
|
|
21996
21996
|
var DIALECTS = {
|
|
21997
21997
|
postgresql: { module: import_postgresql.default, database: "postgresql" },
|
|
21998
|
-
mysql: {
|
|
21999
|
-
|
|
21998
|
+
mysql: {
|
|
21999
|
+
module: import_mysql.default,
|
|
22000
|
+
database: "mysql",
|
|
22001
|
+
bindsByPosition: true
|
|
22002
|
+
},
|
|
22003
|
+
sqlite: {
|
|
22004
|
+
module: import_sqlite.default,
|
|
22005
|
+
database: "sqlite",
|
|
22006
|
+
bindsByPosition: true
|
|
22007
|
+
},
|
|
22000
22008
|
bigquery: { module: import_bigquery.default, database: "bigquery" }
|
|
22001
22009
|
};
|
|
22002
22010
|
function readSqlAccess(sql, options = {}) {
|
|
@@ -22004,7 +22012,27 @@ function readSqlAccess(sql, options = {}) {
|
|
|
22004
22012
|
if (grammar === void 0) {
|
|
22005
22013
|
return [];
|
|
22006
22014
|
}
|
|
22007
|
-
|
|
22015
|
+
const numbered = grammar.bindsByPosition === true ? numberedBinds(sql) : sql;
|
|
22016
|
+
return accessesInSql(numbered, grammar).map(qualified).filter((access) => access !== null).map(
|
|
22017
|
+
(access) => options.placeholders === true ? access : withoutPlaceholders(access)
|
|
22018
|
+
);
|
|
22019
|
+
}
|
|
22020
|
+
function numberedBinds(sql) {
|
|
22021
|
+
let count = 0;
|
|
22022
|
+
return sql.replace(
|
|
22023
|
+
/'(?:[^']|'')*'|"(?:[^"]|"")*"|`[^`]*`|-{2}[^\n]*|\/\*[\s\S]*?\*\/|\?/g,
|
|
22024
|
+
(token) => {
|
|
22025
|
+
if (token !== "?") {
|
|
22026
|
+
return token;
|
|
22027
|
+
}
|
|
22028
|
+
count += 1;
|
|
22029
|
+
return `$${count}`;
|
|
22030
|
+
}
|
|
22031
|
+
);
|
|
22032
|
+
}
|
|
22033
|
+
function withoutPlaceholders(access) {
|
|
22034
|
+
const { placeholders: _left, ...rest } = access;
|
|
22035
|
+
return rest;
|
|
22008
22036
|
}
|
|
22009
22037
|
function accessesInSql(sql, grammar) {
|
|
22010
22038
|
const written = parsedAccesses(sql, grammar);
|
|
@@ -22304,7 +22332,8 @@ function insertAccesses(node, defined) {
|
|
|
22304
22332
|
...refsIn(node.returning),
|
|
22305
22333
|
...conflict.fields
|
|
22306
22334
|
],
|
|
22307
|
-
selector: conflict.selector
|
|
22335
|
+
selector: conflict.selector,
|
|
22336
|
+
bound: boundInRows(namesOf(node.columns), node.values)
|
|
22308
22337
|
};
|
|
22309
22338
|
const sources = sourcesIn(node.table, defined);
|
|
22310
22339
|
return [
|
|
@@ -22316,7 +22345,8 @@ function updateAccesses(node, defined) {
|
|
|
22316
22345
|
const sources = sourcesAcross([node.table, node.from], defined);
|
|
22317
22346
|
const stated = {
|
|
22318
22347
|
fields: [...setTargets(node.set), ...refsIn(node.returning)],
|
|
22319
|
-
selector: refsIn(node.where)
|
|
22348
|
+
selector: refsIn(node.where),
|
|
22349
|
+
bound: [...boundInSet(node.set), ...boundInWhere(node.where)]
|
|
22320
22350
|
};
|
|
22321
22351
|
return accessesAcross(sources, firstTable(node.table), stated, defined);
|
|
22322
22352
|
}
|
|
@@ -22324,7 +22354,8 @@ function deleteAccesses(node, defined) {
|
|
|
22324
22354
|
const written = deletedTable(node);
|
|
22325
22355
|
const stated = {
|
|
22326
22356
|
fields: refsIn(node.returning),
|
|
22327
|
-
selector: refsIn(node.where)
|
|
22357
|
+
selector: refsIn(node.where),
|
|
22358
|
+
bound: boundInWhere(node.where)
|
|
22328
22359
|
};
|
|
22329
22360
|
const sources = withTable(sourcesIn(node.from, defined), written);
|
|
22330
22361
|
return accessesAcross(sources, written, stated, defined);
|
|
@@ -22369,7 +22400,8 @@ function deletedTable(node) {
|
|
|
22369
22400
|
function selectAccesses(statement, names) {
|
|
22370
22401
|
const stated = {
|
|
22371
22402
|
fields: refsIn(statement.columns),
|
|
22372
|
-
selector: refsIn(statement.where)
|
|
22403
|
+
selector: refsIn(statement.where),
|
|
22404
|
+
bound: boundInWhere(statement.where)
|
|
22373
22405
|
};
|
|
22374
22406
|
return accessesAcross(sourcesIn(statement.from, names), null, stated, names);
|
|
22375
22407
|
}
|
|
@@ -22378,15 +22410,40 @@ function accessesAcross(sources, written, stated, defined) {
|
|
|
22378
22410
|
const only = named.length === 1 && !sources.derived ? named[0] ?? null : null;
|
|
22379
22411
|
const fields = byTable(stated.fields, sources.tables, written ?? only);
|
|
22380
22412
|
const selector = byTable(stated.selector, sources.tables, only);
|
|
22381
|
-
const
|
|
22382
|
-
|
|
22383
|
-
|
|
22384
|
-
|
|
22385
|
-
|
|
22386
|
-
|
|
22387
|
-
|
|
22413
|
+
const bound = boundByTable(stated.bound, sources.tables, {
|
|
22414
|
+
fields: written ?? only,
|
|
22415
|
+
selector: only
|
|
22416
|
+
});
|
|
22417
|
+
const own = named.filter((table) => !defined.has(table)).map((table) => {
|
|
22418
|
+
const placeholders = bound.get(table) ?? [];
|
|
22419
|
+
return {
|
|
22420
|
+
table,
|
|
22421
|
+
qualifier: [],
|
|
22422
|
+
kind: table === written ? "write" : "read",
|
|
22423
|
+
fields: fields.get(table) ?? [],
|
|
22424
|
+
selector: selector.get(table) ?? [],
|
|
22425
|
+
...placeholders.length > 0 ? { placeholders } : {}
|
|
22426
|
+
};
|
|
22427
|
+
});
|
|
22388
22428
|
return [...own, ...sources.inside];
|
|
22389
22429
|
}
|
|
22430
|
+
function boundByTable(refs, tables, only) {
|
|
22431
|
+
const found = /* @__PURE__ */ new Map();
|
|
22432
|
+
for (const ref of refs) {
|
|
22433
|
+
const table = ref.table === void 0 ? only[ref.clause] : tables.get(ref.table) ?? null;
|
|
22434
|
+
if (table === null) {
|
|
22435
|
+
continue;
|
|
22436
|
+
}
|
|
22437
|
+
const already = found.get(table) ?? [];
|
|
22438
|
+
already.push({
|
|
22439
|
+
field: ref.field,
|
|
22440
|
+
clause: ref.clause,
|
|
22441
|
+
placeholder: ref.placeholder
|
|
22442
|
+
});
|
|
22443
|
+
found.set(table, already);
|
|
22444
|
+
}
|
|
22445
|
+
return found;
|
|
22446
|
+
}
|
|
22390
22447
|
function byTable(refs, tables, only) {
|
|
22391
22448
|
const found = /* @__PURE__ */ new Map();
|
|
22392
22449
|
for (const ref of refs) {
|
|
@@ -22483,6 +22540,79 @@ function refsIn(value) {
|
|
|
22483
22540
|
walk(value);
|
|
22484
22541
|
return found;
|
|
22485
22542
|
}
|
|
22543
|
+
function placeholderOf(value) {
|
|
22544
|
+
const node = asNode(value);
|
|
22545
|
+
if (node?.type === "var" && node.prefix === "$") {
|
|
22546
|
+
return typeof node.name === "number" ? node.name : null;
|
|
22547
|
+
}
|
|
22548
|
+
return node?.type === "param" ? stringOf(node.value) : null;
|
|
22549
|
+
}
|
|
22550
|
+
function boundInWhere(value) {
|
|
22551
|
+
const found = [];
|
|
22552
|
+
const walk = (node) => {
|
|
22553
|
+
if (Array.isArray(node)) {
|
|
22554
|
+
node.forEach(walk);
|
|
22555
|
+
return;
|
|
22556
|
+
}
|
|
22557
|
+
const record = asNode(node);
|
|
22558
|
+
if (record === null) {
|
|
22559
|
+
return;
|
|
22560
|
+
}
|
|
22561
|
+
if (record.type === "binary_expr" && record.operator === "=") {
|
|
22562
|
+
const pair = columnAndPlaceholder(record.left, record.right) ?? columnAndPlaceholder(record.right, record.left);
|
|
22563
|
+
if (pair !== null) {
|
|
22564
|
+
found.push({ ...pair, clause: "selector" });
|
|
22565
|
+
return;
|
|
22566
|
+
}
|
|
22567
|
+
}
|
|
22568
|
+
for (const entry of Object.values(record)) {
|
|
22569
|
+
walk(entry);
|
|
22570
|
+
}
|
|
22571
|
+
};
|
|
22572
|
+
walk(value);
|
|
22573
|
+
return found;
|
|
22574
|
+
}
|
|
22575
|
+
function columnAndPlaceholder(column, placeholder) {
|
|
22576
|
+
const ref = asNode(column);
|
|
22577
|
+
const field = ref?.type === "column_ref" ? columnName(ref.column) : null;
|
|
22578
|
+
const given = placeholderOf(placeholder);
|
|
22579
|
+
if (ref === null || field === null || given === null) {
|
|
22580
|
+
return null;
|
|
22581
|
+
}
|
|
22582
|
+
return { table: stringOf(ref.table) ?? void 0, field, placeholder: given };
|
|
22583
|
+
}
|
|
22584
|
+
function boundInSet(value) {
|
|
22585
|
+
const found = [];
|
|
22586
|
+
for (const entry of Array.isArray(value) ? value : []) {
|
|
22587
|
+
const node = asNode(entry);
|
|
22588
|
+
const field = node === null ? null : columnName(node.column);
|
|
22589
|
+
const given = placeholderOf(node?.value);
|
|
22590
|
+
if (node !== null && field !== null && given !== null) {
|
|
22591
|
+
const table = stringOf(node.table) ?? void 0;
|
|
22592
|
+
found.push({ table, field, clause: "fields", placeholder: given });
|
|
22593
|
+
}
|
|
22594
|
+
}
|
|
22595
|
+
return found;
|
|
22596
|
+
}
|
|
22597
|
+
function boundInRows(columns, values) {
|
|
22598
|
+
const rows = asNode(values)?.values;
|
|
22599
|
+
const found = [];
|
|
22600
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
22601
|
+
const items = asNode(row)?.value;
|
|
22602
|
+
columns.forEach((field, at) => {
|
|
22603
|
+
const given = placeholderOf(Array.isArray(items) ? items[at] : null);
|
|
22604
|
+
if (given !== null) {
|
|
22605
|
+
found.push({
|
|
22606
|
+
table: void 0,
|
|
22607
|
+
field,
|
|
22608
|
+
clause: "fields",
|
|
22609
|
+
placeholder: given
|
|
22610
|
+
});
|
|
22611
|
+
}
|
|
22612
|
+
});
|
|
22613
|
+
}
|
|
22614
|
+
return found;
|
|
22615
|
+
}
|
|
22486
22616
|
function unqualified(names) {
|
|
22487
22617
|
return names.map((field) => ({ table: void 0, field }));
|
|
22488
22618
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suss/sql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Read which tables and fields a SQL statement touches, so suss can check the query against the schema it runs on.",
|
|
6
6
|
"keywords": [
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
],
|
|
14
14
|
"license": "Apache-2.0",
|
|
15
15
|
"author": "Nimbus Cloud AI LLC",
|
|
16
|
-
"homepage": "https://
|
|
16
|
+
"homepage": "https://suss.sh",
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
19
19
|
"url": "git+https://github.com/nimbuscloud-ai/suss.git",
|