@atscript/db-postgres 0.1.130 → 0.1.132
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +71 -22
- package/dist/index.d.cts +14 -1
- package/dist/index.d.mts +14 -1
- package/dist/index.mjs +73 -24
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
let _atscript_db = require("@atscript/db");
|
|
3
3
|
let _atscript_db_agg = require("@atscript/db/agg");
|
|
4
4
|
let _atscript_db_sql_tools = require("@atscript/db-sql-tools");
|
|
5
|
+
let _uniqu_core = require("@uniqu/core");
|
|
5
6
|
//#region src/sql-builder.ts
|
|
6
7
|
/**
|
|
7
8
|
* PostgreSQL-aware default value for a given design type.
|
|
@@ -76,12 +77,44 @@ const pgDialect = {
|
|
|
76
77
|
]
|
|
77
78
|
};
|
|
78
79
|
},
|
|
80
|
+
calendarBucket: pgCalendarBucket,
|
|
79
81
|
createViewPrefix: "CREATE OR REPLACE VIEW",
|
|
80
82
|
paramPlaceholder(index) {
|
|
81
83
|
return `$${index}`;
|
|
82
84
|
}
|
|
83
85
|
};
|
|
84
86
|
/**
|
|
87
|
+
* Calendar-bucket label over an epoch-ms column (BIGINT for
|
|
88
|
+
* `@db.default.now`, DOUBLE PRECISION otherwise): TEXT `'YYYY-MM-DD'` — the
|
|
89
|
+
* local date of the bucket's first day in `b.tz` — or NULL for a NULL source
|
|
90
|
+
* or one outside `[BUCKET_MIN_INSTANT, BUCKET_MAX_INSTANT)`.
|
|
91
|
+
*
|
|
92
|
+
* The local date is `(to_timestamp(col / 1000) AT TIME ZONE '<tz>')::date`:
|
|
93
|
+
* `timestamptz AT TIME ZONE` yields the wall time in that zone, independent
|
|
94
|
+
* of the session `TimeZone`. Truncation is calendar arithmetic on that date —
|
|
95
|
+
* the week uses the generic `(isodow - weekStart + 7) % 7` formula
|
|
96
|
+
* (`date_trunc('week')` is Monday-only). `date_trunc` and `to_char` get an
|
|
97
|
+
* explicit `::timestamp`: a bare `date` resolves to their `timestamptz`
|
|
98
|
+
* overloads, which would route through the session zone.
|
|
99
|
+
*
|
|
100
|
+
* Parameter-free (the zone is an inlined, charset-checked literal), so the
|
|
101
|
+
* SELECT and GROUP BY renderings match structurally.
|
|
102
|
+
*/
|
|
103
|
+
function pgCalendarBucket(quotedCol, b) {
|
|
104
|
+
const local = `(to_timestamp(${quotedCol}::double precision / 1000) AT TIME ZONE ${(0, _atscript_db_sql_tools.sqlTimeZoneLiteral)(b.tz)})::date`;
|
|
105
|
+
let first;
|
|
106
|
+
switch (b.unit) {
|
|
107
|
+
case "day":
|
|
108
|
+
first = local;
|
|
109
|
+
break;
|
|
110
|
+
case "week":
|
|
111
|
+
first = `(${local} - ((EXTRACT(ISODOW FROM ${local})::int - ${b.weekStartIso} + 7) % 7))`;
|
|
112
|
+
break;
|
|
113
|
+
default: first = `date_trunc('${b.unit}', ${local}::timestamp)`;
|
|
114
|
+
}
|
|
115
|
+
return `CASE WHEN ${quotedCol} >= ${_uniqu_core.BUCKET_MIN_INSTANT} AND ${quotedCol} < ${_uniqu_core.BUCKET_MAX_INSTANT} THEN ${`to_char(${first}::timestamp, 'YYYY-MM-DD')`} END`;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
85
118
|
* Distance-in-meters expression from a geography column to a query point.
|
|
86
119
|
* Used as the `distExpr` of the shared geo search builder.
|
|
87
120
|
*/
|
|
@@ -113,6 +146,10 @@ function parseEwkbPointHex(hex) {
|
|
|
113
146
|
function buildInsert(table, data) {
|
|
114
147
|
return (0, _atscript_db_sql_tools.buildInsert)(pgDialect, table, data);
|
|
115
148
|
}
|
|
149
|
+
/** Multi-row INSERT over `columns` (a missing column → `DEFAULT`). */
|
|
150
|
+
function buildInsertMany(table, rows, columns) {
|
|
151
|
+
return (0, _atscript_db_sql_tools.buildInsertMany)(pgDialect, table, rows, columns);
|
|
152
|
+
}
|
|
116
153
|
function buildSelect(table, where, controls) {
|
|
117
154
|
return (0, _atscript_db_sql_tools.buildSelect)(pgDialect, table, where, controls);
|
|
118
155
|
}
|
|
@@ -428,6 +465,13 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
428
465
|
nativeDefaultFns() {
|
|
429
466
|
return PostgresAdapter.NATIVE_DEFAULT_FNS;
|
|
430
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* Every unit: `pgCalendarBucket` renders them all over any zone in the
|
|
470
|
+
* server's tz database (an unknown one maps to `BUCKET_TZ_UNAVAILABLE`).
|
|
471
|
+
*/
|
|
472
|
+
calendarBucketUnits() {
|
|
473
|
+
return _atscript_db.ALL_BUCKET_UNITS;
|
|
474
|
+
}
|
|
431
475
|
onBeforeFlatten(_type) {}
|
|
432
476
|
onAfterFlatten() {
|
|
433
477
|
for (const fd of this._table.fieldDescriptors) if (fd.collate === "nocase") this._nocaseColumns.add(fd.physicalName);
|
|
@@ -545,31 +589,19 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
545
589
|
const tableName = this.resolveTableName();
|
|
546
590
|
const pkCols = this._table.primaryKeys;
|
|
547
591
|
const returningSuffix = pkCols.length > 0 ? ` RETURNING ${pkCols.map((pk) => qi(pk)).join(", ")}` : "";
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
const paramsPerRow = keys.length;
|
|
551
|
-
const maxRowsPerBatch = paramsPerRow > 0 ? Math.floor(6e4 / paramsPerRow) : data.length;
|
|
592
|
+
const columns = (0, _atscript_db_sql_tools.insertManyColumns)(data);
|
|
593
|
+
const maxRowsPerBatch = columns.length > 0 ? Math.floor(6e4 / columns.length) : data.length;
|
|
552
594
|
const allIds = [];
|
|
553
595
|
for (let offset = 0; offset < data.length; offset += maxRowsPerBatch) {
|
|
554
|
-
const
|
|
555
|
-
const
|
|
556
|
-
const
|
|
557
|
-
const
|
|
558
|
-
for (let i = offset; i < batchEnd; i++) {
|
|
559
|
-
const row = data[i];
|
|
560
|
-
const rowPlaceholders = [];
|
|
561
|
-
for (const k of keys) {
|
|
562
|
-
params.push(pgDialect.toValue(row[k]));
|
|
563
|
-
rowPlaceholders.push(`$${params.length}`);
|
|
564
|
-
}
|
|
565
|
-
valuesClauses.push(`(${rowPlaceholders.join(", ")})`);
|
|
566
|
-
}
|
|
567
|
-
const sql = `INSERT INTO ${quoteTableName(tableName)} (${colsClause}) VALUES ${valuesClauses.join(", ")}${returningSuffix}`;
|
|
596
|
+
const batch = data.slice(offset, offset + maxRowsPerBatch);
|
|
597
|
+
const insert = buildInsertMany(tableName, batch, columns);
|
|
598
|
+
const sql = insert.sql + returningSuffix;
|
|
599
|
+
const params = insert.params;
|
|
568
600
|
this._log(sql, params);
|
|
569
601
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
570
|
-
for (let i = 0; i <
|
|
602
|
+
for (let i = 0; i < batch.length; i++) {
|
|
571
603
|
const returned = result.rows?.[i];
|
|
572
|
-
allIds.push(this._resolveInsertedId(
|
|
604
|
+
allIds.push(this._resolveInsertedId(batch[i], returned ? Object.values(returned)[0] : void 0));
|
|
573
605
|
}
|
|
574
606
|
}
|
|
575
607
|
return {
|
|
@@ -610,11 +642,28 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
610
642
|
if (query.controls.$count) {
|
|
611
643
|
const { sql, params } = buildAggregateCount(tableName, where, query.controls);
|
|
612
644
|
this._log(sql, params);
|
|
613
|
-
return [{ count: parseCount((await this._exec().get(sql, params))?.count) }];
|
|
645
|
+
return [{ count: parseCount((await this._wrapBucketZoneError(() => this._exec().get(sql, params)))?.count) }];
|
|
614
646
|
}
|
|
615
647
|
const { sql, params } = buildAggregateSelect(tableName, where, query.controls);
|
|
616
648
|
this._log(sql, params);
|
|
617
|
-
return this._exec().all(sql, params);
|
|
649
|
+
return this._wrapBucketZoneError(() => this._exec().all(sql, params));
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Maps PostgreSQL's unknown-zone error — SQLSTATE 22023
|
|
653
|
+
* (`invalid_parameter_value`), `time zone "…" not recognized` — raised by a
|
|
654
|
+
* calendar bucket's `AT TIME ZONE '<tz>'` to `BUCKET_TZ_UNAVAILABLE`. The
|
|
655
|
+
* zone passed the core's IANA validation, so the server's tz database is
|
|
656
|
+
* older than the runtime's. Other errors propagate unchanged.
|
|
657
|
+
*/
|
|
658
|
+
async _wrapBucketZoneError(fn) {
|
|
659
|
+
try {
|
|
660
|
+
return await fn();
|
|
661
|
+
} catch (error) {
|
|
662
|
+
const err = error;
|
|
663
|
+
const zone = err?.code === "22023" && typeof err.message === "string" ? /time zone "([^"]*)" not recognized/.exec(err.message)?.[1] : void 0;
|
|
664
|
+
if (zone !== void 0) throw (0, _atscript_db.bucketTimeZoneUnavailable)(`PostgreSQL does not recognize time zone "${zone}" — update the server's time zone data`);
|
|
665
|
+
throw error;
|
|
666
|
+
}
|
|
618
667
|
}
|
|
619
668
|
async updateOne(filter, data, ops, expectedVersion) {
|
|
620
669
|
const where = buildWhere(filter);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TPrimaryKeyChange, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
|
|
1
|
+
import { BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TPrimaryKeyChange, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
|
|
2
2
|
import { TMetadataMap } from "@atscript/typescript/utils";
|
|
3
3
|
import { TSqlFragment } from "@atscript/db-sql-tools";
|
|
4
4
|
|
|
@@ -113,6 +113,11 @@ declare class PostgresAdapter extends BaseDbAdapter {
|
|
|
113
113
|
prepareId(id: unknown, _fieldType: unknown): unknown;
|
|
114
114
|
supportsNativeValueDefaults(): boolean;
|
|
115
115
|
nativeDefaultFns(): ReadonlySet<TDbDefaultFn>;
|
|
116
|
+
/**
|
|
117
|
+
* Every unit: `pgCalendarBucket` renders them all over any zone in the
|
|
118
|
+
* server's tz database (an unknown one maps to `BUCKET_TZ_UNAVAILABLE`).
|
|
119
|
+
*/
|
|
120
|
+
calendarBucketUnits(): ReadonlySet<BucketUnit>;
|
|
116
121
|
onBeforeFlatten(_type: unknown): void;
|
|
117
122
|
onAfterFlatten(): void;
|
|
118
123
|
onFieldScanned(field: string, _type: unknown, metadata: TMetadataMap<AtscriptMetadata>): void;
|
|
@@ -141,6 +146,14 @@ declare class PostgresAdapter extends BaseDbAdapter {
|
|
|
141
146
|
findMany(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
142
147
|
count(query: DbQuery): Promise<number>;
|
|
143
148
|
aggregate(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
149
|
+
/**
|
|
150
|
+
* Maps PostgreSQL's unknown-zone error — SQLSTATE 22023
|
|
151
|
+
* (`invalid_parameter_value`), `time zone "…" not recognized` — raised by a
|
|
152
|
+
* calendar bucket's `AT TIME ZONE '<tz>'` to `BUCKET_TZ_UNAVAILABLE`. The
|
|
153
|
+
* zone passed the core's IANA validation, so the server's tz database is
|
|
154
|
+
* older than the runtime's. Other errors propagate unchanged.
|
|
155
|
+
*/
|
|
156
|
+
private _wrapBucketZoneError;
|
|
144
157
|
updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
145
158
|
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
146
159
|
replaceOne(filter: FilterExpr, data: Record<string, unknown>, expectedVersion?: number): Promise<TDbUpdateResult>;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TPrimaryKeyChange, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
|
|
1
|
+
import { BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TPrimaryKeyChange, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
|
|
2
2
|
import { TSqlFragment } from "@atscript/db-sql-tools";
|
|
3
3
|
import { TMetadataMap } from "@atscript/typescript/utils";
|
|
4
4
|
|
|
@@ -113,6 +113,11 @@ declare class PostgresAdapter extends BaseDbAdapter {
|
|
|
113
113
|
prepareId(id: unknown, _fieldType: unknown): unknown;
|
|
114
114
|
supportsNativeValueDefaults(): boolean;
|
|
115
115
|
nativeDefaultFns(): ReadonlySet<TDbDefaultFn>;
|
|
116
|
+
/**
|
|
117
|
+
* Every unit: `pgCalendarBucket` renders them all over any zone in the
|
|
118
|
+
* server's tz database (an unknown one maps to `BUCKET_TZ_UNAVAILABLE`).
|
|
119
|
+
*/
|
|
120
|
+
calendarBucketUnits(): ReadonlySet<BucketUnit>;
|
|
116
121
|
onBeforeFlatten(_type: unknown): void;
|
|
117
122
|
onAfterFlatten(): void;
|
|
118
123
|
onFieldScanned(field: string, _type: unknown, metadata: TMetadataMap<AtscriptMetadata>): void;
|
|
@@ -141,6 +146,14 @@ declare class PostgresAdapter extends BaseDbAdapter {
|
|
|
141
146
|
findMany(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
142
147
|
count(query: DbQuery): Promise<number>;
|
|
143
148
|
aggregate(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
149
|
+
/**
|
|
150
|
+
* Maps PostgreSQL's unknown-zone error — SQLSTATE 22023
|
|
151
|
+
* (`invalid_parameter_value`), `time zone "…" not recognized` — raised by a
|
|
152
|
+
* calendar bucket's `AT TIME ZONE '<tz>'` to `BUCKET_TZ_UNAVAILABLE`. The
|
|
153
|
+
* zone passed the core's IANA validation, so the server's tz database is
|
|
154
|
+
* older than the runtime's. Other errors propagate unchanged.
|
|
155
|
+
*/
|
|
156
|
+
private _wrapBucketZoneError;
|
|
144
157
|
updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
145
158
|
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
146
159
|
replaceOne(filter: FilterExpr, data: Record<string, unknown>, expectedVersion?: number): Promise<TDbUpdateResult>;
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
|
|
1
|
+
import { ALL_BUCKET_UNITS, BaseDbAdapter, DbError, DbSpace, bucketTimeZoneUnavailable } from "@atscript/db";
|
|
2
2
|
import { resolveAggregateSearch } from "@atscript/db/agg";
|
|
3
|
-
import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, fillReplacePayload, finalizeParams, geoWindowFromControls, normalizeGeoPointValue, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor } from "@atscript/db-sql-tools";
|
|
3
|
+
import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, fillReplacePayload, finalizeParams, geoWindowFromControls, insertManyColumns, normalizeGeoPointValue, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlTimeZoneLiteral } from "@atscript/db-sql-tools";
|
|
4
|
+
import { BUCKET_MAX_INSTANT, BUCKET_MIN_INSTANT } from "@uniqu/core";
|
|
4
5
|
//#region src/sql-builder.ts
|
|
5
6
|
/**
|
|
6
7
|
* PostgreSQL-aware default value for a given design type.
|
|
@@ -75,12 +76,44 @@ const pgDialect = {
|
|
|
75
76
|
]
|
|
76
77
|
};
|
|
77
78
|
},
|
|
79
|
+
calendarBucket: pgCalendarBucket,
|
|
78
80
|
createViewPrefix: "CREATE OR REPLACE VIEW",
|
|
79
81
|
paramPlaceholder(index) {
|
|
80
82
|
return `$${index}`;
|
|
81
83
|
}
|
|
82
84
|
};
|
|
83
85
|
/**
|
|
86
|
+
* Calendar-bucket label over an epoch-ms column (BIGINT for
|
|
87
|
+
* `@db.default.now`, DOUBLE PRECISION otherwise): TEXT `'YYYY-MM-DD'` — the
|
|
88
|
+
* local date of the bucket's first day in `b.tz` — or NULL for a NULL source
|
|
89
|
+
* or one outside `[BUCKET_MIN_INSTANT, BUCKET_MAX_INSTANT)`.
|
|
90
|
+
*
|
|
91
|
+
* The local date is `(to_timestamp(col / 1000) AT TIME ZONE '<tz>')::date`:
|
|
92
|
+
* `timestamptz AT TIME ZONE` yields the wall time in that zone, independent
|
|
93
|
+
* of the session `TimeZone`. Truncation is calendar arithmetic on that date —
|
|
94
|
+
* the week uses the generic `(isodow - weekStart + 7) % 7` formula
|
|
95
|
+
* (`date_trunc('week')` is Monday-only). `date_trunc` and `to_char` get an
|
|
96
|
+
* explicit `::timestamp`: a bare `date` resolves to their `timestamptz`
|
|
97
|
+
* overloads, which would route through the session zone.
|
|
98
|
+
*
|
|
99
|
+
* Parameter-free (the zone is an inlined, charset-checked literal), so the
|
|
100
|
+
* SELECT and GROUP BY renderings match structurally.
|
|
101
|
+
*/
|
|
102
|
+
function pgCalendarBucket(quotedCol, b) {
|
|
103
|
+
const local = `(to_timestamp(${quotedCol}::double precision / 1000) AT TIME ZONE ${sqlTimeZoneLiteral(b.tz)})::date`;
|
|
104
|
+
let first;
|
|
105
|
+
switch (b.unit) {
|
|
106
|
+
case "day":
|
|
107
|
+
first = local;
|
|
108
|
+
break;
|
|
109
|
+
case "week":
|
|
110
|
+
first = `(${local} - ((EXTRACT(ISODOW FROM ${local})::int - ${b.weekStartIso} + 7) % 7))`;
|
|
111
|
+
break;
|
|
112
|
+
default: first = `date_trunc('${b.unit}', ${local}::timestamp)`;
|
|
113
|
+
}
|
|
114
|
+
return `CASE WHEN ${quotedCol} >= ${BUCKET_MIN_INSTANT} AND ${quotedCol} < ${BUCKET_MAX_INSTANT} THEN ${`to_char(${first}::timestamp, 'YYYY-MM-DD')`} END`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
84
117
|
* Distance-in-meters expression from a geography column to a query point.
|
|
85
118
|
* Used as the `distExpr` of the shared geo search builder.
|
|
86
119
|
*/
|
|
@@ -112,6 +145,10 @@ function parseEwkbPointHex(hex) {
|
|
|
112
145
|
function buildInsert$1(table, data) {
|
|
113
146
|
return buildInsert(pgDialect, table, data);
|
|
114
147
|
}
|
|
148
|
+
/** Multi-row INSERT over `columns` (a missing column → `DEFAULT`). */
|
|
149
|
+
function buildInsertMany$1(table, rows, columns) {
|
|
150
|
+
return buildInsertMany(pgDialect, table, rows, columns);
|
|
151
|
+
}
|
|
115
152
|
function buildSelect$1(table, where, controls) {
|
|
116
153
|
return buildSelect(pgDialect, table, where, controls);
|
|
117
154
|
}
|
|
@@ -427,6 +464,13 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
427
464
|
nativeDefaultFns() {
|
|
428
465
|
return PostgresAdapter.NATIVE_DEFAULT_FNS;
|
|
429
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* Every unit: `pgCalendarBucket` renders them all over any zone in the
|
|
469
|
+
* server's tz database (an unknown one maps to `BUCKET_TZ_UNAVAILABLE`).
|
|
470
|
+
*/
|
|
471
|
+
calendarBucketUnits() {
|
|
472
|
+
return ALL_BUCKET_UNITS;
|
|
473
|
+
}
|
|
430
474
|
onBeforeFlatten(_type) {}
|
|
431
475
|
onAfterFlatten() {
|
|
432
476
|
for (const fd of this._table.fieldDescriptors) if (fd.collate === "nocase") this._nocaseColumns.add(fd.physicalName);
|
|
@@ -544,31 +588,19 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
544
588
|
const tableName = this.resolveTableName();
|
|
545
589
|
const pkCols = this._table.primaryKeys;
|
|
546
590
|
const returningSuffix = pkCols.length > 0 ? ` RETURNING ${pkCols.map((pk) => qi(pk)).join(", ")}` : "";
|
|
547
|
-
const
|
|
548
|
-
const
|
|
549
|
-
const paramsPerRow = keys.length;
|
|
550
|
-
const maxRowsPerBatch = paramsPerRow > 0 ? Math.floor(6e4 / paramsPerRow) : data.length;
|
|
591
|
+
const columns = insertManyColumns(data);
|
|
592
|
+
const maxRowsPerBatch = columns.length > 0 ? Math.floor(6e4 / columns.length) : data.length;
|
|
551
593
|
const allIds = [];
|
|
552
594
|
for (let offset = 0; offset < data.length; offset += maxRowsPerBatch) {
|
|
553
|
-
const
|
|
554
|
-
const
|
|
555
|
-
const
|
|
556
|
-
const
|
|
557
|
-
for (let i = offset; i < batchEnd; i++) {
|
|
558
|
-
const row = data[i];
|
|
559
|
-
const rowPlaceholders = [];
|
|
560
|
-
for (const k of keys) {
|
|
561
|
-
params.push(pgDialect.toValue(row[k]));
|
|
562
|
-
rowPlaceholders.push(`$${params.length}`);
|
|
563
|
-
}
|
|
564
|
-
valuesClauses.push(`(${rowPlaceholders.join(", ")})`);
|
|
565
|
-
}
|
|
566
|
-
const sql = `INSERT INTO ${quoteTableName(tableName)} (${colsClause}) VALUES ${valuesClauses.join(", ")}${returningSuffix}`;
|
|
595
|
+
const batch = data.slice(offset, offset + maxRowsPerBatch);
|
|
596
|
+
const insert = buildInsertMany$1(tableName, batch, columns);
|
|
597
|
+
const sql = insert.sql + returningSuffix;
|
|
598
|
+
const params = insert.params;
|
|
567
599
|
this._log(sql, params);
|
|
568
600
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
569
|
-
for (let i = 0; i <
|
|
601
|
+
for (let i = 0; i < batch.length; i++) {
|
|
570
602
|
const returned = result.rows?.[i];
|
|
571
|
-
allIds.push(this._resolveInsertedId(
|
|
603
|
+
allIds.push(this._resolveInsertedId(batch[i], returned ? Object.values(returned)[0] : void 0));
|
|
572
604
|
}
|
|
573
605
|
}
|
|
574
606
|
return {
|
|
@@ -609,11 +641,28 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
609
641
|
if (query.controls.$count) {
|
|
610
642
|
const { sql, params } = buildAggregateCount$1(tableName, where, query.controls);
|
|
611
643
|
this._log(sql, params);
|
|
612
|
-
return [{ count: parseCount((await this._exec().get(sql, params))?.count) }];
|
|
644
|
+
return [{ count: parseCount((await this._wrapBucketZoneError(() => this._exec().get(sql, params)))?.count) }];
|
|
613
645
|
}
|
|
614
646
|
const { sql, params } = buildAggregateSelect$1(tableName, where, query.controls);
|
|
615
647
|
this._log(sql, params);
|
|
616
|
-
return this._exec().all(sql, params);
|
|
648
|
+
return this._wrapBucketZoneError(() => this._exec().all(sql, params));
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Maps PostgreSQL's unknown-zone error — SQLSTATE 22023
|
|
652
|
+
* (`invalid_parameter_value`), `time zone "…" not recognized` — raised by a
|
|
653
|
+
* calendar bucket's `AT TIME ZONE '<tz>'` to `BUCKET_TZ_UNAVAILABLE`. The
|
|
654
|
+
* zone passed the core's IANA validation, so the server's tz database is
|
|
655
|
+
* older than the runtime's. Other errors propagate unchanged.
|
|
656
|
+
*/
|
|
657
|
+
async _wrapBucketZoneError(fn) {
|
|
658
|
+
try {
|
|
659
|
+
return await fn();
|
|
660
|
+
} catch (error) {
|
|
661
|
+
const err = error;
|
|
662
|
+
const zone = err?.code === "22023" && typeof err.message === "string" ? /time zone "([^"]*)" not recognized/.exec(err.message)?.[1] : void 0;
|
|
663
|
+
if (zone !== void 0) throw bucketTimeZoneUnavailable(`PostgreSQL does not recognize time zone "${zone}" — update the server's time zone data`);
|
|
664
|
+
throw error;
|
|
665
|
+
}
|
|
617
666
|
}
|
|
618
667
|
async updateOne(filter, data, ops, expectedVersion) {
|
|
619
668
|
const where = buildWhere(filter);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-postgres",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.132",
|
|
4
4
|
"description": "PostgreSQL adapter for @atscript/db with pg (node-postgres) driver support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -46,17 +46,17 @@
|
|
|
46
46
|
"@atscript/core": "^0.1.92",
|
|
47
47
|
"@atscript/typescript": "^0.1.92",
|
|
48
48
|
"@types/pg": "^8.11.0",
|
|
49
|
-
"@uniqu/core": "^0.1.
|
|
49
|
+
"@uniqu/core": "^0.1.9",
|
|
50
50
|
"pg": "^8.13.0",
|
|
51
51
|
"unplugin-atscript": "^0.1.92"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"@atscript/core": "^0.1.92",
|
|
55
55
|
"@atscript/typescript": "^0.1.92",
|
|
56
|
-
"@uniqu/core": "^0.1.
|
|
56
|
+
"@uniqu/core": "^0.1.9",
|
|
57
57
|
"pg": ">=8.0.0",
|
|
58
|
-
"@atscript/db": "^0.1.
|
|
59
|
-
"@atscript/db-sql-tools": "^0.1.
|
|
58
|
+
"@atscript/db": "^0.1.132",
|
|
59
|
+
"@atscript/db-sql-tools": "^0.1.132"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
62
62
|
"pg": {
|