kern-orm 0.8.0 → 0.9.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.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { B as noOrder, C as toNode, D as isType, E as defineType, F as createNode, L as falseNode, N as defineGenericOperator, P as createList, S as toFunctionOperandNode, T as getBooleanType, U as trueNode, _ as isOperandSource, b as toCastValueNode, c as textOperators, d as orderingOperators, f as compare, g as isExpression, h as getType, k as equalityOperators, v as operandNode, x as toExpression, y as toBooleanExpression } from "./text-CIUxPWPl.mjs";
2
- import { A as literal, C as RequiredRelationError, D as createAggregateCall, E as countType, F as combineNodes, M as isColumnValues, N as not, O as createCountCall, P as or, S as NotFoundError, T as countProjection, _ as many, a as identifier, b as createTableFactory, c as defineSchema, d as requireDerived, f as addTerms, g as matchColumn, h as orderBy, i as createDatabase, j as and, k as toAggregateType, l as SelectQuery, m as desc, n as QueryError, o as param, p as asc, r as toDriverError, s as sql, t as ConnectionError, u as from, v as one, w as ResultDecodeError, x as defineTable, y as createStatement } from "./driver-error-Dx5R83Rp.mjs";
2
+ import { A as toAggregateType, C as NotFoundError, D as countType, E as countProjection, F as or, I as combineNodes, M as and, N as isColumnValues, O as createAggregateCall, P as not, S as defineTable, T as ResultDecodeError, _ as matchColumn, a as toDriverError, b as createStatement, c as sql, d as from, f as requireDerived, g as orderBy, h as desc, i as QueryError, j as literal, k as createCountCall, l as defineSchema, m as asc, n as TransactionConnectionError, o as identifier, p as addTerms, r as ConnectionError, s as param, t as createDatabase, u as SelectQuery, v as many, w as RequiredRelationError, x as createTableFactory, y as one } from "./database-t66EaIGE.mjs";
3
3
  // src/expression/constants.ts
4
4
  /** Condition that always holds, such as for a branch of `and` built in code: `and(check, isAdmin ? TRUE : other)`. */
5
5
  const TRUE = /* @__PURE__ */ toExpression(trueNode, /* @__PURE__ */ getBooleanType());
@@ -127,6 +127,277 @@ function readFilter(options, method) {
127
127
  if (!isOperandSource(filter)) throw new TypeError(`The filter of ${method} must be an expression or a query of one Boolean value`);
128
128
  return filter[operandNode]();
129
129
  }
