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.
- package/dist/bench/serial-mutations.bench.js +1 -1
- package/dist/cf-do/worker.d.ts +1 -1
- package/dist/cf-do/worker.d.ts.map +1 -1
- package/dist/cf-do/worker.js +4 -4
- package/dist/cf-do/worker.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +99 -33
- package/dist/index.js.map +1 -1
- package/dist/pg-proxy-browser.d.ts.map +1 -1
- package/dist/pg-proxy-browser.js +14 -2
- package/dist/pg-proxy-browser.js.map +1 -1
- package/dist/pg-proxy-do-backend.d.ts.map +1 -1
- package/dist/pg-proxy-do-backend.js +117 -34
- package/dist/pg-proxy-do-backend.js.map +1 -1
- package/dist/pg-proxy.d.ts +16 -1
- package/dist/pg-proxy.d.ts.map +1 -1
- package/dist/pg-proxy.js +17 -1
- package/dist/pg-proxy.js.map +1 -1
- package/dist/pg-sqlite-compiler/index.d.ts.map +1 -1
- package/dist/pg-sqlite-compiler/index.js +3 -1
- package/dist/pg-sqlite-compiler/index.js.map +1 -1
- package/dist/pg-sqlite-compiler/passes/types.js +1 -1
- package/dist/pg-sqlite-compiler/passes/types.js.map +1 -1
- package/dist/recovery.d.ts +44 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +33 -0
- package/dist/recovery.js.map +1 -1
- package/dist/replication/handler.d.ts.map +1 -1
- package/dist/replication/handler.js +8 -3
- package/dist/replication/handler.js.map +1 -1
- package/dist/sqlite-keyword-identifiers.d.ts +3 -0
- package/dist/sqlite-keyword-identifiers.d.ts.map +1 -0
- package/dist/sqlite-keyword-identifiers.js +233 -0
- package/dist/sqlite-keyword-identifiers.js.map +1 -0
- package/dist/worker/cf-patches.d.ts.map +1 -1
- package/dist/worker/cf-patches.js +46 -7
- package/dist/worker/cf-patches.js.map +1 -1
- 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
|
-
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
}
|