@vibeorm/adapter-pg 5.0.1 → 5.1.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/README.md CHANGED
@@ -98,6 +98,16 @@ Behind any other transaction-mode pooler (PgBouncer before 1.21 or with `max_pre
98
98
 
99
99
  One case stays silent with every naming scheme: a statement prepared before DDL **shadows** a table. If a migration creates a table with the same name in a schema that comes earlier in the unchanged `search_path`, the prepared statement keeps reading the table it was parsed against until the connection is recycled. (A changed `search_path` itself is safe: PostgreSQL re-parses the statement.) Recycle connections after such a migration, as for the result-shape changes below.
100
100
 
101
+ ### Named raw statements
102
+
103
+ The raw path takes a caller-chosen statement name: `executeUnsafe({ text, values, name })`, which the runtime exposes as `$queryRawUnsafe({ text, values, name })` / `$executeRawUnsafe(…)`, `commitWith: { query, name }` and `commitQuery({ query, name })`. The name is honoured whatever `preparedStatements` says; without one, nothing is named.
104
+
105
+ - **Parse once per connection.** A plain query is sent as `{ name, text, values }`, so node-postgres parses it the first time on each connection. In a one-Sync batch (pipelined statements, the opening, the statement that rides with `COMMIT`) the step writes `Parse` with the name only when this connection has not prepared it, then `Bind` / `Execute` against the name. Both paths keep ONE record: node-postgres' own `parsedStatements` / `submittedNamedStatements`. A name counts as prepared only after its `ParseComplete` arrived; a Parse the server refused or skipped is forgotten. A statement prepared by a protocol Parse stays prepared when its transaction rolls back, and the record keeps it.
106
+ - **One text per name.** The first text a name is used with is its text for the pool's lifetime; a different text is refused before it is sent (`VIBE_VALIDATION`, `meta.reason: "statement-name-reused"`). Names are a fixed set: each one is prepared on every connection that runs it.
107
+ - **Name rules.** Not empty, at most 63 UTF-8 bytes (PostgreSQL truncates longer names), no NUL character (`VIBE_VALIDATION`).
108
+ - **Poolers.** A named statement must reach the backend that prepared it. Behind a transaction-mode pooler without prepared-statement support the call fails with `26000` / `42P05`, with the pooler hint above. Send no name there.
109
+ - **Schema changes.** A DDL change of a named statement's result shape answers `0A000`, as described next; recycle the connections.
110
+
101
111
  ### Schema changes with prepared statements
102
112
 
103
113
  **Recycle backend connections after a migration changes a prepared query's result shape.** PostgreSQL can reject the retained plan with SQLSTATE `0A000` (`cached plan must not change result type`), for example after changing the type of a selected column. `SELECT *` can also be affected by adding a column. `planCacheMode: "force_custom_plan"` does not prevent result-descriptor invalidation.
package/dist/batch.d.ts CHANGED
@@ -20,11 +20,28 @@
20
20
  * so parameters serialize exactly as `client.query` serializes them. Rows are
21
21
  * decoded HERE, with the adapter's query-local type parsers — never through
22
22
  * node-postgres' internal `lib/result.js`.
23
+ *
24
+ * Named steps (throughput request 1): a step with a `name` is parsed under
25
+ * that name only when this connection has not prepared it yet, and bound to
26
+ * the name either way. The record of prepared names is node-postgres' OWN
27
+ * (`connection.parsedStatements` / `submittedNamedStatements`, the objects its
28
+ * `Query` reads), kept exactly as `Query` keeps it: a name is "submitted" when
29
+ * its Parse is written, "parsed" only when its ParseComplete arrives (the
30
+ * connection's `parseComplete` event, counted per Parse this batch wrote), and
31
+ * forgotten when the server refused or skipped its Parse. So a name a plain
32
+ * query prepared is never parsed again here, and the other way round.
23
33
  */
24
34
  /** One statement of a batch. Values are the adapter's already-serialized params. */
25
35
  export type BatchStep = {
26
36
  readonly text: string;
27
37
  readonly values?: readonly unknown[];
38
+ /**
39
+ * A caller-chosen prepared-statement name (raw path, throughput request 1):
40
+ * parsed under it once per connection, bound to it every time. A text that
41
+ * differs from the one this connection already holds is refused before any
42
+ * step or bookkeeping change.
43
+ */
44
+ readonly name?: string;
28
45
  /**
29
46
  * Nobody reads this step's rows (statement-count pass EPIC 5: the final
30
47
  * statement that rides with COMMIT): they are not decoded, so no type parser
@@ -58,6 +75,16 @@ export type BatchOutcome = {
58
75
  * server did with the batch is unknown; `steps` lists only what was seen.
59
76
  */
60
77
  readonly transportError?: unknown;
78
+ /**
79
+ * With `transportError`: PROVABLY no byte of this batch reached the socket,
80
+ * so the server received none of its statements. Either node-postgres never
81
+ * submitted the batch (a client already not queryable or ending answers
82
+ * `query()` with `handleError` and never calls `submit`; a client that fails
83
+ * errors its queued queries the same way), or its stream was no longer
84
+ * writable when it was (node-postgres' `Connection._send` writes nothing to a
85
+ * stream that is not writable, and a socket never becomes writable again).
86
+ */
87
+ readonly unsent?: true;
61
88
  };
62
89
  /** The query-local type parser surface (the adapter's `EXACT_TYPES`). */
63
90
  export type BatchTypeParsers = {
@@ -93,6 +120,10 @@ export declare function prepareBatchValues(params: {
93
120
  * The Submittable. Construct with steps whose values are already prepared
94
121
  * ({@link prepareBatchValues}); pass to `client.query(batch)`; await
95
122
  * {@link PgBatch.done}.
123
+ *
124
+ * It has NO `name` property on purpose: node-postgres' own ParseComplete and
125
+ * ErrorResponse handlers record `activeQuery.name`, and a batch's names are
126
+ * recorded per step here instead.
96
127
  */
97
128
  export declare class PgBatch {
98
129
  /** Settles once: at the batch's ReadyForQuery, at its first server error, or at a transport failure. */
@@ -113,12 +144,35 @@ export declare class PgBatch {
113
144
  private rowError;
114
145
  private settled;
115
146
  private resolve;
147
+ /** The connection's statement record while a named Parse of this batch may still be answered. */
148
+ private book;
149
+ /** Every Parse this batch wrote, in order: the k-th ParseComplete answers the k-th entry. */
150
+ private parses;
151
+ private parsesAnswered;
152
+ private stopParseTracking;
153
+ /** Whether `submit` wrote this batch's messages to a writable stream ({@link BatchOutcome.unsent} otherwise). */
154
+ private written;
116
155
  constructor(params: {
117
156
  steps: readonly BatchStep[];
118
157
  types: BatchTypeParsers;
119
158
  });
120
- /** Called by node-postgres when the batch reaches the head of the client's queue. */
121
- submit(connection: unknown): void;
159
+ /**
160
+ * Called by node-postgres when the batch reaches the head of the client's
161
+ * queue. Returns an error, with nothing written, when a statement name
162
+ * conflicts or the stream is no longer writable. node-postgres then calls
163
+ * `handleError`, as for its own `Query.submit` refusals; `null` once written.
164
+ */
165
+ submit(connection: unknown): Error | null;
166
+ /** One ParseComplete: the oldest unanswered Parse of this batch succeeded — its name is prepared now. */
167
+ private answerParse;
168
+ /**
169
+ * No more ParseComplete can come for this batch (its ReadyForQuery, or a
170
+ * server error: the server skips every message up to the Sync). A Parse
171
+ * still unanswered was refused or skipped, so its name is NOT prepared:
172
+ * forget it, as node-postgres' `_handleErrorMessage` forgets a query's
173
+ * submitted name. Recording it would answer 26000 on its next use.
174
+ */
175
+ private endParseTracking;
122
176
  handleRowDescription(message: {
123
177
  fields: readonly RowDescriptionField[];
124
178
  }): void;