orez 0.3.9 → 0.4.1

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.
Files changed (38) hide show
  1. package/dist/bench/serial-mutations.bench.js +1 -1
  2. package/dist/cf-do/worker.d.ts +1 -1
  3. package/dist/cf-do/worker.d.ts.map +1 -1
  4. package/dist/cf-do/worker.js +4 -4
  5. package/dist/cf-do/worker.js.map +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +99 -33
  8. package/dist/index.js.map +1 -1
  9. package/dist/pg-proxy-browser.d.ts.map +1 -1
  10. package/dist/pg-proxy-browser.js +14 -2
  11. package/dist/pg-proxy-browser.js.map +1 -1
  12. package/dist/pg-proxy-do-backend.d.ts.map +1 -1
  13. package/dist/pg-proxy-do-backend.js +117 -34
  14. package/dist/pg-proxy-do-backend.js.map +1 -1
  15. package/dist/pg-proxy.d.ts +16 -1
  16. package/dist/pg-proxy.d.ts.map +1 -1
  17. package/dist/pg-proxy.js +17 -1
  18. package/dist/pg-proxy.js.map +1 -1
  19. package/dist/pg-sqlite-compiler/index.d.ts.map +1 -1
  20. package/dist/pg-sqlite-compiler/index.js +3 -1
  21. package/dist/pg-sqlite-compiler/index.js.map +1 -1
  22. package/dist/pg-sqlite-compiler/passes/types.js +1 -1
  23. package/dist/pg-sqlite-compiler/passes/types.js.map +1 -1
  24. package/dist/recovery.d.ts +44 -0
  25. package/dist/recovery.d.ts.map +1 -1
  26. package/dist/recovery.js +33 -0
  27. package/dist/recovery.js.map +1 -1
  28. package/dist/replication/handler.d.ts.map +1 -1
  29. package/dist/replication/handler.js +8 -3
  30. package/dist/replication/handler.js.map +1 -1
  31. package/dist/sqlite-keyword-identifiers.d.ts +3 -0
  32. package/dist/sqlite-keyword-identifiers.d.ts.map +1 -0
  33. package/dist/sqlite-keyword-identifiers.js +233 -0
  34. package/dist/sqlite-keyword-identifiers.js.map +1 -0
  35. package/dist/worker/cf-patches.d.ts.map +1 -1
  36. package/dist/worker/cf-patches.js +46 -7
  37. package/dist/worker/cf-patches.js.map +1 -1
  38. package/package.json +4 -4
@@ -2,6 +2,7 @@
2
2
  import { deparseSync, loadModule, parseSync } from 'pgsql-parser';
3
3
  import { RETURNING_INTERNAL_PREFIX } from './do-sql-tracking.js';
4
4
  import { signalReplicationChange } from './replication/handler.js';
