@crvouga/mockingbird-service-postgres 1.5.1 → 1.6.0

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.
@@ -3463,7 +3463,13 @@ var DatabaseState = class _DatabaseState {
3463
3463
  this.settings.set("lc_numeric", "C");
3464
3464
  this.settings.set("lc_time", "C");
3465
3465
  this.settings.set("default_transaction_isolation", "read committed");
3466
+ this.settings.set("lock_timeout", "0");
3467
+ this.settings.set("idle_in_transaction_session_timeout", "0");
3468
+ this.settings.set("transaction_timeout", "0");
3466
3469
  this.settings.set("statement_timeout", "0");
3470
+ this.settings.set("check_function_bodies", "on");
3471
+ this.settings.set("xmloption", "content");
3472
+ this.settings.set("row_security", "on");
3467
3473
  this.settings.set("array_nulls", "on");
3468
3474
  this.settings.set("backslash_quote", "safe_encoding");
3469
3475
  }
@@ -7065,6 +7071,22 @@ function exactDegTan(x) {
7065
7071
  return Math.tan(x * Math.PI / 180);
7066
7072
  }
7067
7073
 
7074
+ // src/sql/deparse.ts
7075
+ function quoteIdent(name) {
7076
+ if (/^[a-z_][a-z0-9_$]*$/.test(name)) return name;
7077
+ return `"${name.replaceAll('"', '""')}"`;
7078
+ }
7079
+
7080
+ // src/runtime/database-context.ts
7081
+ var contexts = /* @__PURE__ */ new WeakMap();
7082
+ var fallback = { name: "postgres", names: () => ["postgres"] };
7083
+ function databaseCatalogContext(state) {
7084
+ return contexts.get(state) ?? fallback;
7085
+ }
7086
+ function setDatabaseCatalogContext(state, context) {
7087
+ contexts.set(state, context);
7088
+ }
7089
+
7068
7090
  // src/functions/misc-fns.ts
7069
7091
  var PG_VERSION_TEXT = "PostgreSQL 18.3 (postgres-mem) on TypeScript, in-memory engine";
