orez 0.4.46 → 0.4.48

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.
@@ -49,7 +49,9 @@ export declare class DoBackend {
49
49
  private ensureMetadataTable;
50
50
  private publicationToJSON;
51
51
  private publicationFromJSON;
52
+ private hydrateSchemaMetadataRow;
52
53
  private loadDurableMetadata;
54
+ private reloadSchemaMetadataIfEmpty;
53
55
  private reloadPublicationsIfEmpty;
54
56
  private repairShardMetadataPublications;
55
57
  private persistDurableMetadata;
@@ -129,6 +131,7 @@ export declare class DoBackend {
129
131
  private generatedIndexName;
130
132
  private uniqueIndexName;
131
133
  private sqliteIndexColumnsFromRows;
134
+ private promotedUniqueIndexKey;
132
135
  private tableIndexInfos;
133
136
  private publicationContainsTable;
134
137
  private publicationsForTable;
@@ -1 +1 @@
1
- {"version":3,"file":"pg-proxy-do-backend.d.ts","sourceRoot":"","sources":["../src/pg-proxy-do-backend.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pg-proxy-do-backend.d.ts","sourceRoot":"","sources":["../src/pg-proxy-do-backend.ts"],"names":[],"mappings":"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"}
@@ -4859,6 +4859,17 @@ export class DoBackend {
4859
4859
  return null;
4860
4860
  }
4861
4861
  }
4862
+ hydrateSchemaMetadataRow(tableName, columnName, value) {
4863
+ const metadata = JSON.parse(value);
4864
+ let table = this.schemaMetadata.get(tableName);
4865
+ if (!table) {
4866
+ table = new Map();
4867
+ this.schemaMetadata.set(tableName, table);
4868
+ }
4869
+ const changed = JSON.stringify(table.get(columnName)) !== value;
4870
+ table.set(columnName, metadata);
4871
+ return changed;
4872
+ }
4862
4873
  async loadDurableMetadata() {
4863
4874
  try {
4864
4875
  await this.ensureMetadataTable();
@@ -4871,13 +4882,7 @@ export class DoBackend {
4871
4882
  const value = String(row.value ?? '');
4872
4883
  this.lastPersistedMetadata.set(`${kind}\u0000${key}\u0000${subkey}`, value);
4873
4884
  if (kind === 'schema-column') {
4874
- const metadata = JSON.parse(value);
4875
- let table = this.schemaMetadata.get(key);
4876
- if (!table) {
4877
- table = new Map();
4878
- this.schemaMetadata.set(key, table);
4879
- }
4880
- table.set(subkey, metadata);
4885
+ this.hydrateSchemaMetadataRow(key, subkey, value);
4881
4886
  }
4882
4887
  else if (kind === 'publication') {
4883
4888
  const publication = this.publicationFromJSON(value);
@@ -4894,6 +4899,42 @@ export class DoBackend {
4894
4899
  }
4895
4900
  catch { }
4896
4901
  }
4902
+ // Schema migrations persist PostgreSQL type metadata directly into the
4903
+ // shared SQL DO, outside this backend instance. If this backend initialized
4904
+ // first, its one-time load cached an empty schema and requests then exposed
4905
+ // physical SQLite INTEGER/TEXT types to zero forever. Refresh before parsing
4906
+ // any request so prepared statements also get semantic parameter types. Do
4907
+ // not negative-cache an empty read: the migration may commit immediately
4908
+ // after it, which is the race this self-heal exists to close.
4909
+ async reloadSchemaMetadataIfEmpty() {
4910
+ if (this.dbName !== 'postgres' || this.schemaMetadata.size > 0)
4911
+ return;
4912
+ try {
4913
+ await this.ensureMetadataTable();
4914
+ const result = await this.doExecResult(`SELECT kind, key, subkey, value FROM ${quoteIdentifier(METADATA_TABLE)} WHERE kind = 'schema-column'`);
4915
+ let changed = false;
4916
+ const persisted = this.lastPersistedMetadata ?? new Map();
4917
+ this.lastPersistedMetadata = persisted;
4918
+ for (const row of result.rows) {
4919
+ const key = String(row.key ?? '');
4920
+ const subkey = String(row.subkey ?? '');
4921
+ const value = String(row.value ?? '');
4922
+ changed = this.hydrateSchemaMetadataRow(key, subkey, value) || changed;
4923
+ persisted.set(`schema-column\u0000${key}\u0000${subkey}`, value);
4924
+ }
4925
+ if (changed) {
4926
+ if (this.txSnapshot) {
4927
+ // The rows were committed out-of-band, not by this transaction. A
4928
+ // later ROLLBACK must preserve them rather than restoring the stale
4929
+ // empty snapshot captured before the migration finished.
4930
+ this.txSnapshot.schemaMetadata = this.cloneSchemaMetadata(this.schemaMetadata);
4931
+ }
4932
+ this.rewriteCache.clear();
4933
+ this.publicationTableInfoCache = null;
4934
+ }
4935
+ }
4936
+ catch { }
4937
+ }
4897
4938
  // self-heal a stale-empty publication cache. publications are durable
4898
4939
  // (_orez_pg_metadata) and SHARED across every DoBackend instance pointed at
4899
4940
  // one DO, but each instance only loads them once at init(). when this
@@ -5206,8 +5247,10 @@ export class DoBackend {
5206
5247
  try {
5207
5248
  switch (msgType) {
5208
5249
  case FT_QUERY:
5250
+ await this.reloadSchemaMetadataIfEmpty();
5209
5251
  return this.handleSimpleQuery(message);
5210
5252
  case FT_PARSE:
5253
+ await this.reloadSchemaMetadataIfEmpty();
5211
5254
  return this.handleParse(message);
5212
5255
  case FT_BIND:
5213
5256
  return this.handleBind(message);
@@ -5706,6 +5749,7 @@ export class DoBackend {
5706
5749
  async execLocked(sql) {
5707
5750
  if (!this.ready)
5708
5751
  await this.waitReady;
5752
+ await this.reloadSchemaMetadataIfEmpty();
5709
5753
  if (await this.handleTransactionControl(sql))
5710
5754
  return [];
5711
5755
  const statements = this.rewriteSQLStatements(sql);
@@ -5761,6 +5805,7 @@ export class DoBackend {
5761
5805
  async queryLocked(sql, params) {
5762
5806
  if (!this.ready)
5763
5807
  await this.waitReady;
5808
+ await this.reloadSchemaMetadataIfEmpty();
5764
5809
  if (await this.handleTransactionControl(sql))
5765
5810
  return { rows: [] };
5766
5811
  const statements = this.rewriteSQLStatements(sql);
@@ -6262,6 +6307,54 @@ export class DoBackend {
6262
6307
  columns[row.name] = row.desc === 1 ? 'DESC' : 'ASC';
6263
6308
  return columns;
6264
6309
  }
6310
+ // a table with no PRIMARY KEY but a full, non-partial UNIQUE index is keyed
6311
+ // by that index — the shape soot's DDL generator emits for composite drizzle
6312
+ // primaryKey() tables (CREATE TABLE without a PK + CREATE UNIQUE INDEX
6313
+ // <table>_pkey), because a PK cannot be retrofitted onto an existing sqlite
6314
+ // table. real pg conveys this key via replica identity; without promoting it
6315
+ // here, zero's initial sync builds a keyless replica spec and its change
6316
+ // processor throws "Cannot replicate table without a PRIMARY KEY or UNIQUE
6317
+ // INDEX" on the first UPDATE (2026-07-10 soot prod outage).
6318
+ //
6319
+ // the <table>_pkey name IS the generator's primary-key convention, so it is
6320
+ // trusted outright: legacy tables created before NOT NULL reached their DDL
6321
+ // have nullable physical columns (and possibly no durable metadata), yet the
6322
+ // index still represents the app-level composite PK — requiring NOT NULL
6323
+ // there would silently skip exactly the tables this promotion exists to
6324
+ // heal. any OTHER unique index must cover only NOT NULL columns (a unique
6325
+ // index over nullable columns permits duplicate NULL rows and is not a row
6326
+ // identity); among those, narrowest wins, name as the tiebreak.
6327
+ promotedUniqueIndexKey(sqliteTableName, columns, indexListRows, indexColumnsByName) {
6328
+ const columnNames = new Set(columns.map((column) => column.name));
6329
+ const notNull = new Set(columns
6330
+ .filter((column) => {
6331
+ const metadata = this.schemaMetadata.get(sqliteTableName)?.get(column.name);
6332
+ return metadata?.notNull || metadata?.primaryKey || column.notnull || column.pk;
6333
+ })
6334
+ .map((column) => column.name));
6335
+ const ref = this.tableRefForSqliteTable(sqliteTableName);
6336
+ const pkeyName = `${ref.tableName}_pkey`;
6337
+ const candidates = [];
6338
+ for (const row of indexListRows) {
6339
+ const name = String(row.name ?? '');
6340
+ if (!name || !Number(row.unique ?? 0) || Number(row.partial ?? 0))
6341
+ continue;
6342
+ const indexColumns = Object.keys(indexColumnsByName.get(name) ?? {});
6343
+ if (indexColumns.length === 0)
6344
+ continue;
6345
+ if (!indexColumns.every((column) => columnNames.has(column)))
6346
+ continue;
6347
+ if (name === pkeyName)
6348
+ return indexColumns;
6349
+ if (!indexColumns.every((column) => notNull.has(column)))
6350
+ continue;
6351
+ candidates.push({ name, columns: indexColumns });
6352
+ }
6353
+ if (candidates.length === 0)
6354
+ return [];
6355
+ candidates.sort((a, b) => a.columns.length - b.columns.length || a.name.localeCompare(b.name));
6356
+ return candidates[0].columns;
6357
+ }
6265
6358
  tableIndexInfos(table, columns, primaryKey, indexListRows, indexColumnsByName) {
6266
6359
  const usedNames = new Set();
6267
6360
  const seenSignatures = new Set();
@@ -6387,17 +6480,21 @@ export class DoBackend {
6387
6480
  const metadataPrimaryKey = columns
6388
6481
  .filter((column) => this.schemaMetadata.get(table.name)?.get(column.name)?.primaryKey)
6389
6482
  .map((column) => column.name);
6483
+ const sqlitePrimaryKey = columns
6484
+ .filter((column) => column.pk > 0)
6485
+ .sort((a, b) => a.pk - b.pk)
6486
+ .map((column) => column.name);
6487
+ const primaryKey = metadataPrimaryKey.length > 0
6488
+ ? metadataPrimaryKey
6489
+ : sqlitePrimaryKey.length > 0
6490
+ ? sqlitePrimaryKey
6491
+ : this.promotedUniqueIndexKey(table.name, columns, pragmaResults[i * 2 + 1]?.rows ?? [], indexColumnsByName);
6390
6492
  const info = {
6391
6493
  name: table.name,
6392
6494
  schema: ref.schema,
6393
6495
  tableName: ref.tableName,
6394
6496
  columns,
6395
- primaryKey: metadataPrimaryKey.length > 0
6396
- ? metadataPrimaryKey
6397
- : columns
6398
- .filter((column) => column.pk > 0)
6399
- .sort((a, b) => a.pk - b.pk)
6400
- .map((column) => column.name),
6497
+ primaryKey,
6401
6498
  indexes: [],
6402
6499
  };
6403
6500
  info.indexes = this.tableIndexInfos(ref, columns, info.primaryKey, pragmaResults[i * 2 + 1]?.rows ?? [], indexColumnsByName);
@@ -6606,7 +6703,16 @@ export class DoBackend {
6606
6703
  elemPgTypeClass: metadata?.elemTyptype ?? null,
6607
6704
  typeOID: metadata?.typeOid ?? pgTypeOid(dataType),
6608
6705
  characterMaximumLength: metadata?.characterMaximumLength ?? null,
6609
- notNull: Boolean(metadata?.notNull || metadata?.primaryKey || column.notnull || column.pk),
6706
+ // primary-key columns are never-null by definition; legacy
6707
+ // tables whose key was promoted from a <table>_pkey unique
6708
+ // index carry nullable physical columns, and zero's
6709
+ // view-syncer refuses to sync a table whose key columns are
6710
+ // nullable on the replica (checkClientSchema).
6711
+ notNull: Boolean(metadata?.notNull ||
6712
+ metadata?.primaryKey ||
6713
+ column.notnull ||
6714
+ column.pk ||
6715
+ info.primaryKey.includes(column.name)),
6610
6716
  dflt: null,
6611
6717
  },
6612
6718
  ];