@happyvertical/sql 0.89.8 → 0.89.9
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 +16 -0
- package/dist/index.js +65 -14
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -298,6 +298,11 @@ values so nullable composite keys update the existing row instead of inserting a
|
|
|
298
298
|
duplicate. Pass `{ nullsDistinct: true }` as the fourth argument to preserve the
|
|
299
299
|
database-native behavior where `NULL` conflict values are distinct.
|
|
300
300
|
|
|
301
|
+
For PostgreSQL 15+, a matching `UNIQUE (...) NULLS NOT DISTINCT` index lets
|
|
302
|
+
nullable upserts use one native `ON CONFLICT` statement. An ordinary `UNIQUE`
|
|
303
|
+
constraint still treats `NULL` values as distinct, so the adapter retains its
|
|
304
|
+
race-safe NULL-aware fallback for that schema.
|
|
305
|
+
|
|
301
306
|
### Transactions
|
|
302
307
|
|
|
303
308
|
```typescript
|
|
@@ -525,6 +530,17 @@ On PostgreSQL, `syncSchema()` recognizes `CREATE [UNIQUE] INDEX CONCURRENTLY
|
|
|
525
530
|
[IF NOT EXISTS]` statements, including optional `USING` index methods, and
|
|
526
531
|
skips indexes that already exist when a schema is applied again.
|
|
527
532
|
|
|
533
|
+
PostgreSQL `tableExists()`, `getTableSchema()`, and `syncSchema()` resolve an
|
|
534
|
+
unqualified table name through the executing connection's `search_path`, so a
|
|
535
|
+
non-`public` schema can be used without changing the API. Root PostgreSQL
|
|
536
|
+
helpers use a pool: do not issue `SET search_path` on one root call and assume
|
|
537
|
+
later root helpers will use that same session. Configure the path for every
|
|
538
|
+
pool connection, or keep transaction-scoped schema work on the supplied `tx`
|
|
539
|
+
handle with `SET LOCAL search_path` before calling `tx.tableExists()` or
|
|
540
|
+
`tx.syncSchema()`. `acquireSession()` pins raw PostgreSQL queries to one
|
|
541
|
+
connection for session-scoped state; root metadata helpers are not part of
|
|
542
|
+
that raw session handle.
|
|
543
|
+
|
|
528
544
|
### Vector Search (PostgreSQL)
|
|
529
545
|
|
|
530
546
|
PostgreSQL adapters expose `db.vector` when pgvector is available:
|
package/dist/index.js
CHANGED
|
@@ -3089,7 +3089,18 @@ async function createDatabase(config) {
|
|
|
3089
3089
|
* @returns Promise resolving to boolean indicating if the table exists
|
|
3090
3090
|
*/
|
|
3091
3091
|
const tableExists = async (tableName) => {
|
|
3092
|
-
return (await executor.query(`SELECT EXISTS (
|
|
3092
|
+
return (await executor.query(`SELECT EXISTS (
|
|
3093
|
+
SELECT 1
|
|
3094
|
+
FROM information_schema.tables
|
|
3095
|
+
WHERE table_name = $1
|
|
3096
|
+
AND table_schema = (
|
|
3097
|
+
SELECT namespace.nspname
|
|
3098
|
+
FROM pg_catalog.pg_class AS relation
|
|
3099
|
+
JOIN pg_catalog.pg_namespace AS namespace
|
|
3100
|
+
ON namespace.oid = relation.relnamespace
|
|
3101
|
+
WHERE relation.oid = pg_catalog.to_regclass(pg_catalog.quote_ident($1))
|
|
3102
|
+
)
|
|
3103
|
+
)`, [tableName])).rows[0].exists;
|
|
3093
3104
|
};
|
|
3094
3105
|
/**
|
|
3095
3106
|
* Synchronizes database schema with provided SQL DDL
|
|
@@ -3138,16 +3149,25 @@ async function createDatabase(config) {
|
|
|
3138
3149
|
tableName
|
|
3139
3150
|
} : null;
|
|
3140
3151
|
};
|
|
3141
|
-
const
|
|
3152
|
+
const indexTargets = [];
|
|
3142
3153
|
for (const command of commands) {
|
|
3143
3154
|
const indexMatch = parseCreateIndex(command.trim());
|
|
3144
|
-
if (indexMatch)
|
|
3155
|
+
if (indexMatch) indexTargets.push(indexMatch);
|
|
3145
3156
|
}
|
|
3146
3157
|
const existingIndexes = /* @__PURE__ */ new Set();
|
|
3147
|
-
if (
|
|
3148
|
-
const result = await executor.query(`SELECT
|
|
3149
|
-
|
|
3150
|
-
|
|
3158
|
+
if (indexTargets.length > 0) {
|
|
3159
|
+
const result = await executor.query(`SELECT target.table_name, target.index_name
|
|
3160
|
+
FROM unnest($1::text[], $2::text[])
|
|
3161
|
+
AS target(table_name, index_name)
|
|
3162
|
+
JOIN pg_catalog.pg_class AS relation
|
|
3163
|
+
ON relation.oid = pg_catalog.to_regclass(pg_catalog.quote_ident(target.table_name))
|
|
3164
|
+
JOIN pg_catalog.pg_namespace AS namespace
|
|
3165
|
+
ON namespace.oid = relation.relnamespace
|
|
3166
|
+
JOIN pg_catalog.pg_indexes AS existing
|
|
3167
|
+
ON existing.schemaname = namespace.nspname
|
|
3168
|
+
AND existing.tablename = target.table_name
|
|
3169
|
+
AND existing.indexname = target.index_name`, [indexTargets.map((target) => target.tableName), indexTargets.map((target) => target.indexName)]);
|
|
3170
|
+
for (const row of result.rows) existingIndexes.add(`${row.table_name}\u0000${row.index_name}`);
|
|
3151
3171
|
}
|
|
3152
3172
|
for (const command of commands) {
|
|
3153
3173
|
const trimmedCommand = command.trim();
|
|
@@ -3170,7 +3190,13 @@ async function createDatabase(config) {
|
|
|
3170
3190
|
SELECT 1 FROM information_schema.columns
|
|
3171
3191
|
WHERE table_name = $1
|
|
3172
3192
|
AND column_name = $2
|
|
3173
|
-
AND table_schema =
|
|
3193
|
+
AND table_schema = (
|
|
3194
|
+
SELECT namespace.nspname
|
|
3195
|
+
FROM pg_catalog.pg_class AS relation
|
|
3196
|
+
JOIN pg_catalog.pg_namespace AS namespace
|
|
3197
|
+
ON namespace.oid = relation.relnamespace
|
|
3198
|
+
WHERE relation.oid = pg_catalog.to_regclass(pg_catalog.quote_ident($1))
|
|
3199
|
+
)
|
|
3174
3200
|
)`, [tableName, columnName])).rows[0].exists) {
|
|
3175
3201
|
const alterCommand = `ALTER TABLE "${tableName}" ADD COLUMN ${columnDef}`;
|
|
3176
3202
|
await executor.query(alterCommand);
|
|
@@ -3186,9 +3212,10 @@ async function createDatabase(config) {
|
|
|
3186
3212
|
const indexMatch = parseCreateIndex(trimmedCommand);
|
|
3187
3213
|
if (indexMatch) {
|
|
3188
3214
|
const { indexName, tableName: indexTableName } = indexMatch;
|
|
3189
|
-
|
|
3215
|
+
const indexKey = `${indexTableName}\u0000${indexName}`;
|
|
3216
|
+
if (!existingIndexes.has(indexKey)) try {
|
|
3190
3217
|
await runTolerated(() => executor.query(trimmedCommand));
|
|
3191
|
-
existingIndexes.add(
|
|
3218
|
+
existingIndexes.add(indexKey);
|
|
3192
3219
|
} catch (error) {
|
|
3193
3220
|
console.warn(`Warning: Failed to create index ${indexName} on ${indexTableName}:`, error instanceof Error ? error.message : String(error));
|
|
3194
3221
|
}
|
|
@@ -3468,7 +3495,13 @@ async function createDatabase(config) {
|
|
|
3468
3495
|
column_default
|
|
3469
3496
|
FROM information_schema.columns
|
|
3470
3497
|
WHERE table_name = ${table}
|
|
3471
|
-
AND table_schema =
|
|
3498
|
+
AND table_schema = (
|
|
3499
|
+
SELECT namespace.nspname
|
|
3500
|
+
FROM pg_catalog.pg_class AS relation
|
|
3501
|
+
JOIN pg_catalog.pg_namespace AS namespace
|
|
3502
|
+
ON namespace.oid = relation.relnamespace
|
|
3503
|
+
WHERE relation.oid = pg_catalog.to_regclass(pg_catalog.quote_ident(${table}))
|
|
3504
|
+
)
|
|
3472
3505
|
ORDER BY ordinal_position
|
|
3473
3506
|
`;
|
|
3474
3507
|
const pkRows = await many`
|
|
@@ -3478,7 +3511,13 @@ async function createDatabase(config) {
|
|
|
3478
3511
|
ON tc.constraint_name = kcu.constraint_name
|
|
3479
3512
|
AND tc.table_schema = kcu.table_schema
|
|
3480
3513
|
WHERE tc.table_name = ${table}
|
|
3481
|
-
AND tc.table_schema =
|
|
3514
|
+
AND tc.table_schema = (
|
|
3515
|
+
SELECT namespace.nspname
|
|
3516
|
+
FROM pg_catalog.pg_class AS relation
|
|
3517
|
+
JOIN pg_catalog.pg_namespace AS namespace
|
|
3518
|
+
ON namespace.oid = relation.relnamespace
|
|
3519
|
+
WHERE relation.oid = pg_catalog.to_regclass(pg_catalog.quote_ident(${table}))
|
|
3520
|
+
)
|
|
3482
3521
|
AND tc.constraint_type = 'PRIMARY KEY'
|
|
3483
3522
|
`;
|
|
3484
3523
|
const pkColumns = new Set(pkRows.map((row) => row.column_name));
|
|
@@ -3498,7 +3537,13 @@ async function createDatabase(config) {
|
|
|
3498
3537
|
indexdef
|
|
3499
3538
|
FROM pg_indexes
|
|
3500
3539
|
WHERE tablename = ${table}
|
|
3501
|
-
AND schemaname =
|
|
3540
|
+
AND schemaname = (
|
|
3541
|
+
SELECT namespace.nspname
|
|
3542
|
+
FROM pg_catalog.pg_class AS relation
|
|
3543
|
+
JOIN pg_catalog.pg_namespace AS namespace
|
|
3544
|
+
ON namespace.oid = relation.relnamespace
|
|
3545
|
+
WHERE relation.oid = pg_catalog.to_regclass(pg_catalog.quote_ident(${table}))
|
|
3546
|
+
)
|
|
3502
3547
|
AND indexname NOT LIKE '%_pkey'
|
|
3503
3548
|
`;
|
|
3504
3549
|
const indexes = [];
|
|
@@ -3533,7 +3578,13 @@ async function createDatabase(config) {
|
|
|
3533
3578
|
AND rc.constraint_schema = tc.table_schema
|
|
3534
3579
|
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
3535
3580
|
AND tc.table_name = ${table}
|
|
3536
|
-
AND tc.table_schema =
|
|
3581
|
+
AND tc.table_schema = (
|
|
3582
|
+
SELECT namespace.nspname
|
|
3583
|
+
FROM pg_catalog.pg_class AS relation
|
|
3584
|
+
JOIN pg_catalog.pg_namespace AS namespace
|
|
3585
|
+
ON namespace.oid = relation.relnamespace
|
|
3586
|
+
WHERE relation.oid = pg_catalog.to_regclass(pg_catalog.quote_ident(${table}))
|
|
3587
|
+
)
|
|
3537
3588
|
`;
|
|
3538
3589
|
const foreignKeys = [];
|
|
3539
3590
|
for (const fkRow of fkRows) foreignKeys.push({
|