7070
7092
  function findSequenceForCall(ctx, name) {
@@ -7148,8 +7170,8 @@ function sequenceNextval(ctx, seq) {
7148
7170
  function getMiscFunctions() {
7149
7171
  const m = /* @__PURE__ */ new Map();
7150
7172
  m.set("version", () => tv("text", PG_VERSION_TEXT));
7151
- m.set("current_database", () => tv("name", "postgres"));
7152
- m.set("current_catalog", () => tv("name", "postgres"));
7173
+ m.set("current_database", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
7174
+ m.set("current_catalog", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
7153
7175
  m.set("current_schema", (ctx) => {
7154
7176
  const path = ctx.state.effectiveSearchPath();
7155
7177
  const first = path.find((s) => ctx.state.schemas.has(s));
@@ -7271,7 +7293,7 @@ function getMiscFunctions() {
7271
7293
  if (!col.identity) return tv("text", null);
7272
7294
  return tv(
7273
7295
  "text",
7274
- col.identity.sequence.split(".").map((p) => p).join(".")
7296
+ col.identity.sequence.split(".").map(quoteIdent).join(".")
7275
7297
  );
7276
7298
  })
7277
7299
  );
@@ -14521,14 +14543,19 @@ var Parser = class {
14521
14543
  parseIdentityOptions() {
14522
14544
  const options = {};
14523
14545
  if (this.eatPunct("(")) {
14524
- Object.assign(options, this.parseSequenceOptions());
14546
+ Object.assign(options, this.parseSequenceOptions(true));
14525
14547
  this.expectPunct(")");
14526
14548
  }
14527
14549
  return options;
14528
14550
  }
14529
- parseSequenceOptions() {
14551
+ parseSequenceOptions(identity = false) {
14530
14552
  const options = {};
14531
14553
  for (; ; ) {
14554
+ if (identity && this.eatKw("sequence")) {
14555
+ this.expectKw("name");
14556
+ options.sequenceName = this.qualifiedName();
14557
+ continue;
14558
+ }
14532
14559
  if (this.eatKw("increment")) {
14533
14560
  this.eatKw("by");
14534
14561
  options.increment = this.parseSignedBigInt();
@@ -20530,6 +20557,27 @@ function parseCsvLine(line, delim, quote) {
20530
20557
  }
20531
20558
  return fields;
20532
20559
  }
20560
+ function splitCsvRecords(data, quote) {
20561
+ const records = [];
20562
+ let start = 0;
20563
+ let quoted = false;
20564
+ for (let i = 0; i < data.length; i++) {
20565
+ const char = data[i];
20566
+ if (char === quote) {
20567
+ if (quoted && data[i + 1] === quote) i++;
20568
+ else quoted = !quoted;
20569
+ continue;
20570
+ }
20571
+ if (char === "\n" && !quoted) {
20572
+ const end = i > start && data[i - 1] === "\r" ? i - 1 : i;
20573
+ records.push(data.slice(start, end));
20574
+ start = i + 1;
20575
+ }
20576
+ }
20577
+ if (quoted) throw pgError("invalid_text_representation", "unterminated CSV quoted field", "22P04");
20578
+ if (start < data.length) records.push(data.slice(start));
20579
+ return records;
20580
+ }
20533
20581
  function executeCopyFromData(env, stmt, data) {
20534
20582
  const format = String(stmt.options.format ?? "text").toLowerCase();
20535
20583
  if (format !== "text" && format !== "csv") throw unsupported(`COPY format ${format}`);
@@ -20540,7 +20588,7 @@ function executeCopyFromData(env, stmt, data) {
20540
20588
  const nullStr = String(stmt.options.null ?? (format === "csv" ? "" : "\\N"));
20541
20589
  const quote = String(stmt.options.quote ?? '"');
20542
20590
  const header = stmt.options.header === true || String(stmt.options.header ?? "").toLowerCase() === "true";
20543
- let lines = data.split(/\r?\n/);
20591
+ let lines = format === "csv" ? splitCsvRecords(data, quote) : data.split(/\r?\n/);
20544
20592
  const endMarker = lines.indexOf("\\.");
20545
20593
  if (endMarker !== -1) lines = lines.slice(0, endMarker);
20546
20594
  if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
@@ -21274,7 +21322,8 @@ function pgSettings(ctx) {
21274
21322
  "pg_settings"
21275
21323
  );
21276
21324
  }
21277
- function pgDatabase() {
21325
+ function pgDatabase(ctx) {
21326
+ const names = databaseCatalogContext(ctx.state).names();
21278
21327
  return rel(
21279
21328
  [
21280
21329
  ["oid", "oid"],
@@ -21283,7 +21332,7 @@ function pgDatabase() {
21283
21332
  ["datistemplate", "bool"],
21284
21333
  ["datallowconn", "bool"]
21285
21334
  ],
21286
- [[5, "postgres", 6, false, true]],
21335
+ names.map((name, index) => [5 + index, name, 6, false, true]),
21287
21336
  "pg_database"
21288
21337
  );
21289
21338
  }
@@ -21318,13 +21367,14 @@ function pgTrigger(ctx) {
21318
21367
  "pg_trigger"
21319
21368
  );
21320
21369
  }
21321
- var CATALOG_NAME = "postgres";
21370
+ var catalogName = (state) => databaseCatalogContext(state).name;
21322
21371
  function infoSchemata(state) {
21372
+ const catalog = catalogName(state);
21323
21373
  const rows = [
21324
- [CATALOG_NAME, "pg_catalog", OWNER],
21325
- [CATALOG_NAME, "information_schema", OWNER]
21374
+ [catalog, "pg_catalog", OWNER],
21375
+ [catalog, "information_schema", OWNER]
21326
21376
  ];
21327
- for (const s of state.schemas.values()) rows.push([CATALOG_NAME, s.name, OWNER]);
21377
+ for (const s of state.schemas.values()) rows.push([catalog, s.name, OWNER]);
21328
21378
  return rel(
21329
21379
  [
21330
21380
  ["catalog_name", "name"],
@@ -21336,13 +21386,14 @@ function infoSchemata(state) {
21336
21386
  );
21337
21387
  }
21338
21388
  function infoTables(ctx) {
21389
+ const catalog = catalogName(ctx.state);
21339
21390
  const rows = [];
21340
21391
  for (const schema of ctx.state.schemas.values()) {
21341
21392
  for (const t of schema.tables.values()) {
21342
- rows.push([CATALOG_NAME, t.schema, t.name, t.temp ? "LOCAL TEMPORARY" : "BASE TABLE", "YES"]);
21393
+ rows.push([catalog, t.schema, t.name, t.temp ? "LOCAL TEMPORARY" : "BASE TABLE", "YES"]);
21343
21394
  }
21344
21395
  for (const v of schema.views.values()) {
21345
- if (!v.materialized) rows.push([CATALOG_NAME, v.schema, v.name, "VIEW", "NO"]);
21396
+ if (!v.materialized) rows.push([catalog, v.schema, v.name, "VIEW", "NO"]);
21346
21397
  }
21347
21398
  }
21348
21399
  return rel(
@@ -21358,6 +21409,7 @@ function infoTables(ctx) {
21358
21409
  );
21359
21410
  }
21360
21411
  function infoColumns(ctx) {
21412
+ const catalog = catalogName(ctx.state);
21361
21413
  const rows = [];
21362
21414
  for (const t of allTables(ctx.state)) {
21363
21415
  t.columns.forEach((c, i) => {
@@ -21368,7 +21420,7 @@ function infoColumns(ctx) {
21368
21420
  const dataType = isArrayType(id) ? "ARRAY" : isEnumType(id) ? "USER-DEFINED" : typeDisplayName(id);
21369
21421
  const udt = isArrayType(id) ? `_${arrayUdt(id)}` : isEnumType(id) ? id.slice(5).split(".").pop() : id;
21370
21422
  rows.push([
21371
- CATALOG_NAME,
21423
+ catalog,
21372
21424
  t.schema,
21373
21425
  t.name,
21374
21426
  c.name,
@@ -21413,10 +21465,11 @@ function arrayUdt(t) {
21413
21465
  return isEnumType(elem) ? elem.slice(5).split(".").pop() : elem;
21414
21466
  }
21415
21467
  function infoViews(ctx) {
21468
+ const catalog = catalogName(ctx.state);
21416
21469
  const rows = [];
21417
21470
  for (const schema of ctx.state.schemas.values()) {
21418
21471
  for (const v of schema.views.values()) {
21419
- if (!v.materialized) rows.push([CATALOG_NAME, v.schema, v.name, null]);
21472
+ if (!v.materialized) rows.push([catalog, v.schema, v.name, null]);
21420
21473
  }
21421
21474
  }
21422
21475
  return rel(
@@ -21431,10 +21484,11 @@ function infoViews(ctx) {
21431
21484
  );
21432
21485
  }
21433
21486
  function infoSequences(ctx) {
21487
+ const catalog = catalogName(ctx.state);
21434
21488
  const rows = [];
21435
21489
  for (const s of allSequences(ctx.state)) {
21436
21490
  rows.push([
21437
- CATALOG_NAME,
21491
+ catalog,
21438
21492
  s.schema,
21439
21493
  s.name,
21440
21494
  typeDisplayName(s.dataType),
@@ -21462,11 +21516,12 @@ function infoSequences(ctx) {
21462
21516
  );
21463
21517
  }
21464
21518
  function infoTableConstraints(ctx) {
21519
+ const catalog = catalogName(ctx.state);
21465
21520
  const rows = [];
21466
21521
  for (const t of allTables(ctx.state)) {
21467
21522
  for (const c of t.constraints) {
21468
21523
  const type = c.kind === "primary_key" ? "PRIMARY KEY" : c.kind === "unique" ? "UNIQUE" : c.kind === "check" ? "CHECK" : "FOREIGN KEY";
21469
- rows.push([CATALOG_NAME, t.schema, c.name, CATALOG_NAME, t.schema, t.name, type]);
21524
+ rows.push([catalog, t.schema, c.name, catalog, t.schema, t.name, type]);
21470
21525
  }
21471
21526
  }
21472
21527
  return rel(
@@ -21484,12 +21539,13 @@ function infoTableConstraints(ctx) {
21484
21539
  );
21485
21540
  }
21486
21541
  function infoKeyColumnUsage(ctx) {
21542
+ const catalog = catalogName(ctx.state);
21487
21543
  const rows = [];
21488
21544
  for (const t of allTables(ctx.state)) {
21489
21545
  for (const c of t.constraints) {
21490
21546
  if (c.kind !== "primary_key" && c.kind !== "unique" && c.kind !== "foreign_key") continue;
21491
21547
  c.columns.forEach((col, i) => {
21492
- rows.push([CATALOG_NAME, t.schema, c.name, CATALOG_NAME, t.schema, t.name, col, i + 1]);
21548
+ rows.push([catalog, t.schema, c.name, catalog, t.schema, t.name, col, i + 1]);
21493
21549
  });
21494
21550
  }
21495
21551
  }
@@ -21509,12 +21565,13 @@ function infoKeyColumnUsage(ctx) {
21509
21565
  );
21510
21566
  }
21511
21567
  function infoRoutines(ctx) {
21568
+ const catalog = catalogName(ctx.state);
21512
21569
  const rows = [];
21513
21570
  for (const schema of ctx.state.schemas.values()) {
21514
21571
  for (const fns of schema.functions.values()) {
21515
21572
  for (const f of fns) {
21516
21573
  rows.push([
21517
- CATALOG_NAME,
21574
+ catalog,
21518
21575
  f.schema,
21519
21576
  f.name,
21520
21577
  "FUNCTION",
@@ -21571,7 +21628,7 @@ function buildCatalogRelation(ctx, schema, name) {
21571
21628
  case "pg_settings":
21572
21629
  return pgSettings(ctx);
21573
21630
  case "pg_database":
21574
- return pgDatabase();
21631
+ return pgDatabase(ctx);
21575
21632
  case "pg_roles":
21576
21633
  case "pg_user":
21577
21634
  return pgRoles();
@@ -21752,6 +21809,11 @@ var MAX_IDENT = 63;
21752
21809
  function truncIdent(s) {
21753
21810
  return s.length > MAX_IDENT ? s.slice(0, MAX_IDENT) : s;
21754
21811
  }
21812
+ function identitySequenceTarget(env, tableSchema, defaultName, options) {
21813
+ const parts = options.sequenceName;
21814
+ if (!parts || parts.length === 0) return { schema: tableSchema, name: defaultName };
21815
+ return targetSchema(env, parts);
21816
+ }
21755
21817
  var SEQ_LIMITS = {
21756
21818
  int2: { min: -32768n, max: 32767n },
21757
21819
  int4: { min: -2147483648n, max: 2147483647n },
@@ -21928,8 +21990,8 @@ function buildColumn(env, schema, tableName, def, out, taken) {
21928
21990
  break;
21929
21991
  }
21930
21992
  case "generated_identity": {
21931
- const seqName = truncIdent(`${tableName}_${def.name}_seq`);
21932
- const seq = buildSequence(env, schema, seqName, c.options, false);
21993
+ const target = identitySequenceTarget(env, schema, truncIdent(`${tableName}_${def.name}_seq`), c.options);
21994
+ const seq = buildSequence(env, target.schema, target.name, c.options, false);
21933
21995
  if (!c.options.as) {
21934
21996
  seq.dataType = col.type.id === "int2" || col.type.id === "int4" || col.type.id === "int8" ? col.type.id : "int8";
21935
21997
  const limits = SEQ_LIMITS[seq.dataType];
@@ -21938,7 +22000,7 @@ function buildColumn(env, schema, tableName, def, out, taken) {
21938
22000
  }
21939
22001
  seq.ownedBy = { table: tableName, column: def.name };
21940
22002
  out.sequences.push(seq);
21941
- col.identity = { always: c.always, sequence: `${schema.name}.${seqName}` };
22003
+ col.identity = { always: c.always, sequence: `${target.schema.name}.${target.name}` };
21942
22004
  col.notNull = true;
21943
22005
  break;
21944
22006
  }
@@ -22074,10 +22136,11 @@ function executeCreateTable(env, stmt) {
22074
22136
  throw pgError("invalid_table_definition", `multiple primary keys for table "${name}" are not allowed`, "42P16");
22075
22137
  }
22076
22138
  for (const seq of built.sequences) {
22077
- if (schema.hasRelation(seq.name)) {
22139
+ const seqSchema = state.getSchema(seq.schema);
22140
+ if (seqSchema.hasRelation(seq.name)) {
22078
22141
  throw pgError("duplicate_table", `relation "${seq.name}" already exists`, "42P07");
22079
22142
  }
22080
- schema.sequences.set(seq.name, seq);
22143
+ seqSchema.sequences.set(seq.name, seq);
22081
22144
  }
22082
22145
  const table = new TableData(schema.name, name, built.columns, state.nextOid(), stmt.temp);
22083
22146
  table.constraints = built.constraints;
@@ -22429,7 +22492,13 @@ function executeAlterTable(env, stmt) {
22429
22492
  const built = { columns: [], constraints: [], sequences: [] };
22430
22493
  const taken = constraintNames(table);
22431
22494
  const col = buildColumn(env, schema, table.name, action.column, built, taken);
22432
- for (const seq of built.sequences) schema.sequences.set(seq.name, seq);
22495
+ for (const seq of built.sequences) {
22496
+ const seqSchema = state.getSchema(seq.schema);
22497
+ if (seqSchema.hasRelation(seq.name)) {
22498
+ throw pgError("duplicate_table", `relation "${seq.name}" already exists`, "42P07");
22499
+ }
22500
+ seqSchema.sequences.set(seq.name, seq);
22501
+ }
22433
22502
  table.columns.push(col);
22434
22503
  table.constraints.push(...built.constraints);
22435
22504
  for (let i = 0; i < rows.length; i++) {
@@ -22749,12 +22818,32 @@ function executeAlterTable(env, stmt) {
22749
22818
  "42P16"
22750
22819
  );
22751
22820
  }
22752
- const seqName = truncIdent(`${table.name}_${action.column}_seq`);
22753
- const seq = buildSequence(env, schema, seqName, action.options, false);
22821
+ if (!col.notNull) {
22822
+ throw pgError(
22823
+ "object_not_in_prerequisite_state",
22824
+ `column "${action.column}" of relation "${table.name}" must be declared NOT NULL before identity can be added`,
22825
+ "55000"
22826
+ );
22827
+ }
22828
+ const target = identitySequenceTarget(
22829
+ env,
22830
+ schema,
22831
+ truncIdent(`${table.name}_${action.column}_seq`),
22832
+ action.options
22833
+ );
22834
+ if (target.schema.hasRelation(target.name)) {
22835
+ throw pgError("duplicate_table", `relation "${target.name}" already exists`, "42P07");
22836
+ }
22837
+ const seq = buildSequence(env, target.schema, target.name, action.options, false);
22838
+ if (!action.options.as) {
22839
+ seq.dataType = col.type.id === "int2" || col.type.id === "int4" || col.type.id === "int8" ? col.type.id : "int8";
22840
+ const limits = SEQ_LIMITS[seq.dataType];
22841
+ if (action.options.maxValue === void 0) seq.maxValue = seq.increment > 0n ? limits.max : -1n;
22842
+ if (action.options.minValue === void 0) seq.minValue = seq.increment > 0n ? 1n : limits.min;
22843
+ }
22754
22844
  seq.ownedBy = { table: table.name, column: action.column };
22755
- schema.sequences.set(seqName, seq);
22756
- col.identity = { always: true, sequence: `${schema.name}.${seqName}` };
22757
- col.notNull = true;
22845
+ target.schema.sequences.set(target.name, seq);
22846
+ col.identity = { always: action.always, sequence: `${target.schema.name}.${target.name}` };
22758
22847
  break;
22759
22848
  }
22760
22849
  case "drop_identity": {
@@ -23623,11 +23712,11 @@ if (typeof disposeKey === "symbol") {
23623
23712
 
23624
23713
  // src/wire/cluster.ts
23625
23714
  var Cluster = class {
23626
- constructor(db) {
23627
- this.db = db;
23628
- registerSessionFunctions(this);
23715
+ constructor(db, defaultDatabaseName = "postgres") {
23716
+ this.defaultDatabaseName = defaultDatabaseName;
23717
+ this.installDatabase(defaultDatabaseName, db);
23629
23718
  }
23630
- db;
23719
+ defaultDatabaseName;
23631
23720
  turnHolder = null;
23632
23721
  turnQueue = [];
23633
23722
  locks = /* @__PURE__ */ new Map();
@@ -23636,6 +23725,77 @@ var Cluster = class {
23636
23725
  sessions = /* @__PURE__ */ new Map();
23637
23726
  current = null;
23638
23727
  nextPid = 1e3;
23728
+ databases = /* @__PURE__ */ new Map();
23729
+ /** The initial database, retained for the server's backwards-compatible public handle. */
23730
+ get db() {
23731
+ return this.requireDatabase(this.defaultDatabaseName);
23732
+ }
23733
+ databaseNames() {
23734
+ return [...this.databases.keys()].sort();
23735
+ }
23736
+ getDatabase(name) {
23737
+ return this.databases.get(name);
23738
+ }
23739
+ requireDatabase(name) {
23740
+ const db = this.databases.get(name);
23741
+ if (!db) throw new PostgresError("undefined_object", `database "${name}" does not exist`, "3D000");
23742
+ return db;
23743
+ }
23744
+ createDatabase(name, template) {
23745
+ if (this.databases.has(name)) {
23746
+ throw new PostgresError("duplicate_object", `database "${name}" already exists`, "42P04");
23747
+ }
23748
+ const db = template === void 0 ? new Database() : this.requireTemplate(template).snapshot().open();
23749
+ this.installDatabase(name, db);
23750
+ }
23751
+ dropDatabase(name, requester) {
23752
+ if (name === requester.databaseName || this.activeConnections(name) > 0) {
23753
+ throw new PostgresError(
23754
+ "object_not_in_prerequisite_state",
23755
+ `database "${name}" is being accessed by other users`,
23756
+ "55006"
23757
+ );
23758
+ }
23759
+ if (!this.databases.delete(name)) {
23760
+ throw new PostgresError("undefined_object", `database "${name}" does not exist`, "3D000");
23761
+ }
23762
+ }
23763
+ renameDatabase(from, to, requester) {
23764
+ if (this.databases.has(to)) {
23765
+ throw new PostgresError("duplicate_object", `database "${to}" already exists`, "42P04");
23766
+ }
23767
+ const db = this.requireDatabase(from);
23768
+ if (from === requester.databaseName || this.activeConnections(from) > 0) {
23769
+ throw new PostgresError(
23770
+ "object_not_in_prerequisite_state",
23771
+ `database "${from}" is being accessed by other users`,
23772
+ "55006"
23773
+ );
23774
+ }
23775
+ this.databases.delete(from);
23776
+ this.installDatabase(to, db);
23777
+ }
23778
+ requireTemplate(name) {
23779
+ if (this.activeConnections(name) > 0) {
23780
+ throw new PostgresError(
23781
+ "object_not_in_prerequisite_state",
23782
+ `source database "${name}" is being accessed by other users`,
23783
+ "55006"
23784
+ );
23785
+ }
23786
+ return this.requireDatabase(name);
23787
+ }
23788
+ activeConnections(name) {
23789
+ let count = 0;
23790
+ for (const session of this.sessions.values()) if (session.databaseName === name) count++;
23791
+ return count;
23792
+ }
23793
+ installDatabase(name, db) {
23794
+ const context = { name, names: () => this.databaseNames() };
23795
+ setDatabaseCatalogContext(db.state, context);
23796
+ this.databases.set(name, db);
23797
+ registerSessionFunctions(this, db);
23798
+ }
23639
23799
  newPid() {
23640
23800
  return this.nextPid++;
23641
23801
  }
@@ -23727,7 +23887,7 @@ var Cluster = class {
23727
23887
  }
23728
23888
  this.unlockAll(session);
23729
23889
  if (this.turnHolder === session) {
23730
- if (this.db.transactions.inTransaction) this.db.transactions.rollback();
23890
+ if (session.database.transactions.inTransaction) session.database.transactions.rollback();
23731
23891
  this.releaseTurn(session);
23732
23892
  }
23733
23893
  }
@@ -23759,8 +23919,7 @@ var LockWait = class extends Error {
23759
23919
  key;
23760
23920
  };
23761
23921
  var lockKey = (args) => args.map((a) => String(a)).join(":");
23762
- var registerSessionFunctions = (cluster) => {
23763
- const db = cluster.db;
23922
+ var registerSessionFunctions = (cluster, db) => {
23764
23923
  const me = () => {
23765
23924
  if (!cluster.current) throw new PostgresError("internal", "no session is executing", "XX000");
23766
23925
  return cluster.current;
@@ -23927,6 +24086,17 @@ var backend = {
23927
24086
  for (const oid of oids) w.i32(oid);
23928
24087
  }),
23929
24088
  notificationResponse: (pid, channel, payload) => message("A", (w) => w.i32(pid).cstring(channel).cstring(payload)),
24089
+ copyInResponse: (columns) => message("G", (w) => {
24090
+ w.u8(0).i16(columns);
24091
+ for (let i = 0; i < columns; i++) w.i16(0);
24092
+ }),
24093
+ copyOutResponse: (columns) => message("H", (w) => {
24094
+ w.u8(0).i16(columns);
24095
+ for (let i = 0; i < columns; i++) w.i16(0);
24096
+ }),
24097
+ copyData: (data) => message("d", (w) => w.bytes(data)),
24098
+ copyDone: () => message("c", () => {
24099
+ }),
23930
24100
  /** The one-byte answers to SSLRequest / GSSENCRequest: no encryption here. */
23931
24101
  no: () => new Uint8Array([78])
23932
24102
  };
@@ -24168,6 +24338,23 @@ var LISTEN = /^listen\s+("?)([A-Za-z_][\w$]*)\1\s*$/i;
24168
24338
  var UNLISTEN = /^unlisten\s+(\*|"?[A-Za-z_][\w$]*"?)\s*$/i;
24169
24339
  var NOTIFY = /^notify\s+("?)([A-Za-z_][\w$]*)\1\s*(?:,\s*'((?:[^']|'')*)')?\s*$/i;
24170
24340
  var RETURNS_ROWS = /^(select|with|values|show|table|explain)\b/i;
24341
+ var CLUSTER_DDL = /^(create|drop|alter)\s+database\b/i;
24342
+ var IDENT = '(?:"((?:[^"]|"")+)"|([A-Za-z_][A-Za-z0-9_$]*))';
24343
+ var CREATE_DATABASE = new RegExp(
24344
+ `^create\\s+database\\s+${IDENT}(?:\\s+with)?(?:\\s+template\\s*(?:=\\s*)?${IDENT})?\\s*$`,
24345
+ "i"
24346
+ );
24347
+ var DROP_DATABASE = new RegExp(
24348
+ `^drop\\s+database\\s+(if\\s+exists\\s+)?${IDENT}(?:\\s+with\\s*\\(\\s*force\\s*\\))?\\s*$`,
24349
+ "i"
24350
+ );
24351
+ var RENAME_DATABASE = new RegExp(`^alter\\s+database\\s+${IDENT}\\s+rename\\s+to\\s+${IDENT}\\s*$`, "i");
24352
+ var identifier = (quoted, plain) => quoted === void 0 ? plain.toLowerCase() : quoted.replaceAll('""', '"');
24353
+ var ROW_LOCK = /\s+for\s+(?:no\s+key\s+update|key\s+share|update|share)(?:\s+of\s+(?:(?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)(?:\s*,\s*(?:(?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?))*)?(?:\s+(nowait|skip\s+locked))?\s*$/i;
24354
+ var LIMIT = /\s+limit\s+(\d+)(?:\s+offset\s+(\d+))?\s*$/i;
24355
+ var FROM_TABLE = /\bfrom\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)/i;
24356
+ var UPDATE_TARGET = /^update\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)(?:\s+(?:as\s+)?(?:"[^"]+"|[A-Za-z_][\w$]*))?\s+set\s+[\s\S]*?(?:\s+where\s+([\s\S]*?))?(?:\s+returning\s+[\s\S]*)?$/i;
24357
+ var DELETE_TARGET = /^delete\s+from\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)(?:\s+(?:as\s+)?(?:"[^"]+"|[A-Za-z_][\w$]*))?(?:\s+where\s+([\s\S]*?))?(?:\s+returning\s+[\s\S]*)?$/i;
24171
24358
  var TYPLEN = {
24172
24359
  bool: 1,
24173
24360
  int2: 2,
@@ -24260,6 +24447,7 @@ var binaryValue = (type, text) => {
24260
24447
  }
24261
24448
  };
24262
24449
  var binaryEncodable = (type) => TEXT_TYPES2.has(type) || ["bool", "int2", "int4", "oid", "int8", "float4", "float8", "bytea", "uuid"].includes(type);
24450
+ var mutatesDatabase = (sql) => parse(sql).some((stmt) => !["select", "show", "explain", "transaction", "comment", "no_op"].includes(stmt.type));
24263
24451
  var decodeParam = (oid, format, bytes) => {
24264
24452
  if (bytes === null) return null;
24265
24453
  if (format === 0) return utf83.decode(bytes);
@@ -24308,6 +24496,8 @@ var Connection = class {
24308
24496
  this.socket = socket;
24309
24497
  this.cluster = cluster;
24310
24498
  this.options = options;
24499
+ this.database = cluster.db;
24500
+ this.databaseName = cluster.defaultDatabaseName;
24311
24501
  this.pid = cluster.newPid();
24312
24502
  this.secret = Math.floor(Math.random() * 2147483647);
24313
24503
  cluster.sessions.set(this.pid, this);
@@ -24323,6 +24513,8 @@ var Connection = class {
24323
24513
  waitingForTurn = null;
24324
24514
  waitingForLock = null;
24325
24515
  pendingNotifies = [];
24516
+ database;
24517
+ databaseName;
24326
24518
  frames = new FrameParser();
24327
24519
  inbox = [];
24328
24520
  pumping = false;
@@ -24339,6 +24531,10 @@ var Connection = class {
24339
24531
  prepared = /* @__PURE__ */ new Map();
24340
24532
  portals = /* @__PURE__ */ new Map();
24341
24533
  notifications = [];
24534
+ copyIn = null;
24535
+ copyDrain = false;
24536
+ /** Successful writes in this session's READ COMMITTED transaction workspace. */
24537
+ transaction = null;
24342
24538
  closed = false;
24343
24539
  receive(chunk) {
24344
24540
  let messages;
@@ -24416,6 +24612,20 @@ var Connection = class {
24416
24612
  this.fatal("08P01", "expected startup or password message");
24417
24613
  return;
24418
24614
  }
24615
+ if (this.copyIn && message2.kind === "typed") {
24616
+ await this.handleCopyIn(message2.type, message2.body);
24617
+ return;
24618
+ }
24619
+ if (this.copyDrain && message2.kind === "typed") {
24620
+ if (message2.type === "c" || message2.type === "f") {
24621
+ this.copyDrain = false;
24622
+ this.readyForQuery();
24623
+ } else if (message2.type === "X") {
24624
+ this.socket.end();
24625
+ this.dispose();
24626
+ }
24627
+ return;
24628
+ }
24419
24629
  if (this.skipUntilSync && message2.type !== "S" && message2.type !== "X") return;
24420
24630
  try {
24421
24631
  switch (message2.type) {
@@ -24466,10 +24676,13 @@ var Connection = class {
24466
24676
  }
24467
24677
  this.user = parameters.user ?? "postgres";
24468
24678
  const requested = parameters.database && parameters.database.length > 0 ? parameters.database : this.user;
24469
- if (this.options.database !== void 0 && requested !== this.options.database) {
24679
+ const database = this.cluster.getDatabase(requested);
24680
+ if (!database || this.options.database !== void 0 && requested !== this.options.database) {
24470
24681
  this.fatal("3D000", `database "${requested}" does not exist`);
24471
24682
  return;
24472
24683
  }
24684
+ this.database = database;
24685
+ this.databaseName = requested;
24473
24686
  if (this.options.password !== void 0) {
24474
24687
  this.scram = new ScramServer(this.options.password);
24475
24688
  this.write(backend.authenticationSASL(["SCRAM-SHA-256"]));
@@ -24533,7 +24746,7 @@ var Connection = class {
24533
24746
  }
24534
24747
  // --- execution ----------------------------------------------------------------------------
24535
24748
  get inTransaction() {
24536
- return this.cluster.turnHolder === this && this.cluster.db.transactions.inTransaction;
24749
+ return this.transaction !== null;
24537
24750
  }
24538
24751
  status() {
24539
24752
  if (this.aborted) return "E";
@@ -24556,6 +24769,7 @@ var Connection = class {
24556
24769
  return outcome;
24557
24770
  } catch (error) {
24558
24771
  if (this.inTransaction) this.aborted = true;
24772
+ if (error instanceof PostgresError && error.sqlState === "40P01") this.cluster.unlockAll(this, true);
24559
24773
  const status = error instanceof PostgresError ? error.sqlState : "XX000";
24560
24774
  this.options.onLog?.({ pid: this.pid, sql: text, durationMs: performance.now() - started, status });
24561
24775
  throw error;
@@ -24580,7 +24794,7 @@ var Connection = class {
24580
24794
  if (!this.inTransaction) this.flushNotifies();
24581
24795
  return command("NOTIFY");
24582
24796
  }
24583
- const db = this.cluster.db;
24797
+ if (CLUSTER_DDL.test(text)) return this.executeClusterDdl(text);
24584
24798
  for (; ; ) {
24585
24799
  if (this.cancelled) throw pgError2("internal", "canceling statement due to user request", "57014");
24586
24800
  await this.cluster.acquireTurn(this);
@@ -24590,7 +24804,7 @@ var Connection = class {
24590
24804
  faults.delayStatementMs = void 0;
24591
24805
  await new Promise((resolve) => setTimeout(resolve, ms));
24592
24806
  }
24593
- if (faults.dropConnection && db.transactions.inTransaction) {
24807
+ if (faults.dropConnection && this.inTransaction) {
24594
24808
  faults.dropConnection = void 0;
24595
24809
  this.destroy();
24596
24810
  throw new ConnectionDropped();
@@ -24602,7 +24816,7 @@ var Connection = class {
24602
24816
  } catch (error) {
24603
24817
  if (error instanceof LockWait) {
24604
24818
  const key = error.key;
24605
- if (!db.transactions.inTransaction) this.cluster.releaseTurn(this);
24819
+ this.cluster.releaseTurn(this);
24606
24820
  await this.waitForLock(key);
24607
24821
  continue;
24608
24822
  }
@@ -24632,57 +24846,196 @@ var Connection = class {
24632
24846
  }
24633
24847
  /** The engine call itself: statement-atomic, and transaction control handled as the server sees it. */
24634
24848
  runStatement(text, params, control) {
24635
- const db = this.cluster.db;
24636
- const tx = db.transactions;
24637
24849
  const faults = this.options.faults;
24638
24850
  if (control && COMMIT.test(text)) {
24639
24851
  if (this.aborted) {
24640
- tx.rollback();
24852
+ this.transaction = null;
24641
24853
  this.aborted = false;
24642
24854
  return command("ROLLBACK");
24643
24855
  }
24644
24856
  if (faults.failCommit !== void 0) {
24645
24857
  const code = faults.failCommit;
24646
24858
  faults.failCommit = void 0;
24647
- tx.rollback();
24859
+ this.transaction = null;
24648
24860
  throw pgError2("internal", "could not serialize access due to concurrent update", code);
24649
24861
  }
24650
24862
  }
24651
24863
  if (control && this.aborted && /^(rollback|abort)\b/i.test(text) && !/\bto\b/i.test(text)) {
24652
- tx.rollback();
24864
+ this.transaction = null;
24653
24865
  this.aborted = false;
24654
24866
  return command("ROLLBACK");
24655
24867
  }
24656
- const run = () => {
24657
- const result = this.cluster.as(this, () => db.prepare(text).textResult(...params));
24658
- return { empty: false, result, tag: commandTag(result) };
24659
- };
24660
- if (control) return run();
24661
- if (tx.inTransaction) {
24662
- const name = `__wire_${this.pid}`;
24663
- tx.savepoint(name);
24664
- try {
24665
- const outcome = run();
24666
- tx.releaseSavepoint(name);
24667
- return outcome;
24668
- } catch (error) {
24669
- tx.rollbackToSavepoint(name);
24670
- tx.releaseSavepoint(name);
24671
- throw error;
24868
+ if (control) return this.transactionControl(text);
24869
+ const workspace = this.transaction === null ? null : this.openWorkspace();
24870
+ const db = workspace ?? this.database;
24871
+ try {
24872
+ const run = () => {
24873
+ const result = this.cluster.as(this, () => this.runQuery(db, text, params));
24874
+ return { empty: false, result, tag: commandTag(result) };
24875
+ };
24876
+ const outcome = db.transaction(run);
24877
+ if (this.transaction && mutatesDatabase(text)) {
24878
+ this.transaction.statements.push({ kind: "sql", sql: text, params: params.slice() });
24672
24879
  }
24880
+ return outcome;
24881
+ } finally {
24882
+ workspace?.close();
24673
24883
  }
24674
- return db.transaction(run);
24675
24884
  }
24676
- /** Release the turn and transaction-scoped locks once no block is open; deliver NOTIFYs at commit. */
24885
+ /** Release the statement turn; transaction-scoped locks live until COMMIT / ROLLBACK. */
24677
24886
  afterStatement() {
24678
- if (this.cluster.db.transactions.inTransaction) return;
24679
- this.cluster.unlockAll(this, true);
24680
24887
  this.cluster.releaseTurn(this);
24681
- this.flushNotifies();
24888
+ if (!this.inTransaction) {
24889
+ this.cluster.unlockAll(this, true);
24890
+ this.flushNotifies();
24891
+ }
24892
+ }
24893
+ transactionControl(text) {
24894
+ const stmt = parse(text)[0];
24895
+ if (stmt?.type !== "transaction") throw pgError2("syntax", "invalid transaction command", "42601");
24896
+ switch (stmt.action) {
24897
+ case "begin":
24898
+ this.transaction ??= { statements: [], savepoints: [] };
24899
+ return command("BEGIN");
24900
+ case "commit": {
24901
+ const transaction = this.transaction;
24902
+ this.transaction = null;
24903
+ if (transaction) {
24904
+ this.database.transaction(() => {
24905
+ for (const logged of transaction.statements) this.replay(this.database, logged);
24906
+ });
24907
+ }
24908
+ return command("COMMIT");
24909
+ }
24910
+ case "rollback":
24911
+ this.transaction = null;
24912
+ return command("ROLLBACK");
24913
+ case "savepoint": {
24914
+ if (!this.transaction) throw pgError2("internal", "SAVEPOINT can only be used in transaction blocks", "25P01");
24915
+ this.transaction.savepoints.push({
24916
+ name: stmt.savepointName,
24917
+ statementCount: this.transaction.statements.length
24918
+ });
24919
+ return command("SAVEPOINT");
24920
+ }
24921
+ case "release": {
24922
+ const index = this.findSavepoint(stmt.savepointName);
24923
+ this.transaction?.savepoints.splice(index);
24924
+ return command("RELEASE");
24925
+ }
24926
+ case "rollback_to": {
24927
+ const index = this.findSavepoint(stmt.savepointName);
24928
+ const savepoint = this.transaction?.savepoints[index];
24929
+ if (!this.transaction || !savepoint) throw pgError2("internal", "savepoint does not exist", "3B001");
24930
+ this.transaction.statements.splice(savepoint.statementCount);
24931
+ this.transaction.savepoints.splice(index + 1);
24932
+ this.aborted = false;
24933
+ return command("ROLLBACK");
24934
+ }
24935
+ }
24936
+ }
24937
+ findSavepoint(name) {
24938
+ if (!this.transaction) throw pgError2("internal", "SAVEPOINT can only be used in transaction blocks", "25P01");
24939
+ for (let i = this.transaction.savepoints.length - 1; i >= 0; i--) {
24940
+ if (this.transaction.savepoints[i]?.name === name) return i;
24941
+ }
24942
+ throw pgError2("internal", `savepoint "${name}" does not exist`, "3B001");
24943
+ }
24944
+ /** Rebase this transaction's successful writes onto the latest committed state. */
24945
+ openWorkspace() {
24946
+ const workspace = this.database.branch();
24947
+ setDatabaseCatalogContext(workspace.state, {
24948
+ name: this.databaseName,
24949
+ names: () => this.cluster.databaseNames()
24950
+ });
24951
+ for (const logged of this.transaction?.statements ?? []) {
24952
+ workspace.transaction(() => this.replay(workspace, logged));
24953
+ }
24954
+ return workspace;
24955
+ }
24956
+ replay(db, logged) {
24957
+ this.cluster.as(this, () => {
24958
+ if (logged.kind === "sql") db.prepare(logged.sql).textResult(...logged.params);
24959
+ else {
24960
+ const env = { ctx: new EngineCtx(db.state), params: null, ctes: /* @__PURE__ */ new Map(), outer: null };
24961
+ executeCopyFromData(env, logged.stmt, logged.data);
24962
+ }
24963
+ });
24964
+ }
24965
+ runQuery(db, text, params) {
24966
+ const lock = ROW_LOCK.exec(text);
24967
+ if (!lock) {
24968
+ const mutation = UPDATE_TARGET.exec(text) ?? DELETE_TARGET.exec(text);
24969
+ if (mutation) {
24970
+ const lockQuery = `SELECT * FROM ${mutation[1]}${mutation[2] ? ` WHERE ${mutation[2]}` : ""}`;
24971
+ this.lockRows(db, lockQuery, params, "wait");
24972
+ }
24973
+ return db.prepare(text).textResult(...params);
24974
+ }
24975
+ let query = text.slice(0, lock.index).trimEnd();
24976
+ const limitMatch = LIMIT.exec(query);
24977
+ const limit = limitMatch ? Number(limitMatch[1]) : Number.POSITIVE_INFINITY;
24978
+ const offset = limitMatch ? Number(limitMatch[2] ?? 0) : 0;
24979
+ if (limitMatch) query = query.slice(0, limitMatch.index).trimEnd();
24980
+ return this.lockRows(db, query, params, lock[1]?.toLowerCase().replace(/\s+/g, "_") ?? "wait", limit, offset);
24981
+ }
24982
+ lockRows(db, query, params, policy, limit = Number.POSITIVE_INFINITY, offset = 0) {
24983
+ const result = db.prepare(query).textResult(...params);
24984
+ const rows = [];
24985
+ for (const row of result.rows.slice(offset)) {
24986
+ const key = this.rowLockKey(db, query, result.columns, row);
24987
+ if (this.cluster.tryLock(this, key, true)) {
24988
+ rows.push(row);
24989
+ if (rows.length >= limit) break;
24990
+ continue;
24991
+ }
24992
+ if (policy === "skip_locked") continue;
24993
+ if (policy === "nowait") {
24994
+ throw pgError2("internal", "could not obtain lock on row in relation", "55P03");
24995
+ }
24996
+ throw new LockWait(key);
24997
+ }
24998
+ return { ...result, rows, rowCount: rows.length };
24999
+ }
25000
+ rowLockKey(db, query, columns, row) {
25001
+ const rawName = FROM_TABLE.exec(query)?.[1] ?? "query";
25002
+ const parts = rawName.split(".").map((part) => part.startsWith('"') ? part.slice(1, -1).replaceAll('""', '"') : part.toLowerCase());
25003
+ const table = db.state.findTable(parts);
25004
+ const primary = table?.constraints.find((constraint) => constraint.kind === "primary_key");
25005
+ const indexes = primary?.columns.map((column) => columns.indexOf(column)) ?? [];
25006
+ const identity = indexes.length > 0 && indexes.every((index) => index >= 0) ? indexes.map((i) => row[i]) : row;
25007
+ return `row:${this.databaseName}:${parts.join(".")}:${JSON.stringify(identity)}`;
24682
25008
  }
24683
25009
  flushNotifies() {
24684
25010
  for (const { channel, payload } of this.pendingNotifies.splice(0)) this.cluster.notify(this, channel, payload);
24685
25011
  }
25012
+ executeClusterDdl(text) {
25013
+ if (this.inTransaction) {
25014
+ throw pgError2("internal", "CREATE/DROP/ALTER DATABASE cannot run inside a transaction block", "25001");
25015
+ }
25016
+ const create = CREATE_DATABASE.exec(text);
25017
+ if (create) {
25018
+ const name = identifier(create[1], create[2]);
25019
+ const template = create[3] === void 0 && create[4] === void 0 ? void 0 : identifier(create[3], create[4]);
25020
+ this.cluster.createDatabase(name, template);
25021
+ return command("CREATE DATABASE");
25022
+ }
25023
+ const drop = DROP_DATABASE.exec(text);
25024
+ if (drop) {
25025
+ const name = identifier(drop[2], drop[3]);
25026
+ if (drop[1] && !this.cluster.getDatabase(name)) return command("DROP DATABASE");
25027
+ this.cluster.dropDatabase(name, this);
25028
+ return command("DROP DATABASE");
25029
+ }
25030
+ const rename = RENAME_DATABASE.exec(text);
25031
+ if (rename) {
25032
+ const from = identifier(rename[1], rename[2]);
25033
+ const to = identifier(rename[3], rename[4]);
25034
+ this.cluster.renameDatabase(from, to, this);
25035
+ return command("ALTER DATABASE");
25036
+ }
25037
+ throw pgError2("syntax", "unsupported CREATE/DROP/ALTER DATABASE option", "0A000");
25038
+ }
24686
25039
  listen(channel) {
24687
25040
  const set = this.cluster.listeners.get(channel) ?? /* @__PURE__ */ new Set();
24688
25041
  set.add(this);
@@ -24703,6 +25056,24 @@ var Connection = class {
24703
25056
  this.readyForQuery();
24704
25057
  return;
24705
25058
  }
25059
+ if (statements.length === 1) {
25060
+ const copy = this.copyStatement(statements[0]);
25061
+ if (copy?.direction === "from") {
25062
+ await this.beginCopyIn(copy);
25063
+ return;
25064
+ }
25065
+ if (copy?.direction === "to") {
25066
+ try {
25067
+ const outcome = await this.execute(statements[0]);
25068
+ this.sendCopyOut(outcome, this.copyColumnCount(copy));
25069
+ } catch (error) {
25070
+ this.write(backend.errorResponse(errorFields(error)));
25071
+ if (this.inTransaction) this.aborted = true;
25072
+ }
25073
+ this.readyForQuery();
25074
+ return;
25075
+ }
25076
+ }
24706
25077
  const implicit = statements.length > 1 && !this.inTransaction && !statements.some((s) => TX_CONTROL.test(s));
24707
25078
  let failed = false;
24708
25079
  try {
@@ -24723,6 +25094,138 @@ var Connection = class {
24723
25094
  if (failed && this.inTransaction) this.aborted = true;
24724
25095
  this.readyForQuery();
24725
25096
  }
25097
+ copyStatement(sql) {
25098
+ try {
25099
+ const statements = parse(sql);
25100
+ const stmt = statements.length === 1 ? statements[0] : void 0;
25101
+ return stmt?.type === "copy" ? stmt : null;
25102
+ } catch {
25103
+ return null;
25104
+ }
25105
+ }
25106
+ copyColumnCount(stmt) {
25107
+ if (stmt.columns) return stmt.columns.length;
25108
+ if (stmt.table) return this.database.state.findTable(stmt.table)?.columns.length ?? 0;
25109
+ return 0;
25110
+ }
25111
+ async handleCopyIn(type, body) {
25112
+ const copy = this.copyIn;
25113
+ if (type === "d") {
25114
+ try {
25115
+ copy.buffer += copy.decoder.decode(body.rest(), { stream: true });
25116
+ this.flushCopyRecords(copy, false);
25117
+ } catch (error) {
25118
+ this.abortCopyIn(copy, error, true);
25119
+ }
25120
+ return;
25121
+ }
25122
+ if (type === "f") {
25123
+ const detail = body.cstring();
25124
+ this.abortCopyIn(copy, pgError2("internal", `COPY from stdin failed: ${detail}`, "57014"), false);
25125
+ return;
25126
+ }
25127
+ if (type === "c") {
25128
+ this.finishCopyIn(copy);
25129
+ return;
25130
+ }
25131
+ if (type === "H") return;
25132
+ if (type === "X") {
25133
+ this.socket.end();
25134
+ this.dispose();
25135
+ return;
25136
+ }
25137
+ this.fatal("08P01", `unexpected frontend message type ${JSON.stringify(type)} during COPY`);
25138
+ }
25139
+ async beginCopyIn(stmt) {
25140
+ await this.cluster.acquireTurn(this);
25141
+ const workspace = this.transaction ? this.openWorkspace() : null;
25142
+ const database = workspace ?? this.database;
25143
+ database.transactions.begin();
25144
+ this.copyIn = {
25145
+ stmt,
25146
+ database,
25147
+ workspace: workspace !== null,
25148
+ buffer: "",
25149
+ decoder: new TextDecoder(),
25150
+ rowCount: 0,
25151
+ headerPending: stmt.options.header === true || String(stmt.options.header ?? "").toLowerCase() === "true",
25152
+ loggedData: ""
25153
+ };
25154
+ this.write(backend.copyInResponse(this.copyColumnCount(stmt)));
25155
+ }
25156
+ finishCopyIn(copy) {
25157
+ const tx = copy.database.transactions;
25158
+ try {
25159
+ copy.buffer += copy.decoder.decode();
25160
+ this.flushCopyRecords(copy, true);
25161
+ tx.commit();
25162
+ if (copy.workspace) {
25163
+ this.transaction?.statements.push({ kind: "copy", stmt: copy.stmt, data: copy.loggedData });
25164
+ copy.database.close();
25165
+ }
25166
+ this.copyIn = null;
25167
+ this.write(backend.commandComplete(`COPY ${copy.rowCount}`));
25168
+ this.cluster.releaseTurn(this);
25169
+ this.readyForQuery();
25170
+ } catch (error) {
25171
+ this.abortCopyIn(copy, error, false);
25172
+ }
25173
+ }
25174
+ flushCopyRecords(copy, final) {
25175
+ const format = String(copy.stmt.options.format ?? "text").toLowerCase();
25176
+ const quote = String(copy.stmt.options.quote ?? '"');
25177
+ const boundaries = [];
25178
+ let quoted = false;
25179
+ for (let i = 0; i < copy.buffer.length; i++) {
25180
+ const char = copy.buffer[i];
25181
+ if (format === "csv" && char === quote) {
25182
+ if (quoted && copy.buffer[i + 1] === quote) i++;
25183
+ else quoted = !quoted;
25184
+ } else if (char === "\n" && !quoted) {
25185
+ boundaries.push(i + 1);
25186
+ }
25187
+ }
25188
+ if (final && quoted) {
25189
+ throw new PostgresError("invalid_text_representation", "unterminated CSV quoted field", "22P04");
25190
+ }
25191
+ const end = boundaries.at(-1) ?? 0;
25192
+ const data = final ? copy.buffer : end > 0 ? copy.buffer.slice(0, end) : "";
25193
+ if (data === "") return;
25194
+ copy.buffer = final ? "" : copy.buffer.slice(end);
25195
+ const stmt = copy.headerPending ? copy.stmt : { ...copy.stmt, options: { ...copy.stmt.options, header: false } };
25196
+ const env = {
25197
+ ctx: new EngineCtx(copy.database.state),
25198
+ params: null,
25199
+ ctes: /* @__PURE__ */ new Map(),
25200
+ outer: null
25201
+ };
25202
+ const result = this.cluster.as(this, () => executeCopyFromData(env, stmt, data));
25203
+ copy.rowCount += result.rowCount;
25204
+ copy.loggedData += data;
25205
+ copy.headerPending = false;
25206
+ }
25207
+ abortCopyIn(copy, error, drain) {
25208
+ const tx = copy.database.transactions;
25209
+ if (tx.inTransaction) tx.rollback();
25210
+ if (copy.workspace) copy.database.close();
25211
+ if (this.inTransaction) this.aborted = true;
25212
+ this.cluster.releaseTurn(this);
25213
+ this.copyIn = null;
25214
+ this.copyDrain = drain;
25215
+ this.write(backend.errorResponse(errorFields(error)));
25216
+ if (!drain) this.readyForQuery();
25217
+ }
25218
+ sendCopyOut(outcome, columns) {
25219
+ if (outcome.empty) throw pgError2("internal", "COPY TO produced no result", "XX000");
25220
+ this.write(backend.copyOutResponse(columns));
25221
+ for (const row of outcome.result.rows) {
25222
+ const line = row[0] ?? "";
25223
+ this.write(backend.copyData(utf83.encode(`${line}
25224
+ `)));
25225
+ }
25226
+ this.write(backend.copyDone());
25227
+ this.write(backend.commandComplete(outcome.tag));
25228
+ }
24726
25229
  sendOutcome(outcome, resultFormats, opts = { rowDescription: false }) {
24727
25230
  const from = opts.from ?? 0;
24728
25231
  const limit = opts.limit ?? 0;
@@ -24777,7 +25280,7 @@ var Connection = class {
24777
25280
  for (let i = 0; i < count; i++) paramOids.push(body.i32());
24778
25281
  const text = sql.trim();
24779
25282
  if (text !== "" && !LISTEN.test(text) && !UNLISTEN.test(text) && !NOTIFY.test(text) && !TX_CONTROL.test(text)) {
24780
- this.cluster.db.prepare(text);
25283
+ if (!CLUSTER_DDL.test(text)) this.database.prepare(text);
24781
25284
  }
24782
25285
  if (name !== "" && this.prepared.has(name)) {
24783
25286
  throw pgError2("internal", `prepared statement "${name}" already exists`, "42P05");
@@ -24836,7 +25339,7 @@ var Connection = class {
24836
25339
  */
24837
25340
  async shapeOf(prepared) {
24838
25341
  if (!RETURNS_ROWS.test(prepared.sql)) return null;
24839
- const db = this.cluster.db;
25342
+ const db = this.database;
24840
25343
  await this.cluster.acquireTurn(this);
24841
25344
  try {
24842
25345
  const params = prepared.paramOids.map(() => null);
@@ -24953,7 +25456,8 @@ var isSnapshot = (value) => typeof value === "object" && value !== null && "open
24953
25456
  var serve = (input = {}) => {
24954
25457
  const options = normalize(input);
24955
25458
  const database = isSnapshot(options.database) ? options.database.open() : options.database ?? new Database();
24956
- const cluster = new Cluster(database);
25459
+ const initialName = options.databaseName ?? "postgres";
25460
+ const cluster = new Cluster(database, initialName);
24957
25461
  const faults = {};
24958
25462
  const connections = /* @__PURE__ */ new Set();
24959
25463
  const host = options.host ?? "127.0.0.1";
@@ -24977,13 +25481,19 @@ var serve = (input = {}) => {
24977
25481
  server.removeListener("error", reject);
24978
25482
  const address = server.address();
24979
25483
  const port = typeof address === "object" && address !== null ? address.port : 0;
24980
- const name = options.databaseName ?? "postgres";
25484
+ const name = initialName;
24981
25485
  const auth = options.password ? `${encodeURIComponent(user)}:${encodeURIComponent(options.password)}@` : `${encodeURIComponent(user)}@`;
24982
25486
  resolve({
24983
25487
  port,
24984
25488
  host,
24985
25489
  connectionString: `postgres://${auth}${host}:${port}/${encodeURIComponent(name)}`,
24986
25490
  database,
25491
+ databaseNames() {
25492
+ return cluster.databaseNames();
25493
+ },
25494
+ getDatabase(databaseName) {
25495
+ return cluster.requireDatabase(databaseName);
25496
+ },
24987
25497
  server,
24988
25498
  get connections() {
24989
25499
  return connections.size;
@@ -24991,8 +25501,8 @@ var serve = (input = {}) => {
24991
25501
  fault(next) {
24992
25502
  Object.assign(faults, next);
24993
25503
  },
24994
- snapshot() {
24995
- return database.snapshot();
25504
+ snapshot(databaseName = initialName) {
25505
+ return cluster.requireDatabase(databaseName).snapshot();
24996
25506
  },
24997
25507
  close() {
24998
25508
  for (const connection of connections) connection.destroy();