130
+ // src/execution/error-condition-codes.ts
131
+ /**
132
+ * Condition names of the SQLSTATE codes of PostgreSQL errors, by code: the codes of type `E` with a condition name, which PL/pgSQL takes in `EXCEPTION WHEN`, under the headings of their classes.
133
+ *
134
+ * A code that ends in `000` is the condition of its class.
135
+ * A few names have two codes, such as `null_value_not_allowed`.
136
+ */
137
+ const conditionsByCode = {
138
+ "03000": "sql_statement_not_yet_complete",
139
+ "08000": "connection_exception",
140
+ "08003": "connection_does_not_exist",
141
+ "08006": "connection_failure",
142
+ "08001": "sqlclient_unable_to_establish_sqlconnection",
143
+ "08004": "sqlserver_rejected_establishment_of_sqlconnection",
144
+ "08007": "transaction_resolution_unknown",
145
+ "08P01": "protocol_violation",
146
+ "09000": "triggered_action_exception",
147
+ "0A000": "feature_not_supported",
148
+ "0B000": "invalid_transaction_initiation",
149
+ "0F000": "locator_exception",
150
+ "0F001": "invalid_locator_specification",
151
+ "0L000": "invalid_grantor",
152
+ "0LP01": "invalid_grant_operation",
153
+ "0P000": "invalid_role_specification",
154
+ "0Z000": "diagnostics_exception",
155
+ "0Z002": "stacked_diagnostics_accessed_without_active_handler",
156
+ "10608": "invalid_argument_for_xquery",
157
+ "20000": "case_not_found",
158
+ "21000": "cardinality_violation",
159
+ "22000": "data_exception",
160
+ "2202E": "array_subscript_error",
161
+ "22021": "character_not_in_repertoire",
162
+ "22008": "datetime_field_overflow",
163
+ "22012": "division_by_zero",
164
+ "22005": "error_in_assignment",
165
+ "2200B": "escape_character_conflict",
166
+ "22022": "indicator_overflow",
167
+ "22015": "interval_field_overflow",
168
+ "2201E": "invalid_argument_for_logarithm",
169
+ "22014": "invalid_argument_for_ntile_function",
170
+ "22016": "invalid_argument_for_nth_value_function",
171
+ "2201F": "invalid_argument_for_power_function",
172
+ "2201G": "invalid_argument_for_width_bucket_function",
173
+ "22018": "invalid_character_value_for_cast",
174
+ "22007": "invalid_datetime_format",
175
+ "22019": "invalid_escape_character",
176
+ "2200D": "invalid_escape_octet",
177
+ "22025": "invalid_escape_sequence",
178
+ "22P06": "nonstandard_use_of_escape_character",
179
+ "22010": "invalid_indicator_parameter_value",
180
+ "22023": "invalid_parameter_value",
181
+ "22013": "invalid_preceding_or_following_size",
182
+ "2201B": "invalid_regular_expression",
183
+ "2201W": "invalid_row_count_in_limit_clause",
184
+ "2201X": "invalid_row_count_in_result_offset_clause",
185
+ "2202H": "invalid_tablesample_argument",
186
+ "2202G": "invalid_tablesample_repeat",
187
+ "22009": "invalid_time_zone_displacement_value",
188
+ "2200C": "invalid_use_of_escape_character",
189
+ "2200G": "most_specific_type_mismatch",
190
+ "22004": "null_value_not_allowed",
191
+ "22002": "null_value_no_indicator_parameter",
192
+ "22003": "numeric_value_out_of_range",
193
+ "2200H": "sequence_generator_limit_exceeded",
194
+ "22026": "string_data_length_mismatch",
195
+ "22001": "string_data_right_truncation",
196
+ "22011": "substring_error",
197
+ "22027": "trim_error",
198
+ "22024": "unterminated_c_string",
199
+ "2200F": "zero_length_character_string",
200
+ "22P01": "floating_point_exception",
201
+ "22P02": "invalid_text_representation",
202
+ "22P03": "invalid_binary_representation",
203
+ "22P04": "bad_copy_file_format",
204
+ "22P05": "untranslatable_character",
205
+ "2200L": "not_an_xml_document",
206
+ "2200M": "invalid_xml_document",
207
+ "2200N": "invalid_xml_content",
208
+ "2200S": "invalid_xml_comment",
209
+ "2200T": "invalid_xml_processing_instruction",
210
+ "22030": "duplicate_json_object_key_value",
211
+ "22031": "invalid_argument_for_sql_json_datetime_function",
212
+ "22032": "invalid_json_text",
213
+ "22033": "invalid_sql_json_subscript",
214
+ "22034": "more_than_one_sql_json_item",
215
+ "22035": "no_sql_json_item",
216
+ "22036": "non_numeric_sql_json_item",
217
+ "22037": "non_unique_keys_in_a_json_object",
218
+ "22038": "singleton_sql_json_item_required",
219
+ "22039": "sql_json_array_not_found",
220
+ "2203A": "sql_json_member_not_found",
221
+ "2203B": "sql_json_number_not_found",
222
+ "2203C": "sql_json_object_not_found",
223
+ "2203D": "too_many_json_array_elements",
224
+ "2203E": "too_many_json_object_members",
225
+ "2203F": "sql_json_scalar_required",
226
+ "2203G": "sql_json_item_cannot_be_cast_to_target_type",
227
+ "23000": "integrity_constraint_violation",
228
+ "23001": "restrict_violation",
229
+ "23502": "not_null_violation",
230
+ "23503": "foreign_key_violation",
231
+ "23505": "unique_violation",
232
+ "23514": "check_violation",
233
+ "23P01": "exclusion_violation",
234
+ "24000": "invalid_cursor_state",
235
+ "25000": "invalid_transaction_state",
236
+ "25001": "active_sql_transaction",
237
+ "25002": "branch_transaction_already_active",
238
+ "25008": "held_cursor_requires_same_isolation_level",
239
+ "25003": "inappropriate_access_mode_for_branch_transaction",
240
+ "25004": "inappropriate_isolation_level_for_branch_transaction",
241
+ "25005": "no_active_sql_transaction_for_branch_transaction",
242
+ "25006": "read_only_sql_transaction",
243
+ "25007": "schema_and_data_statement_mixing_not_supported",
244
+ "25P01": "no_active_sql_transaction",
245
+ "25P02": "in_failed_sql_transaction",
246
+ "25P03": "idle_in_transaction_session_timeout",
247
+ "25P04": "transaction_timeout",
248
+ "26000": "invalid_sql_statement_name",
249
+ "27000": "triggered_data_change_violation",
250
+ "28000": "invalid_authorization_specification",
251
+ "28P01": "invalid_password",
252
+ "2B000": "dependent_privilege_descriptors_still_exist",
253
+ "2BP01": "dependent_objects_still_exist",
254
+ "2D000": "invalid_transaction_termination",
255
+ "2F000": "sql_routine_exception",
256
+ "2F005": "function_executed_no_return_statement",
257
+ "2F002": "modifying_sql_data_not_permitted",
258
+ "2F003": "prohibited_sql_statement_attempted",
259
+ "2F004": "reading_sql_data_not_permitted",
260
+ "34000": "invalid_cursor_name",
261
+ "38000": "external_routine_exception",
262
+ "38001": "containing_sql_not_permitted",
263
+ "38002": "modifying_sql_data_not_permitted",
264
+ "38003": "prohibited_sql_statement_attempted",
265
+ "38004": "reading_sql_data_not_permitted",
266
+ "39000": "external_routine_invocation_exception",
267
+ "39001": "invalid_sqlstate_returned",
268
+ "39004": "null_value_not_allowed",
269
+ "39P01": "trigger_protocol_violated",
270
+ "39P02": "srf_protocol_violated",
271
+ "39P03": "event_trigger_protocol_violated",
272
+ "3B000": "savepoint_exception",
273
+ "3B001": "invalid_savepoint_specification",
274
+ "3D000": "invalid_catalog_name",
275
+ "3F000": "invalid_schema_name",
276
+ "40000": "transaction_rollback",
277
+ "40002": "transaction_integrity_constraint_violation",
278
+ "40001": "serialization_failure",
279
+ "40003": "statement_completion_unknown",
280
+ "40P01": "deadlock_detected",
281
+ "42000": "syntax_error_or_access_rule_violation",
282
+ "42601": "syntax_error",
283
+ "42501": "insufficient_privilege",
284
+ "42846": "cannot_coerce",
285
+ "42803": "grouping_error",
286
+ "42P20": "windowing_error",
287
+ "42P19": "invalid_recursion",
288
+ "42830": "invalid_foreign_key",
289
+ "42602": "invalid_name",
290
+ "42622": "name_too_long",
291
+ "42939": "reserved_name",
292
+ "42804": "datatype_mismatch",
293
+ "42P18": "indeterminate_datatype",
294
+ "42P21": "collation_mismatch",
295
+ "42P22": "indeterminate_collation",
296
+ "42809": "wrong_object_type",
297
+ "428C9": "generated_always",
298
+ "42703": "undefined_column",
299
+ "42883": "undefined_function",
300
+ "42P01": "undefined_table",
301
+ "42P02": "undefined_parameter",
302
+ "42704": "undefined_object",
303
+ "42701": "duplicate_column",
304
+ "42P03": "duplicate_cursor",
305
+ "42P04": "duplicate_database",
306
+ "42723": "duplicate_function",
307
+ "42P05": "duplicate_prepared_statement",
308
+ "42P06": "duplicate_schema",
309
+ "42P07": "duplicate_table",
310
+ "42712": "duplicate_alias",
311
+ "42710": "duplicate_object",
312
+ "42702": "ambiguous_column",
313
+ "42725": "ambiguous_function",
314
+ "42P08": "ambiguous_parameter",
315
+ "42P09": "ambiguous_alias",
316
+ "42P10": "invalid_column_reference",
317
+ "42611": "invalid_column_definition",
318
+ "42P11": "invalid_cursor_definition",
319
+ "42P12": "invalid_database_definition",
320
+ "42P13": "invalid_function_definition",
321
+ "42P14": "invalid_prepared_statement_definition",
322
+ "42P15": "invalid_schema_definition",
323
+ "42P16": "invalid_table_definition",
324
+ "42P17": "invalid_object_definition",
325
+ "44000": "with_check_option_violation",
326
+ "53000": "insufficient_resources",
327
+ "53100": "disk_full",
328
+ "53200": "out_of_memory",
329
+ "53300": "too_many_connections",
330
+ "53400": "configuration_limit_exceeded",
331
+ "54000": "program_limit_exceeded",
332
+ "54001": "statement_too_complex",
333
+ "54011": "too_many_columns",
334
+ "54023": "too_many_arguments",
335
+ "55000": "object_not_in_prerequisite_state",
336
+ "55006": "object_in_use",
337
+ "55P02": "cant_change_runtime_param",
338
+ "55P03": "lock_not_available",
339
+ "55P04": "unsafe_new_enum_value_usage",
340
+ "57000": "operator_intervention",
341
+ "57014": "query_canceled",
342
+ "57P01": "admin_shutdown",
343
+ "57P02": "crash_shutdown",
344
+ "57P03": "cannot_connect_now",
345
+ "57P04": "database_dropped",
346
+ "57P05": "idle_session_timeout",
347
+ "58000": "system_error",
348
+ "58030": "io_error",
349
+ "58P01": "undefined_file",
350
+ "58P02": "duplicate_file",
351
+ "58P03": "file_name_too_long",
352
+ F0000: "config_file_error",
353
+ F0001: "lock_file_exists",
354
+ HV000: "fdw_error",
355
+ HV005: "fdw_column_name_not_found",
356
+ HV002: "fdw_dynamic_parameter_value_needed",
357
+ HV010: "fdw_function_sequence_error",
358
+ HV021: "fdw_inconsistent_descriptor_information",
359
+ HV024: "fdw_invalid_attribute_value",
360
+ HV007: "fdw_invalid_column_name",
361
+ HV008: "fdw_invalid_column_number",
362
+ HV004: "fdw_invalid_data_type",
363
+ HV006: "fdw_invalid_data_type_descriptors",
364
+ HV091: "fdw_invalid_descriptor_field_identifier",
365
+ HV00B: "fdw_invalid_handle",
366
+ HV00C: "fdw_invalid_option_index",
367
+ HV00D: "fdw_invalid_option_name",
368
+ HV090: "fdw_invalid_string_length_or_buffer_length",
369
+ HV00A: "fdw_invalid_string_format",
370
+ HV009: "fdw_invalid_use_of_null_pointer",
371
+ HV014: "fdw_too_many_handles",
372
+ HV001: "fdw_out_of_memory",
373
+ HV00P: "fdw_no_schemas",
374
+ HV00J: "fdw_option_name_not_found",
375
+ HV00K: "fdw_reply_handle",
376
+ HV00Q: "fdw_schema_not_found",
377
+ HV00R: "fdw_table_not_found",
378
+ HV00L: "fdw_unable_to_create_execution",
379
+ HV00M: "fdw_unable_to_create_reply",
380
+ HV00N: "fdw_unable_to_establish_connection",
381
+ P0000: "plpgsql_error",
382
+ P0001: "raise_exception",
383
+ P0002: "no_data_found",
384
+ P0003: "too_many_rows",
385
+ P0004: "assert_failure",
386
+ XX000: "internal_error",
387
+ XX001: "data_corrupted",
388
+ XX002: "index_corrupted"
389
+ };
390
+ // src/execution/error-condition.ts
391
+ /**
392
+ * Tells whether `error` is a `QueryError` of `condition`, including a `QueryError` of another copy of kern-orm.
393
+ *
394
+ * The condition of a class, such as `integrity_constraint_violation`, matches every error of its class, as in PL/pgSQL.
395
+ */
396
+ function isQueryError(error, condition) {
397
+ if (!(error instanceof QueryError)) return false;
398
+ const conditions = conditionsByCode;
399
+ return conditions[error.code] === condition || conditions[`${error.code.slice(0, 2)}000`] === condition;
400
+ }
130
401
  // src/query/reusable-expression.ts
