@uniflowed/sql 0.17.0 → 0.18.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/index.js +16 -2
- package/package.json +1 -1
- package/postgresql.js +39 -9
package/index.js
CHANGED
|
@@ -85,7 +85,9 @@ export type SqlFailure =
|
|
|
85
85
|
| {| readonly kind: "encode", readonly expected: string, readonly value: mixed |}
|
|
86
86
|
| {| readonly kind: "shape", readonly expected: number, readonly actual: number |}
|
|
87
87
|
| {| readonly kind: "closed" |}
|
|
88
|
-
| {| readonly kind: "unsupported", readonly feature: string |}
|
|
88
|
+
| {| readonly kind: "unsupported", readonly feature: string |}
|
|
89
|
+
| {| readonly kind: "params", readonly width: number, readonly max: number |}
|
|
90
|
+
| {| readonly kind: "row", readonly index: number, readonly length: number |};
|
|
89
91
|
|
|
90
92
|
/**
|
|
91
93
|
* A value that did not fit its column, a row of the wrong width, or a
|
|
@@ -135,6 +137,10 @@ function explain(failure: SqlFailure, query: string | null): string {
|
|
|
135
137
|
{kind: "closed"} => "the transaction has already committed or rolled back",
|
|
136
138
|
{kind: "unsupported", feature: const feature} =>
|
|
137
139
|
`${feature} is not supported by this adapter`,
|
|
140
|
+
{kind: "params", width: const width, max: const max} =>
|
|
141
|
+
`a :copyfrom row binds ${width} parameters and this adapter allows ${max}`,
|
|
142
|
+
{kind: "row", index: const index, length: const length} =>
|
|
143
|
+
`a :copyfrom row has ${length} fields and the statement reads index ${index}`,
|
|
138
144
|
}
|
|
139
145
|
);
|
|
140
146
|
}
|
|
@@ -375,6 +381,10 @@ export async function copyFrom(
|
|
|
375
381
|
return 0;
|
|
376
382
|
}
|
|
377
383
|
const width = plan.refs.length;
|
|
384
|
+
// One row is the smallest statement. Wider than `maxParams`, it cannot be sent.
|
|
385
|
+
if (width > db.maxParams) {
|
|
386
|
+
throw new SqlError({ kind: "params", width, max: db.maxParams }, name);
|
|
387
|
+
}
|
|
378
388
|
const perStatement =
|
|
379
389
|
width === 0 ? COPY_ROWS_WITHOUT_PARAMS : Math.max(1, Math.floor(db.maxParams / width));
|
|
380
390
|
const insert = async (q: Queryable): Promise<number> => {
|
|
@@ -386,7 +396,11 @@ export async function copyFrom(
|
|
|
386
396
|
for (const row of chunk) {
|
|
387
397
|
let tuple = plan.tuple[0];
|
|
388
398
|
for (let i = 0; i < width; i += 1) {
|
|
389
|
-
|
|
399
|
+
const index = plan.refs[i];
|
|
400
|
+
if (index >= row.length) {
|
|
401
|
+
throw new SqlError({ kind: "row", index, length: row.length }, name);
|
|
402
|
+
}
|
|
403
|
+
params.push(row[index]);
|
|
390
404
|
tuple += (q.engine === "postgresql" ? `$${params.length}` : "?") + plan.tuple[i + 1];
|
|
391
405
|
}
|
|
392
406
|
tuples.push(tuple);
|
package/package.json
CHANGED
package/postgresql.js
CHANGED
|
@@ -358,7 +358,21 @@ function instantToText(value: Date): string {
|
|
|
358
358
|
if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
|
|
359
359
|
return encodeError("timestamptz (a valid Date)", value);
|
|
360
360
|
}
|
|
361
|
-
|
|
361
|
+
// ISO DateStyle. `toISOString()` is `T`/`Z` with a leading `+` past year
|
|
362
|
+
// 9999, and PostgreSQL rejects that form for BC, year 0, and year >= 10000.
|
|
363
|
+
// Year 0 is `0001 … BC` and year -43 is `0044 … BC`.
|
|
364
|
+
const year = value.getUTCFullYear();
|
|
365
|
+
const bc = year <= 0;
|
|
366
|
+
const printed = String(bc ? 1 - year : year);
|
|
367
|
+
const y = printed.length < 4 ? printed.padStart(4, "0") : printed;
|
|
368
|
+
const pad = (part: number): string => String(part).padStart(2, "0");
|
|
369
|
+
const millis = value.getUTCMilliseconds();
|
|
370
|
+
const fraction = millis === 0 ? "" : `.${String(millis).padStart(3, "0")}`;
|
|
371
|
+
return (
|
|
372
|
+
`${y}-${pad(value.getUTCMonth() + 1)}-${pad(value.getUTCDate())} ` +
|
|
373
|
+
`${pad(value.getUTCHours())}:${pad(value.getUTCMinutes())}:${pad(value.getUTCSeconds())}` +
|
|
374
|
+
`${fraction}+00${bc ? " BC" : ""}`
|
|
375
|
+
);
|
|
362
376
|
}
|
|
363
377
|
|
|
364
378
|
/** `timestamp with time zone`: an instant. Millisecond precision, which is a `Date`'s. */
|
|
@@ -404,10 +418,12 @@ export function enumeration<T extends string>(
|
|
|
404
418
|
/**
|
|
405
419
|
* Parse an array literal: `{1,2,NULL}`, `{{a,b},{c,d}}`, `{"a b","x\"y"}`.
|
|
406
420
|
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
421
|
+
* `delimiter` is the type's typdelim: `,` for almost every type, `;` for
|
|
422
|
+
* `box`, whose literal contains commas. A literal with non-default bounds
|
|
423
|
+
* starts with a dimension decoration (`[0:2]={…}`); the bounds are not part
|
|
424
|
+
* of the value and are dropped.
|
|
409
425
|
*/
|
|
410
|
-
export function parseArray(literal: string): $ReadOnlyArray<ArrayNode> {
|
|
426
|
+
export function parseArray(literal: string, delimiter: string = ","): $ReadOnlyArray<ArrayNode> {
|
|
411
427
|
let at = 0;
|
|
412
428
|
if (literal.startsWith("[")) {
|
|
413
429
|
const equals = literal.indexOf("=");
|
|
@@ -456,7 +472,11 @@ export function parseArray(literal: string): $ReadOnlyArray<ArrayNode> {
|
|
|
456
472
|
items.push(value);
|
|
457
473
|
} else {
|
|
458
474
|
const start = at;
|
|
459
|
-
while (
|
|
475
|
+
while (
|
|
476
|
+
at < literal.length &&
|
|
477
|
+
literal.charAt(at) !== "}" &&
|
|
478
|
+
!literal.startsWith(delimiter, at)
|
|
479
|
+
) {
|
|
460
480
|
at += 1;
|
|
461
481
|
}
|
|
462
482
|
const raw = literal.slice(start, at).trim();
|
|
@@ -468,8 +488,8 @@ export function parseArray(literal: string): $ReadOnlyArray<ArrayNode> {
|
|
|
468
488
|
while (literal.charAt(at) === " ") {
|
|
469
489
|
at += 1;
|
|
470
490
|
}
|
|
471
|
-
if (literal.
|
|
472
|
-
at +=
|
|
491
|
+
if (literal.startsWith(delimiter, at)) {
|
|
492
|
+
at += delimiter.length;
|
|
473
493
|
} else if (literal.charAt(at) === "}") {
|
|
474
494
|
at += 1;
|
|
475
495
|
return items;
|
|
@@ -486,6 +506,15 @@ export function parseArray(literal: string): $ReadOnlyArray<ArrayNode> {
|
|
|
486
506
|
return root;
|
|
487
507
|
}
|
|
488
508
|
|
|
509
|
+
// `box` literals contain commas, so PostgreSQL delimits `box[]` (every dimension) with `;`.
|
|
510
|
+
function arrayDelimiter(sqlType: string): string {
|
|
511
|
+
let base = sqlType;
|
|
512
|
+
while (base.endsWith("[]")) {
|
|
513
|
+
base = base.slice(0, -2);
|
|
514
|
+
}
|
|
515
|
+
return base === "box" ? ";" : ",";
|
|
516
|
+
}
|
|
517
|
+
|
|
489
518
|
/**
|
|
490
519
|
* An array of `element`. Nest it for more dimensions: `array(array(int4))` is
|
|
491
520
|
* `int4[][]`.
|
|
@@ -498,6 +527,7 @@ export function parseArray(literal: string): $ReadOnlyArray<ArrayNode> {
|
|
|
498
527
|
*/
|
|
499
528
|
export function array<T>(element: Codec<T>): Codec<$ReadOnlyArray<T>> {
|
|
500
529
|
const sqlType = `${element.sqlType}[]`;
|
|
530
|
+
const delimiter = arrayDelimiter(element.sqlType);
|
|
501
531
|
const fromNode = (node: ArrayNode): $ReadOnlyArray<T> => {
|
|
502
532
|
if (node === null || typeof node === "string") {
|
|
503
533
|
return decodeError(`${sqlType} with ${element.depth + 1} dimension(s)`, node);
|
|
@@ -508,7 +538,7 @@ export function array<T>(element: Codec<T>): Codec<$ReadOnlyArray<T>> {
|
|
|
508
538
|
if (!Array.isArray(value)) {
|
|
509
539
|
return encodeError(`${sqlType} (an array)`, value);
|
|
510
540
|
}
|
|
511
|
-
return `{${value.map(element.literal).join(
|
|
541
|
+
return `{${value.map(element.literal).join(delimiter)}}`;
|
|
512
542
|
};
|
|
513
543
|
return {
|
|
514
544
|
sqlType,
|
|
@@ -516,7 +546,7 @@ export function array<T>(element: Codec<T>): Codec<$ReadOnlyArray<T>> {
|
|
|
516
546
|
if (typeof value !== "string") {
|
|
517
547
|
return decodeError(sqlType, value);
|
|
518
548
|
}
|
|
519
|
-
const root = parseArray(value);
|
|
549
|
+
const root = parseArray(value, delimiter);
|
|
520
550
|
// An empty array has no dimensions at all, whatever the column says.
|
|
521
551
|
return root.length === 0 ? [] : fromNode(root);
|
|
522
552
|
},
|