@happyvertical/sql 0.89.7 → 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 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 (SELECT 1 FROM information_schema.tables WHERE table_name = $1 AND table_schema = 'public')`, [tableName])).rows[0].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 indexNames = [];
3152
+ const indexTargets = [];
3142
3153
  for (const command of commands) {
3143
3154
  const indexMatch = parseCreateIndex(command.trim());
3144
- if (indexMatch) indexNames.push(indexMatch.indexName);
3155
+ if (indexMatch) indexTargets.push(indexMatch);
3145
3156
  }
3146
3157
  const existingIndexes = /* @__PURE__ */ new Set();
3147
- if (indexNames.length > 0) {
3148
- const result = await executor.query(`SELECT indexname FROM pg_indexes
3149
- WHERE schemaname = 'public' AND indexname = ANY($1::text[])`, [indexNames]);
3150
- for (const row of result.rows) existingIndexes.add(row.indexname);
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 = 'public'
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
- if (!existingIndexes.has(indexName)) try {
3215
+ const indexKey = `${indexTableName}\u0000${indexName}`;
3216
+ if (!existingIndexes.has(indexKey)) try {
3190
3217
  await runTolerated(() => executor.query(trimmedCommand));
3191
- existingIndexes.add(indexName);
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 = 'public'
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 = 'public'
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 = 'public'
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 = 'public'
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({