131
402
  function defineExpression(_table, expression) {
132
403
  return expression;
@@ -157,4 +428,4 @@ const wordStart = /(?<=[\p{Ll}\p{N}])(?=\p{Lu})|(?<=\p{Lu})(?=\p{Lu}(?!s$)\p{Ll}
157
428
  function toSnakeCase(key) {
158
429
  return key.replace(wordStart, "_").toLowerCase();
159
430
  }
160
- export { ConnectionError, FALSE, NotFoundError, QueryError, RequiredRelationError, ResultDecodeError, TRUE, and, asc, avg, caseWhen, coalesce, compare, count, countDistinct, createDatabase, createStatement, createTableFactory, defineExpression, defineGenericOperator, defineHelper, defineSchema, defineTable, defineType, desc, equalityOperators, every, from, greatest, identifier, least, literal, many, matches, max, min, not, one, or, orderBy, orderingOperators, param, some, sql, stringAgg, sum, textOperators, toDriverError, toSnakeCase };
431
+ export { ConnectionError, FALSE, NotFoundError, QueryError, RequiredRelationError, ResultDecodeError, TRUE, TransactionConnectionError, and, asc, avg, caseWhen, coalesce, compare, count, countDistinct, createDatabase, createStatement, createTableFactory, defineExpression, defineGenericOperator, defineHelper, defineSchema, defineTable, defineType, desc, equalityOperators, every, from, greatest, identifier, isQueryError, least, literal, many, matches, max, min, not, one, or, orderBy, orderingOperators, param, some, sql, stringAgg, sum, textOperators, toDriverError, toSnakeCase };
@@ -1,4 +1,4 @@
1
- import { G as TestFixture, J as TestTransactionOptions, K as TestHelpers, Li as Driver, U as OpenedTestFixture, W as PendingTestFixture, Y as createTestHelpers, q as TestHooks } from "./index-B1aH5Ht0.mjs";
1
+ import { $ as TestHooks, J as OpenedTestFixture, Q as TestHelpers, Ui as Driver, X as TestFixture, Y as PendingTestFixture, Z as TestFixtureOptions, et as TestTransactionOptions, tt as createTestHelpers } from "./index-2z5iTzXf.mjs";
2
2
  /** Options of `describeDriverContract`: how to create the driver under test. */
3
3
  interface DriverContractOptions {
4
4
  /** Creates a driver over a new client of the test database with `max` connections. */
@@ -23,4 +23,4 @@ interface DriverContractRunner extends TestHooks {
23
23
  * They run in the empty database of its clients and leave it unchanged.
24
24
  */
25
25
  export declare function describeDriverContract(options: DriverContractOptions, runner: DriverContractRunner): void;
26
- export { type DriverContractOptions, type DriverContractRunner, type OpenedTestFixture, type PendingTestFixture, type TestFixture, type TestHelpers, type TestHooks, type TestTransactionOptions, createTestHelpers };
26
+ export { type DriverContractOptions, type DriverContractRunner, type OpenedTestFixture, type PendingTestFixture, type TestFixture, type TestFixtureOptions, type TestHelpers, type TestHooks, type TestTransactionOptions, createTestHelpers };
package/dist/testing.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as text, t as char } from "./text-CIUxPWPl.mjs";
2
- import { c as defineSchema, i as createDatabase, j as and, n as QueryError, o as param, s as sql, t as ConnectionError, x as defineTable, y as createStatement } from "./driver-error-Dx5R83Rp.mjs";
2
+ import { M as and, S as defineTable, b as createStatement, c as sql, i as QueryError, l as defineSchema, n as TransactionConnectionError, r as ConnectionError, s as param, t as createDatabase } from "./database-t66EaIGE.mjs";
3
3
  import { _ as timestamptz, a as numeric, b as array, d as jsonb, f as enumOf, g as timestamp, h as time, i as integer, l as inet, m as interval, n as bigint, p as date, r as doublePrecision, t as uuid, v as boolean, y as bytea } from "./types-D3d35qR2.mjs";
4
4
  import assert from "node:assert/strict";
5
5
  // src/execution/testing.ts
@@ -9,11 +9,11 @@ function createTestHelpers(database, hooks) {
9
9
  if (!coordinator.test) throw new Error("createTestHelpers requires a database created with test: true");
10
10
  return {
11
11
  testTransaction: (callback, options) => coordinator.testTransaction(callback, options),
12
- useTestFixture(seed) {
12
+ useTestFixture(seed, options) {
13
13
  let pending;
14
14
  let fixture;
15
15
  hooks.before(async () => {
16
- const current = coordinator.startTestFixture(seed);
16
+ const current = coordinator.startTestFixture(seed, options);
17
17
  pending = current;
18
18
  try {
19
19
  const opened = await current.fixture;
@@ -193,7 +193,6 @@ function describeDriverContract(options, runner) {
193
193
  try {
194
194
  const connection = await own.acquire();
195
195
  const { runStatement, selectValue } = createStatementRunners(connection);
196
- let failure;
197
196
  try {
198
197
  await runStatement("CREATE TEMP TABLE pg_order_test (id integer)");
199
198
  await runStatement("BEGIN");
@@ -204,11 +203,8 @@ function describeDriverContract(options, runner) {
204
203
  runStatement("ROLLBACK")
205
204
  ]);
206
205
  assert.equal(await selectValue("SELECT count(*) FROM pg_order_test"), "0");
207
- } catch (error) {
208
- failure = { error };
209
- throw error;
210
206
  } finally {
211
- await connection.release(failure);
207
+ await connection.release();
212
208
  }
213
209
  } finally {
214
210
  await own.close();
@@ -292,72 +288,103 @@ function describeDriverContract(options, runner) {
292
288
  await single.close();
293
289
  }
294
290
  });
295
- it("leaves no failed transaction on the pool after a release with its error", async () => {
296
- const driver = createDriver({ max: 1 });
291
+ it("returns a connection to the pool after PostgreSQL rejected its last statement outside a transaction", async () => {
292
+ const own = createDriver({ max: 1 });
297
293
  try {
298
- const connection = await driver.acquire();
299
- await connection.execute(createStatement("BEGIN"));
300
- const error = await connection.execute(createStatement("SELECT 1/0")).catch((error) => error);
301
- await connection.release({ error });
302
- await assert.rejects(driver.execute(createStatement("SAVEPOINT probe")), /transaction blocks/);
294
+ const connection = await own.acquire();
295
+ let pid;
296
+ try {
297
+ pid = await createStatementRunners(connection).selectValue("SELECT pg_backend_pid()");
298
+ await assert.rejects(connection.execute(createStatement("SELECT 1/0")), QueryError);
299
+ } finally {
300
+ await connection.release();
301
+ }
302
+ assert.equal(await createStatementRunners(own).selectValue("SELECT pg_backend_pid()"), pid);
303
303
  } finally {
304
- await driver.close();
304
+ await own.close();
305
305
  }
306
306
  });
307
- if (driver.closesReservedConnections) {
308
- for (const [state, text] of [["an open", "SELECT 1"], ["a failed", "SELECT 1/0"]]) it(`does not return a connection in ${state} transaction to the pool`, async () => {
309
- const driver = createDriver({ max: 1 });
307
+ for (const [state, text] of [["an open", "SELECT 1"], ["a failed", "SELECT 1/0"]]) it(`does not return a connection in ${state} transaction to the pool`, async () => {
308
+ const own = createDriver({ max: 1 });
309
+ try {
310
+ const connection = await own.acquire();
310
311
  try {
311
- const connection = await driver.acquire();
312
312
  await connection.execute(createStatement("BEGIN"));
313
313
  await connection.execute(createStatement(text)).catch(() => {});
314
- await connection.release({ error: /* @__PURE__ */ new Error("failure") });
315
- await assert.rejects(driver.execute(createStatement("SAVEPOINT probe")), /transaction blocks/);
316
314
  } finally {
317
- await driver.close();
315
+ await connection.release();
318
316
  }
319
- });
320
- it("rejects a transaction whose connection is lost and keeps the pool usable", async () => {
321
- const single = createDatabase({ driver: createDriver({ max: 1 }) });
322
- const { selectValue: selectValueOnSingle } = createStatementRunners(single);
317
+ await assert.rejects(own.execute(createStatement("SAVEPOINT probe")), /transaction blocks/);
318
+ } finally {
319
+ await own.close();
320
+ }
321
+ });
322
+ describe("a statement on the pool that leaves a transaction open", () => {
323
+ const cases = [
324
+ [
325
+ "a statement",
326
+ (own) => own.execute(createStatement("BEGIN")),
327
+ ConnectionError
328
+ ],
329
+ [
330
+ "a script",
331
+ (own) => own.executeScript("BEGIN; SELECT 1"),
332
+ ConnectionError
333
+ ],
334
+ [
335
+ "a script that fails in the transaction",
336
+ (own) => own.executeScript("BEGIN; SELECT 1/0"),
337
+ Error
338
+ ]
339
+ ];
340
+ for (const [name, run, rejection] of cases) it(`rejects ${name} and leaves no transaction on the pool`, async () => {
341
+ const own = createDriver({ max: 2 });
342
+ const held = [];
323
343
  try {
324
- const transaction = single.transaction(async () => {
325
- const pid = await selectValueOnSingle("SELECT pg_backend_pid()");
326
- await selectValue("SELECT pg_terminate_backend($1)", [pid]);
327
- await selectValueOnSingle("SELECT 1");
328
- });
329
- await assert.rejects(transaction, /Transaction rollback failed/);
330
- assert.equal(await single.transaction(() => selectValueOnSingle("SELECT 'usable'")), "usable");
344
+ await assert.rejects(run(own), rejection);
345
+ for (let count = 0; count < 2; count++) held.push(await own.acquire());
346
+ for (const connection of held) await assert.rejects(connection.execute(createStatement("SAVEPOINT probe")), /transaction blocks/);
331
347
  } finally {
332
- await single.close();
348
+ await Promise.allSettled(held.map(async (connection) => connection.release()));
349
+ await own.close();
333
350
  }
334
351
  });
335
- it("rejects a statement of a transaction whose connection is lost with ConnectionError", async () => {
336
- const own = createDriver({ max: 1 });
352
+ });
353
+ it("rejects a transaction whose connection is lost and keeps the pool usable", async () => {
354
+ const single = createDatabase({ driver: createDriver({ max: 1 }) });
355
+ const { selectValue: selectValueOnSingle } = createStatementRunners(single);
356
+ try {
357
+ const transaction = single.transaction(async () => {
358
+ const pid = await selectValueOnSingle("SELECT pg_backend_pid()");
359
+ await selectValue("SELECT pg_terminate_backend($1)", [pid]);
360
+ await selectValueOnSingle("SELECT 1");
361
+ });
362
+ await assert.rejects(transaction, (error) => error instanceof TransactionConnectionError && error.phase === "transaction");
363
+ assert.equal(await single.transaction(() => selectValueOnSingle("SELECT 'usable'")), "usable");
364
+ } finally {
365
+ await single.close();
366
+ }
367
+ });
368
+ it("rejects a statement of a transaction whose connection is lost with ConnectionError", async () => {
369
+ const own = createDriver({ max: 1 });
370
+ try {
371
+ const connection = await own.acquire();
372
+ const { runStatement, selectValue: selectValueOnConnection } = createStatementRunners(connection);
337
373
  try {
338
- const connection = await own.acquire();
339
- const { runStatement, selectValue: selectValueOnConnection } = createStatementRunners(connection);
340
- let failure;
341
- try {
342
- await runStatement("BEGIN");
343
- await runStatement("CREATE TEMP TABLE pg_lost_test (id integer) ON COMMIT DROP");
344
- const pid = await selectValueOnConnection("SELECT pg_backend_pid()");
345
- assert.equal(await selectValue("SELECT pg_terminate_backend($1, 5000)", [pid]), "t");
346
- await new Promise((resolve) => setTimeout(resolve, 50));
347
- const error = await runStatement("INSERT INTO pg_lost_test VALUES (1)").catch((error) => error);
348
- failure = { error };
349
- assert.ok(error instanceof ConnectionError, `Expected ConnectionError, got ${String(error)}`);
350
- } catch (error) {
351
- failure ??= { error };
352
- throw error;
353
- } finally {
354
- await connection.release(failure);
355
- }
374
+ await runStatement("BEGIN");
375
+ await runStatement("CREATE TEMP TABLE pg_lost_test (id integer) ON COMMIT DROP");
376
+ const pid = await selectValueOnConnection("SELECT pg_backend_pid()");
377
+ assert.equal(await selectValue("SELECT pg_terminate_backend($1, 5000)", [pid]), "t");
378
+ await new Promise((resolve) => setTimeout(resolve, 50));
379
+ const error = await runStatement("INSERT INTO pg_lost_test VALUES (1)").catch((error) => error);
380
+ assert.ok(error instanceof ConnectionError, `Expected ConnectionError, got ${String(error)}`);
356
381
  } finally {
357
- await own.close();
382
+ await connection.release();
358
383
  }
359
- });
360
- }
384
+ } finally {
385
+ await own.close();
386
+ }
387
+ });
361
388
  it("replaces an idle connection whose backend exited", async () => {
362
389
  const own = createDriver({ max: 2 });
363
390
  const held = [];
package/dist/types.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { E as AnyExpression, Gi as Type, Jr as HasEquality, Kr as EqualityOperator, Qr as ComparisonSignature, S as TextTypeOperators, Ui as BaseType, Yr as InSignature, aa as NoOperators, ia as MergeOperators, oa as OperatorDefinitions, qr as EqualityOperators, sa as GenericOperator, w as NumberTypeOperators, x as TextOperators } from "./index-B1aH5Ht0.mjs";
1
+ import { $r as EqualityOperators, A as AnyExpression, E as TextTypeOperators, Ji as BaseType, O as NumberTypeOperators, Qr as EqualityOperator, T as TextOperators, Xi as Type, da as OperatorDefinitions, ei as HasEquality, fa as GenericOperator, ii as ComparisonSignature, la as MergeOperators, ti as InSignature, ua as NoOperators } from "./index-2z5iTzXf.mjs";
2
2
  /**
3
3
  * Creates a one-dimensional array type of `element` values.
4
4
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kern-orm",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Fully typed relational query builder for PostgreSQL with ambient transactions and single-query relation loading",
5
5
  "keywords": [
6
6
  "orm",