@uniflowed/sql 0.34.0 → 0.36.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/adapters/better-sqlite3.js +8 -8
- package/adapters/bun-sqlite.js +8 -8
- package/adapters/d1.js +2 -2
- package/adapters/mysql2.js +13 -13
- package/adapters/node-sqlite.js +7 -7
- package/adapters/pg.js +120 -9
- package/adapters/pglite.js +29 -6
- package/adapters/postgres.js +99 -8
- package/index.js +233 -97
- package/internal/shared.js +4 -4
- package/internal/statements.js +1 -1
- package/mysql.js +10 -10
- package/package.json +1 -1
- package/postgresql.js +14 -14
- package/sqlite.js +13 -13
package/index.js
CHANGED
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
// type. So an `int8` is a `bigint` under `pg` and under `postgres` alike,
|
|
17
17
|
// rather than whatever each driver's own parser decided.
|
|
18
18
|
// * **The parts of sqlc's semantics a driver does not have**: `sqlc.slice`
|
|
19
|
-
// expansion, `:copyfrom`
|
|
20
|
-
//
|
|
19
|
+
// expansion, `:copyfrom` (PostgreSQL `COPY` when the adapter can send it,
|
|
20
|
+
// otherwise chunked multi-row inserts), `:batch*` in one transaction, and
|
|
21
|
+
// nested transactions as savepoints.
|
|
21
22
|
// * **A synchronous path** for drivers that run a statement on the calling
|
|
22
23
|
// thread (`node:sqlite`, `better-sqlite3`, `bun:sqlite`). Generated code
|
|
23
24
|
// with `sync: true` calls it, so a transaction can stay inside `BEGIN`
|
|
@@ -65,12 +66,24 @@ export type QueryMode = "rows" | "exec";
|
|
|
65
66
|
* `transaction` is absent where the platform has no interactive transactions
|
|
66
67
|
* (Cloudflare D1). Inside a transaction it opens a savepoint.
|
|
67
68
|
*/
|
|
69
|
+
/**
|
|
70
|
+
* Send one `COPY … FROM STDIN` payload and resolve to the number of rows the
|
|
71
|
+
* server accepted.
|
|
72
|
+
*
|
|
73
|
+
* Present on a PostgreSQL adapter that can speak `COPY`. Absent everywhere
|
|
74
|
+
* else, where `:copyfrom` stays a chunked `INSERT`. The payload is PostgreSQL
|
|
75
|
+
* text format: tab-separated fields, one row per line.
|
|
76
|
+
*/
|
|
77
|
+
export type CopyIn = (statement: string, payload: string) => Promise<number>;
|
|
78
|
+
|
|
68
79
|
export type Queryable = {
|
|
69
80
|
readonly engine: Engine,
|
|
70
81
|
/** The most bound parameters one statement may carry; `:copyfrom` chunks under it. */
|
|
71
82
|
readonly maxParams: number,
|
|
72
83
|
query(text: string, params: $ReadOnlyArray<SqlParam>, mode: QueryMode): Promise<QueryResult>,
|
|
73
84
|
readonly transaction?: <T>(body: (tx: Queryable) => Promise<T>) => Promise<T>,
|
|
85
|
+
/** PostgreSQL `COPY`, when this adapter can send one. */
|
|
86
|
+
readonly copy?: CopyIn,
|
|
74
87
|
...
|
|
75
88
|
};
|
|
76
89
|
|
|
@@ -91,7 +104,7 @@ export type Queryable = {
|
|
|
91
104
|
* body would deadlock the same way `await db.query` does inside `transaction`.
|
|
92
105
|
*/
|
|
93
106
|
export type SyncQueryable = {
|
|
94
|
-
readonly engine: Engine,
|
|
107
|
+
readonly engine : Engine,
|
|
95
108
|
readonly maxParams: number,
|
|
96
109
|
querySync(text: string, params: $ReadOnlyArray<SqlParam>, mode: QueryMode): QueryResult,
|
|
97
110
|
readonly transactionSync?: <T>(body: (tx: SyncQueryable) => T) => T,
|
|
@@ -155,19 +168,19 @@ function explain(failure: SqlFailure, query: string | null): string {
|
|
|
155
168
|
return (
|
|
156
169
|
where +
|
|
157
170
|
match (failure) {
|
|
158
|
-
{kind: "decode", expected: const expected, value: const value}
|
|
171
|
+
{kind: "decode", expected: const expected, value: const value} =>
|
|
159
172
|
`expected ${expected} from the database, got ${show(value)}`,
|
|
160
|
-
{kind: "encode", expected: const expected, value: const value}
|
|
173
|
+
{kind: "encode", expected: const expected, value: const value} =>
|
|
161
174
|
`expected ${expected} as a parameter, got ${show(value)}`,
|
|
162
175
|
{kind: "shape", expected: const expected, actual: const actual} =>
|
|
163
176
|
`the adapter returned ${actual} columns where the query has ${expected}; ` +
|
|
164
177
|
"regenerate with `uf sqlc generate`, or check that the adapter returns rows as arrays",
|
|
165
|
-
{kind: "closed"}
|
|
166
|
-
{kind: "unsupported", feature: const feature}
|
|
178
|
+
{kind: "closed"} => "the transaction has already committed or rolled back",
|
|
179
|
+
{kind: "unsupported", feature: const feature} =>
|
|
167
180
|
`${feature} is not supported by this adapter`,
|
|
168
|
-
{kind: "params", width: const width, max: const max}
|
|
181
|
+
{kind: "params", width: const width, max: const max} =>
|
|
169
182
|
`a :copyfrom row binds ${width} parameters and this adapter allows ${max}`,
|
|
170
|
-
{kind: "row", index: const index, length: const length}
|
|
183
|
+
{kind: "row", index: const index, length: const length} =>
|
|
171
184
|
`a :copyfrom row has ${length} fields and the statement reads index ${index}`,
|
|
172
185
|
}
|
|
173
186
|
);
|
|
@@ -229,11 +242,11 @@ function allRows<T>(rows: $ReadOnlyArray<Row>, width: number, decode: (row: Row)
|
|
|
229
242
|
|
|
230
243
|
/** `:one` — the first row, or `null`. */
|
|
231
244
|
export async function one<T>(
|
|
232
|
-
db: Queryable,
|
|
233
|
-
name: string,
|
|
234
|
-
text: string,
|
|
245
|
+
db : Queryable,
|
|
246
|
+
name : string,
|
|
247
|
+
text : string,
|
|
235
248
|
params: $ReadOnlyArray<SqlParam>,
|
|
236
|
-
width: number,
|
|
249
|
+
width : number,
|
|
237
250
|
decode: (row: Row) => T,
|
|
238
251
|
): Promise<T | null> {
|
|
239
252
|
try {
|
|
@@ -246,11 +259,11 @@ export async function one<T>(
|
|
|
246
259
|
|
|
247
260
|
/** `:one` on a [`SyncQueryable`]. */
|
|
248
261
|
export function oneSync<T>(
|
|
249
|
-
db: SyncQueryable,
|
|
250
|
-
name: string,
|
|
251
|
-
text: string,
|
|
262
|
+
db : SyncQueryable,
|
|
263
|
+
name : string,
|
|
264
|
+
text : string,
|
|
252
265
|
params: $ReadOnlyArray<SqlParam>,
|
|
253
|
-
width: number,
|
|
266
|
+
width : number,
|
|
254
267
|
decode: (row: Row) => T,
|
|
255
268
|
): T | null {
|
|
256
269
|
try {
|
|
@@ -263,11 +276,11 @@ export function oneSync<T>(
|
|
|
263
276
|
|
|
264
277
|
/** `:many` — every row. */
|
|
265
278
|
export async function many<T>(
|
|
266
|
-
db: Queryable,
|
|
267
|
-
name: string,
|
|
268
|
-
text: string,
|
|
279
|
+
db : Queryable,
|
|
280
|
+
name : string,
|
|
281
|
+
text : string,
|
|
269
282
|
params: $ReadOnlyArray<SqlParam>,
|
|
270
|
-
width: number,
|
|
283
|
+
width : number,
|
|
271
284
|
decode: (row: Row) => T,
|
|
272
285
|
): Promise<Array<T>> {
|
|
273
286
|
try {
|
|
@@ -280,11 +293,11 @@ export async function many<T>(
|
|
|
280
293
|
|
|
281
294
|
/** `:many` on a [`SyncQueryable`]. */
|
|
282
295
|
export function manySync<T>(
|
|
283
|
-
db: SyncQueryable,
|
|
284
|
-
name: string,
|
|
285
|
-
text: string,
|
|
296
|
+
db : SyncQueryable,
|
|
297
|
+
name : string,
|
|
298
|
+
text : string,
|
|
286
299
|
params: $ReadOnlyArray<SqlParam>,
|
|
287
|
-
width: number,
|
|
300
|
+
width : number,
|
|
288
301
|
decode: (row: Row) => T,
|
|
289
302
|
): Array<T> {
|
|
290
303
|
try {
|
|
@@ -296,9 +309,9 @@ export function manySync<T>(
|
|
|
296
309
|
}
|
|
297
310
|
|
|
298
311
|
async function run(
|
|
299
|
-
db: Queryable,
|
|
300
|
-
name: string,
|
|
301
|
-
text: string,
|
|
312
|
+
db : Queryable,
|
|
313
|
+
name : string,
|
|
314
|
+
text : string,
|
|
302
315
|
params: $ReadOnlyArray<SqlParam>,
|
|
303
316
|
): Promise<QueryResult> {
|
|
304
317
|
try {
|
|
@@ -309,9 +322,9 @@ async function run(
|
|
|
309
322
|
}
|
|
310
323
|
|
|
311
324
|
function executed(
|
|
312
|
-
db: SyncQueryable,
|
|
313
|
-
name: string,
|
|
314
|
-
text: string,
|
|
325
|
+
db : SyncQueryable,
|
|
326
|
+
name : string,
|
|
327
|
+
text : string,
|
|
315
328
|
params: $ReadOnlyArray<SqlParam>,
|
|
316
329
|
): QueryResult {
|
|
317
330
|
try {
|
|
@@ -323,9 +336,9 @@ function executed(
|
|
|
323
336
|
|
|
324
337
|
/** `:exec`. */
|
|
325
338
|
export async function exec(
|
|
326
|
-
db: Queryable,
|
|
327
|
-
name: string,
|
|
328
|
-
text: string,
|
|
339
|
+
db : Queryable,
|
|
340
|
+
name : string,
|
|
341
|
+
text : string,
|
|
329
342
|
params: $ReadOnlyArray<SqlParam>,
|
|
330
343
|
): Promise<void> {
|
|
331
344
|
await run(db, name, text, params);
|
|
@@ -333,9 +346,9 @@ export async function exec(
|
|
|
333
346
|
|
|
334
347
|
/** `:exec` on a [`SyncQueryable`]. */
|
|
335
348
|
export function execSync(
|
|
336
|
-
db: SyncQueryable,
|
|
337
|
-
name: string,
|
|
338
|
-
text: string,
|
|
349
|
+
db : SyncQueryable,
|
|
350
|
+
name : string,
|
|
351
|
+
text : string,
|
|
339
352
|
params: $ReadOnlyArray<SqlParam>,
|
|
340
353
|
): void {
|
|
341
354
|
executed(db, name, text, params);
|
|
@@ -343,9 +356,9 @@ export function execSync(
|
|
|
343
356
|
|
|
344
357
|
/** `:execrows` — how many rows the statement changed. */
|
|
345
358
|
export async function execRows(
|
|
346
|
-
db: Queryable,
|
|
347
|
-
name: string,
|
|
348
|
-
text: string,
|
|
359
|
+
db : Queryable,
|
|
360
|
+
name : string,
|
|
361
|
+
text : string,
|
|
349
362
|
params: $ReadOnlyArray<SqlParam>,
|
|
350
363
|
): Promise<number> {
|
|
351
364
|
return (await run(db, name, text, params)).rowsAffected;
|
|
@@ -353,9 +366,9 @@ export async function execRows(
|
|
|
353
366
|
|
|
354
367
|
/** `:execrows` on a [`SyncQueryable`]. */
|
|
355
368
|
export function execRowsSync(
|
|
356
|
-
db: SyncQueryable,
|
|
357
|
-
name: string,
|
|
358
|
-
text: string,
|
|
369
|
+
db : SyncQueryable,
|
|
370
|
+
name : string,
|
|
371
|
+
text : string,
|
|
359
372
|
params: $ReadOnlyArray<SqlParam>,
|
|
360
373
|
): number {
|
|
361
374
|
return executed(db, name, text, params).rowsAffected;
|
|
@@ -369,9 +382,9 @@ export type ExecResult = {|
|
|
|
369
382
|
|
|
370
383
|
/** `:execresult` — the affected-row count and, on MySQL and SQLite, the last id. */
|
|
371
384
|
export async function execResult(
|
|
372
|
-
db: Queryable,
|
|
373
|
-
name: string,
|
|
374
|
-
text: string,
|
|
385
|
+
db : Queryable,
|
|
386
|
+
name : string,
|
|
387
|
+
text : string,
|
|
375
388
|
params: $ReadOnlyArray<SqlParam>,
|
|
376
389
|
): Promise<ExecResult> {
|
|
377
390
|
const { rowsAffected, lastInsertId } = await run(db, name, text, params);
|
|
@@ -380,9 +393,9 @@ export async function execResult(
|
|
|
380
393
|
|
|
381
394
|
/** `:execresult` on a [`SyncQueryable`]. */
|
|
382
395
|
export function execResultSync(
|
|
383
|
-
db: SyncQueryable,
|
|
384
|
-
name: string,
|
|
385
|
-
text: string,
|
|
396
|
+
db : SyncQueryable,
|
|
397
|
+
name : string,
|
|
398
|
+
text : string,
|
|
386
399
|
params: $ReadOnlyArray<SqlParam>,
|
|
387
400
|
): ExecResult {
|
|
388
401
|
const { rowsAffected, lastInsertId } = executed(db, name, text, params);
|
|
@@ -403,9 +416,9 @@ function insertedId(result: QueryResult, name: string): bigint {
|
|
|
403
416
|
* `:one`, and this throws rather than resolve to a made-up zero.
|
|
404
417
|
*/
|
|
405
418
|
export async function execLastId(
|
|
406
|
-
db: Queryable,
|
|
407
|
-
name: string,
|
|
408
|
-
text: string,
|
|
419
|
+
db : Queryable,
|
|
420
|
+
name : string,
|
|
421
|
+
text : string,
|
|
409
422
|
params: $ReadOnlyArray<SqlParam>,
|
|
410
423
|
): Promise<bigint> {
|
|
411
424
|
return insertedId(await run(db, name, text, params), name);
|
|
@@ -413,9 +426,9 @@ export async function execLastId(
|
|
|
413
426
|
|
|
414
427
|
/** `:execlastid` on a [`SyncQueryable`]. */
|
|
415
428
|
export function execLastIdSync(
|
|
416
|
-
db: SyncQueryable,
|
|
417
|
-
name: string,
|
|
418
|
-
text: string,
|
|
429
|
+
db : SyncQueryable,
|
|
430
|
+
name : string,
|
|
431
|
+
text : string,
|
|
419
432
|
params: $ReadOnlyArray<SqlParam>,
|
|
420
433
|
): bigint {
|
|
421
434
|
return insertedId(executed(db, name, text, params), name);
|
|
@@ -453,7 +466,7 @@ export function slice(values: $ReadOnlyArray<SqlParam>): Slice {
|
|
|
453
466
|
* should match.
|
|
454
467
|
*/
|
|
455
468
|
export function expand(
|
|
456
|
-
parts: $ReadOnlyArray<string>,
|
|
469
|
+
parts : $ReadOnlyArray<string>,
|
|
457
470
|
values: $ReadOnlyArray<SqlParam | Slice>,
|
|
458
471
|
): {| readonly text: string, readonly params: $ReadOnlyArray<SqlParam> |} {
|
|
459
472
|
const params: Array<SqlParam> = [];
|
|
@@ -485,20 +498,110 @@ export function expand(
|
|
|
485
498
|
* `VALUES ($1, $2)` is `tuple: ["(", ", ", ")"]`, `refs: [0, 1]`.
|
|
486
499
|
*/
|
|
487
500
|
export type CopyPlan = {|
|
|
488
|
-
readonly head: string,
|
|
501
|
+
readonly head : string,
|
|
489
502
|
readonly tuple: $ReadOnlyArray<string>,
|
|
490
|
-
readonly refs: $ReadOnlyArray<number>,
|
|
491
|
-
readonly tail: string,
|
|
503
|
+
readonly refs : $ReadOnlyArray<number>,
|
|
504
|
+
readonly tail : string,
|
|
492
505
|
|};
|
|
493
506
|
|
|
494
507
|
/** The most rows one statement carries when a row has no parameters at all. */
|
|
495
508
|
const COPY_ROWS_WITHOUT_PARAMS = 1000;
|
|
496
509
|
|
|
497
|
-
|
|
498
|
-
|
|
510
|
+
/**
|
|
511
|
+
* `COPY <table> (<columns>) FROM STDIN` for an `INSERT … VALUES` plan, or
|
|
512
|
+
* `null` when the plan is not that shape.
|
|
513
|
+
*
|
|
514
|
+
* sqlc only accepts `INSERT INTO t (…) VALUES (…)` for `:copyfrom`, and the
|
|
515
|
+
* generator puts the table and the column list in `head`. Anything else keeps
|
|
516
|
+
* the chunked `INSERT`, which is the path every engine can run.
|
|
517
|
+
*/
|
|
518
|
+
function copyStatement(plan: CopyPlan): string | null {
|
|
519
|
+
const prefix = "INSERT INTO ";
|
|
520
|
+
const suffix = " VALUES ";
|
|
521
|
+
if (!plan.head.startsWith(prefix) || !plan.head.endsWith(suffix)) {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
const target = plan.head.slice(prefix.length, plan.head.length - suffix.length);
|
|
525
|
+
return `COPY ${target} FROM STDIN WITH (FORMAT text)`;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** One field of PostgreSQL's text `COPY` format. */
|
|
529
|
+
function copyField(value: SqlParam): string {
|
|
530
|
+
if (value === null) {
|
|
531
|
+
return "\\N";
|
|
532
|
+
}
|
|
533
|
+
return escapeCopyText(copyText(value));
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function copyText(value: string | number | bigint | Uint8Array): string {
|
|
537
|
+
if (typeof value === "string") {
|
|
538
|
+
return value;
|
|
539
|
+
}
|
|
540
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
541
|
+
return String(value);
|
|
542
|
+
}
|
|
543
|
+
let hex = "\\x";
|
|
544
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
545
|
+
hex += value[i].toString(16).padStart(2, "0");
|
|
546
|
+
}
|
|
547
|
+
return hex;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** `\`, newline, carriage return and tab, the four escapes text `COPY` requires. */
|
|
551
|
+
function escapeCopyText(text: string): string {
|
|
552
|
+
let out = "";
|
|
553
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
554
|
+
const character = text[i];
|
|
555
|
+
if (character === "\\") {
|
|
556
|
+
out += "\\\\";
|
|
557
|
+
} else if (character === "\n") {
|
|
558
|
+
out += "\\n";
|
|
559
|
+
} else if (character === "\r") {
|
|
560
|
+
out += "\\r";
|
|
561
|
+
} else if (character === "\t") {
|
|
562
|
+
out += "\\t";
|
|
563
|
+
} else {
|
|
564
|
+
out += character;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return out;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Every row of a `:copyfrom` as one text-format payload.
|
|
572
|
+
*
|
|
573
|
+
* A short row throws before anything is sent, the same way the `INSERT` path
|
|
574
|
+
* does. A null field is `\N`. The payload ends each row with a newline, which
|
|
575
|
+
* is the row terminator `COPY` counts.
|
|
576
|
+
*/
|
|
577
|
+
function copyPayload(
|
|
499
578
|
plan: CopyPlan,
|
|
500
|
-
|
|
579
|
+
rows: $ReadOnlyArray<$ReadOnlyArray<SqlParam>>,
|
|
501
580
|
name: string,
|
|
581
|
+
): string {
|
|
582
|
+
const width = plan.refs.length;
|
|
583
|
+
let payload = "";
|
|
584
|
+
for (const row of rows) {
|
|
585
|
+
for (let i = 0; i < width; i += 1) {
|
|
586
|
+
const index = plan.refs[i];
|
|
587
|
+
if (index >= row.length) {
|
|
588
|
+
throw new SqlError({ kind: "row", index, length: row.length }, name);
|
|
589
|
+
}
|
|
590
|
+
if (i > 0) {
|
|
591
|
+
payload += "\t";
|
|
592
|
+
}
|
|
593
|
+
payload += copyField(row[index]);
|
|
594
|
+
}
|
|
595
|
+
payload += "\n";
|
|
596
|
+
}
|
|
597
|
+
return payload;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function copyChunk(
|
|
601
|
+
engine: Engine,
|
|
602
|
+
plan : CopyPlan,
|
|
603
|
+
chunk : $ReadOnlyArray<$ReadOnlyArray<SqlParam>>,
|
|
604
|
+
name : string,
|
|
502
605
|
): {| readonly text: string, readonly params: $ReadOnlyArray<SqlParam> |} {
|
|
503
606
|
const width = plan.refs.length;
|
|
504
607
|
const params: Array<SqlParam> = [];
|
|
@@ -528,15 +631,18 @@ function copyWidth(db: { readonly maxParams: number, ... }, plan: CopyPlan, name
|
|
|
528
631
|
}
|
|
529
632
|
|
|
530
633
|
/**
|
|
531
|
-
* `:copyfrom` — insert every row
|
|
634
|
+
* `:copyfrom` — insert every row.
|
|
532
635
|
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
*
|
|
636
|
+
* A PostgreSQL adapter that implements `copy` receives one `COPY … FROM STDIN`
|
|
637
|
+
* in text format, which is one statement and so one success or one failure.
|
|
638
|
+
* Every other adapter, and a plan that is not an `INSERT … VALUES`, stays on
|
|
639
|
+
* chunked multi-row `INSERT`s. Those run inside one transaction when the
|
|
640
|
+
* `Queryable` can open one and the rows do not fit in a single statement, so
|
|
641
|
+
* a failure in the third chunk does not leave the first two behind. Resolves
|
|
642
|
+
* to the number of rows inserted.
|
|
537
643
|
*/
|
|
538
644
|
export async function copyFrom(
|
|
539
|
-
db: Queryable,
|
|
645
|
+
db : Queryable,
|
|
540
646
|
name: string,
|
|
541
647
|
plan: CopyPlan,
|
|
542
648
|
rows: $ReadOnlyArray<$ReadOnlyArray<SqlParam>>,
|
|
@@ -544,6 +650,13 @@ export async function copyFrom(
|
|
|
544
650
|
if (rows.length === 0) {
|
|
545
651
|
return 0;
|
|
546
652
|
}
|
|
653
|
+
const copy = db.copy;
|
|
654
|
+
if (db.engine === "postgresql" && typeof copy === "function") {
|
|
655
|
+
const statement = copyStatement(plan);
|
|
656
|
+
if (statement !== null) {
|
|
657
|
+
return copy(statement, copyPayload(plan, rows, name));
|
|
658
|
+
}
|
|
659
|
+
}
|
|
547
660
|
const perStatement = copyWidth(db, plan, name);
|
|
548
661
|
const insert = async (q: Queryable): Promise<number> => {
|
|
549
662
|
let total = 0;
|
|
@@ -562,7 +675,7 @@ export async function copyFrom(
|
|
|
562
675
|
|
|
563
676
|
/** `:copyfrom` on a [`SyncQueryable`]. */
|
|
564
677
|
export function copyFromSync(
|
|
565
|
-
db: SyncQueryable,
|
|
678
|
+
db : SyncQueryable,
|
|
566
679
|
name: string,
|
|
567
680
|
plan: CopyPlan,
|
|
568
681
|
rows: $ReadOnlyArray<$ReadOnlyArray<SqlParam>>,
|
|
@@ -598,9 +711,9 @@ export function copyFromSync(
|
|
|
598
711
|
* order without one, and the docs say so.
|
|
599
712
|
*/
|
|
600
713
|
export async function batch<A, R>(
|
|
601
|
-
db: Queryable,
|
|
714
|
+
db : Queryable,
|
|
602
715
|
items: $ReadOnlyArray<A>,
|
|
603
|
-
body: (q: Queryable, item: A) => Promise<R>,
|
|
716
|
+
body : (q: Queryable, item: A) => Promise<R>,
|
|
604
717
|
): Promise<Array<R>> {
|
|
605
718
|
const each = async (q: Queryable): Promise<Array<R>> => {
|
|
606
719
|
const out: Array<R> = [];
|
|
@@ -623,9 +736,9 @@ export async function batch<A, R>(
|
|
|
623
736
|
* items still run in order, on this thread.
|
|
624
737
|
*/
|
|
625
738
|
export function batchSync<A, R>(
|
|
626
|
-
db: SyncQueryable,
|
|
739
|
+
db : SyncQueryable,
|
|
627
740
|
items: $ReadOnlyArray<A>,
|
|
628
|
-
body: (q: SyncQueryable, item: A) => R,
|
|
741
|
+
body : (q: SyncQueryable, item: A) => R,
|
|
629
742
|
): Array<R> {
|
|
630
743
|
const each = (q: SyncQueryable): Array<R> => {
|
|
631
744
|
const out: Array<R> = [];
|
|
@@ -646,27 +759,29 @@ export function batchSync<A, R>(
|
|
|
646
759
|
|
|
647
760
|
/** How an adapter runs one statement on one connection. */
|
|
648
761
|
export type Run = (
|
|
649
|
-
text: string,
|
|
762
|
+
text : string,
|
|
650
763
|
params: $ReadOnlyArray<SqlParam>,
|
|
651
|
-
mode: QueryMode,
|
|
764
|
+
mode : QueryMode,
|
|
652
765
|
) => Promise<QueryResult>;
|
|
653
766
|
|
|
654
767
|
/** [`Run`] for a driver that finishes the statement before returning. */
|
|
655
768
|
export type RunSync = (
|
|
656
|
-
text: string,
|
|
769
|
+
text : string,
|
|
657
770
|
params: $ReadOnlyArray<SqlParam>,
|
|
658
|
-
mode: QueryMode,
|
|
771
|
+
mode : QueryMode,
|
|
659
772
|
) => QueryResult;
|
|
660
773
|
|
|
661
774
|
/** What [`transactionOn`] needs from an adapter. */
|
|
662
775
|
export type Connection = {|
|
|
663
|
-
readonly engine: Engine,
|
|
776
|
+
readonly engine : Engine,
|
|
664
777
|
readonly maxParams: number,
|
|
665
|
-
readonly run: Run,
|
|
778
|
+
readonly run : Run,
|
|
666
779
|
/** Present when the driver can also run a statement on the calling thread. */
|
|
667
780
|
readonly runSync?: RunSync,
|
|
668
781
|
/** The statement that opens a transaction; `BEGIN` unless the adapter knows better. */
|
|
669
782
|
readonly begin?: string,
|
|
783
|
+
/** PostgreSQL `COPY` on this connection, when the driver can send one. */
|
|
784
|
+
readonly copy?: CopyIn,
|
|
670
785
|
|};
|
|
671
786
|
|
|
672
787
|
/**
|
|
@@ -681,7 +796,7 @@ export type Connection = {|
|
|
|
681
796
|
*/
|
|
682
797
|
export async function transactionOn<T>(
|
|
683
798
|
connection: Connection,
|
|
684
|
-
body: (tx: Queryable) => Promise<T>,
|
|
799
|
+
body : (tx: Queryable) => Promise<T>,
|
|
685
800
|
): Promise<T> {
|
|
686
801
|
await connection.run(connection.begin ?? "BEGIN", [], "exec");
|
|
687
802
|
return scoped(connection, 0, body, "COMMIT", "ROLLBACK");
|
|
@@ -689,10 +804,10 @@ export async function transactionOn<T>(
|
|
|
689
804
|
|
|
690
805
|
async function scoped<T>(
|
|
691
806
|
connection: Connection,
|
|
692
|
-
depth: number,
|
|
693
|
-
body: (tx: Queryable) => Promise<T>,
|
|
694
|
-
commit: string,
|
|
695
|
-
rollback: string,
|
|
807
|
+
depth : number,
|
|
808
|
+
body : (tx: Queryable) => Promise<T>,
|
|
809
|
+
commit : string,
|
|
810
|
+
rollback : string,
|
|
696
811
|
): Promise<T> {
|
|
697
812
|
let open = true;
|
|
698
813
|
const guarded: Run = (text, params, mode) => {
|
|
@@ -701,10 +816,17 @@ async function scoped<T>(
|
|
|
701
816
|
}
|
|
702
817
|
return connection.run(text, params, mode);
|
|
703
818
|
};
|
|
819
|
+
const copy = connection.copy;
|
|
820
|
+
const guardedCopy: CopyIn | void =
|
|
821
|
+
copy === undefined
|
|
822
|
+
? undefined
|
|
823
|
+
: (statement, payload) =>
|
|
824
|
+
open ? copy(statement, payload) : Promise.reject(new SqlError({ kind: "closed" }));
|
|
704
825
|
const tx: Queryable = {
|
|
705
|
-
engine: connection.engine,
|
|
826
|
+
engine : connection.engine,
|
|
706
827
|
maxParams: connection.maxParams,
|
|
707
|
-
query: guarded,
|
|
828
|
+
query : guarded,
|
|
829
|
+
...(guardedCopy === undefined ? null : { copy: guardedCopy }),
|
|
708
830
|
transaction: async <U>(inner: (tx: Queryable) => Promise<U>): Promise<U> => {
|
|
709
831
|
const savepoint = `uf_sp_${depth + 1}`;
|
|
710
832
|
await guarded(`SAVEPOINT ${savepoint}`, [], "exec");
|
|
@@ -773,12 +895,12 @@ export function transactionOnSync<T>(connection: Connection, body: (tx: SyncQuer
|
|
|
773
895
|
}
|
|
774
896
|
|
|
775
897
|
function scopedSync<T>(
|
|
776
|
-
runSync: RunSync,
|
|
898
|
+
runSync : RunSync,
|
|
777
899
|
connection: Connection,
|
|
778
|
-
depth: number,
|
|
779
|
-
body: (tx: SyncQueryable) => T,
|
|
780
|
-
commit: string,
|
|
781
|
-
rollback: string,
|
|
900
|
+
depth : number,
|
|
901
|
+
body : (tx: SyncQueryable) => T,
|
|
902
|
+
commit : string,
|
|
903
|
+
rollback : string,
|
|
782
904
|
): T {
|
|
783
905
|
let open = true;
|
|
784
906
|
const guarded: RunSync = (text, params, mode) => {
|
|
@@ -788,7 +910,7 @@ function scopedSync<T>(
|
|
|
788
910
|
return runSync(text, params, mode);
|
|
789
911
|
};
|
|
790
912
|
const tx: SyncQueryable = {
|
|
791
|
-
engine: connection.engine,
|
|
913
|
+
engine : connection.engine,
|
|
792
914
|
maxParams: connection.maxParams,
|
|
793
915
|
querySync: guarded,
|
|
794
916
|
transactionSync: <U>(inner: (tx: SyncQueryable) => U): U => {
|
|
@@ -863,9 +985,9 @@ function connect(connection: Connection): Handle {
|
|
|
863
985
|
const runSync = connection.runSync;
|
|
864
986
|
|
|
865
987
|
const query = async (
|
|
866
|
-
text: string,
|
|
988
|
+
text : string,
|
|
867
989
|
params: $ReadOnlyArray<SqlParam>,
|
|
868
|
-
mode: QueryMode,
|
|
990
|
+
mode : QueryMode,
|
|
869
991
|
): Promise<QueryResult> => {
|
|
870
992
|
if (syncOpen) {
|
|
871
993
|
unsupported("an asynchronous query while a synchronous transaction is open");
|
|
@@ -894,9 +1016,9 @@ function connect(connection: Connection): Handle {
|
|
|
894
1016
|
}
|
|
895
1017
|
};
|
|
896
1018
|
const querySync = (
|
|
897
|
-
text: string,
|
|
1019
|
+
text : string,
|
|
898
1020
|
params: $ReadOnlyArray<SqlParam>,
|
|
899
|
-
mode: QueryMode,
|
|
1021
|
+
mode : QueryMode,
|
|
900
1022
|
): QueryResult => {
|
|
901
1023
|
const run =
|
|
902
1024
|
connection.runSync ?? unsupported("a synchronous query on an asynchronous connection");
|
|
@@ -910,6 +1032,19 @@ function connect(connection: Connection): Handle {
|
|
|
910
1032
|
}
|
|
911
1033
|
return run(text, params, mode);
|
|
912
1034
|
};
|
|
1035
|
+
const rawCopy = connection.copy;
|
|
1036
|
+
const copy =
|
|
1037
|
+
rawCopy === undefined
|
|
1038
|
+
? undefined
|
|
1039
|
+
: async (statement: string, payload: string): Promise<number> => {
|
|
1040
|
+
if (syncOpen) {
|
|
1041
|
+
unsupported("a COPY while a synchronous transaction is open");
|
|
1042
|
+
}
|
|
1043
|
+
while (gate !== null) {
|
|
1044
|
+
await gate;
|
|
1045
|
+
}
|
|
1046
|
+
return rawCopy(statement, payload);
|
|
1047
|
+
};
|
|
913
1048
|
const transactionSync = <T>(body: (tx: SyncQueryable) => T): T => {
|
|
914
1049
|
if (runSync == null) {
|
|
915
1050
|
unsupported("a synchronous transaction on an asynchronous connection");
|
|
@@ -930,12 +1065,13 @@ function connect(connection: Connection): Handle {
|
|
|
930
1065
|
}
|
|
931
1066
|
};
|
|
932
1067
|
return {
|
|
933
|
-
engine: connection.engine,
|
|
1068
|
+
engine : connection.engine,
|
|
934
1069
|
maxParams: connection.maxParams,
|
|
935
1070
|
query,
|
|
936
1071
|
transaction,
|
|
937
1072
|
querySync,
|
|
938
1073
|
transactionSync,
|
|
1074
|
+
...(copy === undefined ? null : { copy }),
|
|
939
1075
|
};
|
|
940
1076
|
}
|
|
941
1077
|
|
package/internal/shared.js
CHANGED
|
@@ -161,9 +161,9 @@ export function bytesParam(expected: string, value: mixed): Uint8Array {
|
|
|
161
161
|
|
|
162
162
|
/** One of an enum's labels. */
|
|
163
163
|
export function label<T extends string>(
|
|
164
|
-
values: $ReadOnlyArray<T>,
|
|
164
|
+
values : $ReadOnlyArray<T>,
|
|
165
165
|
expected: string,
|
|
166
|
-
value: mixed,
|
|
166
|
+
value : mixed,
|
|
167
167
|
): T {
|
|
168
168
|
const index = typeof value === "string" ? values.findIndex((item) => item === value) : -1;
|
|
169
169
|
if (index < 0) {
|
|
@@ -174,9 +174,9 @@ export function label<T extends string>(
|
|
|
174
174
|
|
|
175
175
|
/** Encode one of an enum's labels, refusing a string that is not one. */
|
|
176
176
|
export function labelParam<T extends string>(
|
|
177
|
-
values: $ReadOnlyArray<T>,
|
|
177
|
+
values : $ReadOnlyArray<T>,
|
|
178
178
|
expected: string,
|
|
179
|
-
value: mixed,
|
|
179
|
+
value : mixed,
|
|
180
180
|
): T {
|
|
181
181
|
const index = typeof value === "string" ? values.findIndex((item) => item === value) : -1;
|
|
182
182
|
if (index < 0) {
|
package/internal/statements.js
CHANGED