5
+ import { markSQLiteKeywordIdentifiers, restoreSQLiteKeywordIdentifierMarkers, } from './sqlite-keyword-identifiers.js';
5
6
  /**
6
7
  * DoBackend: a PGlite-compatible adapter that forwards SQL to Cloudflare Durable Objects.
7
8
  *
@@ -142,9 +143,44 @@ function buildCommandComplete(tag) {
142
143
  function buildReadyForQuery(status = STATUS_IDLE) {
143
144
  return msg(0x5a, new Uint8Array([status]));
144
145
  }
145
- function buildErrorResponse(message) {
146
+ /**
147
+ * map a SQLite error message to the closest PostgreSQL SQLSTATE. zero-cache and
148
+ * pg clients (e.g. @take-out/database's migrate) branch on the SQLSTATE code,
149
+ * not the message — notably to treat "already exists" / "does not exist" DDL as
150
+ * idempotent during migration replay. without this the DO backend reported
151
+ * everything as XX000 (internal_error), so a re-applied `ADD COLUMN` aborted the
152
+ * whole migration instead of being recorded as applied. codes mirror the ones
153
+ * postgres returns for the equivalent failures.
154
+ */
155
+ function sqlstateForSqliteError(message) {
156
+ const m = message.toLowerCase();
157
+ // duplicate-object DDL (idempotent on replay)
158
+ if (m.includes('duplicate column name'))
159
+ return '42701'; // duplicate_column
160
+ if (/\btable\b[^]*already exists/.test(m))
161
+ return '42P07'; // duplicate_table
162
+ if (/already exists/.test(m))
163
+ return '42710'; // duplicate_object (index/trigger/view/etc)
164
+ // missing-object DDL (idempotent for DROP ... without IF EXISTS)
165
+ if (m.includes('no such column'))
166
+ return '42703'; // undefined_column
167
+ if (m.includes('no such table'))
168
+ return '42P01'; // undefined_table
169
+ if (m.includes('no such index') || m.includes('no such trigger'))
170
+ return '42704'; // undefined_object
171
+ if (m.includes('syntax error'))
172
+ return '42601'; // syntax_error
173
+ if (m.includes('unique constraint failed'))
174
+ return '23505'; // unique_violation
175
+ if (m.includes('not null constraint failed'))
176
+ return '23502'; // not_null_violation
177
+ if (m.includes('foreign key constraint failed'))
178
+ return '23503'; // foreign_key_violation
179
+ return 'XX000'; // internal_error
180
+ }
181
+ function buildErrorResponse(message, sqlstate) {
146
182
  const field = (code, value) => concat(textEncoder.encode(code), cstr(value));
147
- return msg(0x45, concat(field('S', 'ERROR'), field('V', 'ERROR'), field('C', 'XX000'), field('M', message), new Uint8Array([0])));
183
+ return msg(0x45, concat(field('S', 'ERROR'), field('V', 'ERROR'), field('C', sqlstate ?? sqlstateForSqliteError(message)), field('M', message), new Uint8Array([0])));
148
184
  }
149
185
  function buildParseComplete() {
150
186
  return msg(0x31, zero4);
@@ -2121,17 +2157,69 @@ function normalizeColumnType(columnDef) {
2121
2157
  if (sqliteType)
2122
2158
  setTypeName(columnDef.typeName, sqliteType);
2123
2159
  }
2160
+ // pg SERIAL types auto-assign from a sequence. SQLite only auto-increments an
2161
+ // INTEGER PRIMARY KEY, so a non-PK serial column (e.g. zero 1.6's replicas.rank
2162
+ // BIGSERIAL) becomes a plain nullable integer and stays NULL on inserts that
2163
+ // don't supply it — zero then reads it and throws "Expected bigint at rank.
2164
+ // Got null". emulate the sequence with an AFTER INSERT trigger that fills the
2165
+ // column with max()+1 when it's left NULL.
2166
+ const SERIAL_TYPES = new Set([
2167
+ 'serial',
2168
+ 'serial2',
2169
+ 'serial4',
2170
+ 'serial8',
2171
+ 'smallserial',
2172
+ 'bigserial',
2173
+ ]);
2174
+ function serialColumnNames(createStmt) {
2175
+ const names = [];
2176
+ for (const elt of createStmt?.tableElts ?? []) {
2177
+ const col = elt?.ColumnDef;
2178
+ if (!col?.colname)
2179
+ continue;
2180
+ const base = typeNameBase(col.typeName);
2181
+ if (!base || !SERIAL_TYPES.has(base))
2182
+ continue;
2183
+ // an inline PRIMARY KEY serial becomes INTEGER PRIMARY KEY (rowid alias),
2184
+ // which SQLite already auto-increments — no trigger needed.
2185
+ const isPrimaryKey = (col.constraints ?? []).some((c) => c?.Constraint?.contype === 'CONSTR_PRIMARY');
2186
+ if (isPrimaryKey)
2187
+ continue;
2188
+ names.push(col.colname);
2189
+ }
2190
+ return names;
2191
+ }
2192
+ function serialTriggerStatements(table, columns) {
2193
+ return columns.map((col) => ({
2194
+ sql: `CREATE TRIGGER IF NOT EXISTS ${quoteIdentifier(`${table}_${col}_serial`)}
2195
+ AFTER INSERT ON ${quoteIdentifier(table)}
2196
+ FOR EACH ROW WHEN NEW.${quoteIdentifier(col)} IS NULL
2197
+ BEGIN
2198
+ UPDATE ${quoteIdentifier(table)}
2199
+ SET ${quoteIdentifier(col)} = (SELECT coalesce(max(${quoteIdentifier(col)}), 0) + 1 FROM ${quoteIdentifier(table)})
2200
+ WHERE rowid = NEW.rowid;
2201
+ END`,
2202
+ isDDL: true,
2203
+ }));
2204
+ }
2124
2205
  function isDefaultConstraint(constraint) {
2125
2206
  return constraint?.Constraint?.contype === 'CONSTR_DEFAULT';
2126
2207
  }
2127
- function shouldDropAddColumnDefault(constraint) {
2208
+ // functions that produce a fresh non-constant value and have no usable SQLite
2209
+ // column-default form. on the DO replica these defaults are never exercised:
2210
+ // zero-cache replicates full row values (every column is supplied), and zero's
2211
+ // own replicas.id default is explicitly "for backwards compatibility" with each
2212
+ // insert providing id. so we drop the default rather than translate it. checked
2213
+ // recursively because zero wraps it, e.g. replace(gen_random_uuid()::text,…).
2214
+ const NON_CONSTANT_DEFAULT_FUNCTIONS = new Set([
2215
+ 'gen_random_uuid',
2216
+ 'uuid_generate_v4',
2217
+ 'md5',
2218
+ ]);
2219
+ function shouldDropFunctionDefault(constraint) {
2128
2220
  if (!isDefaultConstraint(constraint))
2129
2221
  return false;
2130
- const raw = constraint.Constraint.raw_expr;
2131
- if (!raw?.FuncCall)
2132
- return false;
2133
- const name = functionName(raw.FuncCall);
2134
- return name === 'md5' || name === 'gen_random_uuid';
2222
+ return containsAnyFuncCall(constraint.Constraint.raw_expr, NON_CONSTANT_DEFAULT_FUNCTIONS);
2135
2223
  }
2136
2224
  function containsAnyFuncCall(value, names) {
2137
2225
  if (!value || typeof value !== 'object')
@@ -2155,7 +2243,7 @@ function normalizeColumnDef(columnDef, options) {
2155
2243
  if (options?.addedColumn &&
2156
2244
  (type === 'CONSTR_NOTNULL' || type === 'CONSTR_PRIMARY'))
2157
2245
  return false;
2158
- if (options?.addedColumn && shouldDropAddColumnDefault(constraint))
2246
+ if (shouldDropFunctionDefault(constraint))
2159
2247
  return false;
2160
2248
  if (type === 'CONSTR_GENERATED' &&
2161
2249
  containsAnyFuncCall(constraint.Constraint.raw_expr, UNSUPPORTED_GENERATED_COLUMN_FUNCTIONS))
@@ -2589,7 +2677,8 @@ function replaceSchemaSpecsFunctionCalls(sql) {
2589
2677
  return { sql: replaced, count };
2590
2678
  }
2591
2679
  function deparseStatement(version, stmt) {
2592
- return stripTrailingSemicolon(deparseSync({ version, stmts: [{ stmt }] }).trim());
2680
+ const quotedByMarker = markSQLiteKeywordIdentifiers(stmt);
2681
+ return stripTrailingSemicolon(restoreSQLiteKeywordIdentifierMarkers(deparseSync({ version, stmts: [{ stmt }] }), quotedByMarker).trim());
2593
2682
  }
2594
2683
  function starTarget() {
2595
2684
  return {
@@ -2824,7 +2913,7 @@ function selectWhereSQL(version, whereClause) {
2824
2913
  op: 'SETOP_NONE',
2825
2914
  },
2826
2915
  };
2827
- const sql = stripTrailingSemicolon(deparseSync({ version, stmts: [{ stmt }] }).trim());
2916
+ const sql = deparseStatement(version, stmt);
2828
2917
  const whereIndex = findKeywordOutsideQuotes(sql, 'WHERE');
2829
2918
  if (whereIndex < 0)
2830
2919
  return null;
@@ -2849,27 +2938,17 @@ function compileSelectIntoNew(version, select, triggerTable, rowCondition) {
2849
2938
  const targetColumn = rel.relname;
2850
2939
  const cloned = cloneAst(select);
2851
2940
  delete cloned.intoClause;
2852
- const selectSQL = stripTrailingSemicolon(deparseSync({
2853
- version,
2854
- stmts: [{ stmt: { SelectStmt: rewriteNode(cloned) } }],
2855
- }).trim());
2941
+ const selectSQL = deparseStatement(version, { SelectStmt: rewriteNode(cloned) });
2856
2942
  return rowUpdateSQL(triggerTable, targetColumn, `(${selectSQL})`, rowCondition);
2857
2943
  }
2858
2944
  function deparseExpressionSQL(version, expr) {
2859
- const sql = stripTrailingSemicolon(deparseSync({
2860
- version,
2861
- stmts: [
2862
- {
2863
- stmt: {
2864
- SelectStmt: {
2865
- targetList: [{ ResTarget: { val: expr, location: -1 } }],
2866
- limitOption: 'LIMIT_OPTION_DEFAULT',
2867
- op: 'SETOP_NONE',
2868
- },
2869
- },
2870
- },
2871
- ],
2872
- }).trim());
2945
+ const sql = deparseStatement(version, {
2946
+ SelectStmt: {
2947
+ targetList: [{ ResTarget: { val: expr, location: -1 } }],
2948
+ limitOption: 'LIMIT_OPTION_DEFAULT',
2949
+ op: 'SETOP_NONE',
2950
+ },
2951
+ });
2873
2952
  const selectIndex = findKeywordOutsideQuotes(sql, 'SELECT');
2874
2953
  if (selectIndex < 0)
2875
2954
  return null;
@@ -3228,6 +3307,7 @@ function rewriteParsedStatement(version, rawStmt, context) {
3228
3307
  let skipIfTableEmpty = null;
3229
3308
  let changeTracking;
3230
3309
  let writeTable = null;
3310
+ let serialTriggers = [];
3231
3311
  if (nodeType === 'AlterTableStmt') {
3232
3312
  alterMetadata = normalizeAlterTable(node);
3233
3313
  if (!node.cmds?.length) {
@@ -3245,7 +3325,12 @@ function rewriteParsedStatement(version, rawStmt, context) {
3245
3325
  }
3246
3326
  else if (nodeType === 'CreateStmt') {
3247
3327
  schemaColumns = schemaColumnsForCreateTable(node);
3328
+ // capture serial columns before normalizeCreateTable rewrites the type to integer
3329
+ const serialCols = serialColumnNames(node);
3248
3330
  normalizeCreateTable(node);
3331
+ if (serialCols.length && node.relation?.relname) {
3332
+ serialTriggers = serialTriggerStatements(node.relation.relname, serialCols);
3333
+ }
3249
3334
  }
3250
3335
  else if (nodeType === 'CreateTableAsStmt') {
3251
3336
  normalizeCreateTableAs(node);
@@ -3356,7 +3441,7 @@ function rewriteParsedStatement(version, rawStmt, context) {
3356
3441
  const isWrite = nodeType === 'DeleteStmt' || nodeType === 'InsertStmt' || nodeType === 'UpdateStmt';
3357
3442
  const usesPublishedSchemaFunction = context?.skippedFunctionNames?.has('schema_specs') &&
3358
3443
  containsFuncCall(stmt, 'schema_specs');
3359
- return {
3444
+ const mainStatement = {
3360
3445
  sql: rewritten,
3361
3446
  ...(isDDL ? { isDDL } : null),
3362
3447
  ...(isWrite ? { isWrite } : null),
@@ -3378,6 +3463,7 @@ function rewriteParsedStatement(version, rawStmt, context) {
3378
3463
  ...(skipIfColumnMissing ? { skipIfColumnMissing } : null),
3379
3464
  ...(skipIfTableEmpty ? { skipIfTableEmpty } : null),
3380
3465
  };
3466
+ return serialTriggers.length ? [mainStatement, ...serialTriggers] : mainStatement;
3381
3467
  }
3382
3468
  function rewriteSQLStatements(sql, context) {
3383
3469
  const trimmed = sql.trim();
@@ -3539,10 +3625,7 @@ function copySelectSQL(sql) {
3539
3625
  stringValue(def.arg)?.toLowerCase() === 'binary');
3540
3626
  });
3541
3627
  return {
3542
- sql: stripTrailingSemicolon(deparseSync({
3543
- version: parsed.version,
3544
- stmts: [{ stmt: { SelectStmt: copy.query.SelectStmt } }],
3545
- }).trim()),
3628
+ sql: deparseStatement(parsed.version, { SelectStmt: copy.query.SelectStmt }),
3546
3629
  binary,
3547
3630
  };
3548
3631
  }