1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
//! Postgres named-cursor helpers — DECLARE, FETCH, CLOSE.
//!
//! # What
//!
//! A [`PgCursor`] wraps the lifecycle of a Postgres named cursor opened inside
//! an active transaction. Postgres named cursors are transaction-local: they
//! live for the duration of the enclosing transaction, then vanish when the
//! transaction commits or rolls back (Postgres auto-closes them). This module
//! provides the low-level primitives that the higher-level `QuerySet::stream`
//! terminal builds on.
//!
//! # Why a separate module
//!
//! The cursor lifecycle (`DECLARE … CURSOR FOR …`, `FETCH n FROM …`,
//! `CLOSE …`) involves three distinct SQL statements and a state machine with
//! four meaningful states (`Declared`, `Fetching`, `Exhausted`, `Closed`).
//! Keeping this in its own file — rather than embedding the state machine
//! inside `stream.rs` — makes each file auditable: `cursor.rs` owns the
//! Postgres wire protocol for cursors, `stream.rs` owns the `Stream` trait
//! machinery and the glue between cursors and `FromPgRow` decoding.
//!
//! # Cursor-name format
//!
//! Every cursor gets a unique name of the form `djogi_cur_<32 hex chars>`.
//! The name is generated from a fresh `Uuid::new_v4()` using the
//! `simple()` formatter (no hyphens), prefixed by `djogi_cur_`. The result
//! is always an ASCII identifier containing only lowercase letters, digits,
//! and underscores — safe as an unquoted Postgres identifier. Validation
//! follows the rule: every byte after the prefix must be ASCII alphanumeric
//! (letter or digit); underscores are accepted only in the prefix. No regex
//! is used — just byte-level checks via `u8::is_ascii_alphanumeric`.
//!
//! # Transaction invariant
//!
//! Callers MUST only create a `PgCursor` from a `PgConnection` that
//! has an open transaction. Declaring a cursor outside a transaction is
//! a Postgres error (`ERROR: cursor can only scan forward`). The
//! `QuerySet::stream` terminal enforces this by checking
//! `ContextInner::Transaction` before calling `PgCursor::declare`.
//!
//! # Fetch size
//!
//! `FETCH <n> FROM <cursor>` retrieves up to `n` rows per round trip.
//! The default is `1000`. Callers may choose a smaller value (e.g. `100`)
//! for testing or memory-sensitive workloads; a larger value (e.g. `10_000`)
//! for high-throughput exports where network round-trip cost dominates.
use cratePgConnection;
use crate::;
use Row;
use Uuid;
/// A Postgres named cursor opened inside a transaction.
///
/// Created by [`PgCursor::declare`]. Rows are retrieved by [`PgCursor::fetch`].
/// The cursor must be closed by calling [`PgCursor::close`] when done; failing
/// to close is not a resource leak in the strictest sense (the transaction
/// ending closes it), but an explicit `CLOSE` is best practice and enables the
/// `stream_drop_closes_cursor` integration test to query `pg_cursors`.
///
/// `PgCursor` does NOT implement `Drop` to issue `CLOSE` because `close`
/// is async; the caller (the stream) must drive the close explicitly in its
/// drop logic via a separate mechanism. See `stream.rs` for the channel-based
/// close notification pattern.
/// Generate a unique cursor name.
///
/// The name is `djogi_cur_` followed by the `simple` (no-hyphens) form of
/// a new random UUID. All 32 hex characters after the prefix are ASCII
/// lowercase alphanumeric bytes. Total length: 10 + 32 = 42 bytes, well
/// within Postgres's 63-byte identifier limit.
///
/// Validation proof (no regex): The UUID simple form is always exactly
/// 32 lowercase hex digits (ASCII bytes in `[0-9a-f]`). Each byte satisfies
/// `b.is_ascii_alphanumeric()`. The prefix `djogi_cur_` contains only ASCII
/// letters and underscores. Therefore the whole name is a valid unquoted
/// Postgres identifier.
/// Issue `FETCH <fetch_size> FROM <cursor_name>` on `conn`.
///
/// Free function so stream types can call it with split borrows:
/// `cursor_fetch(self.cursor.name(), self.conn, self.fetch_size)` avoids
/// the borrow-checker conflict that arises when `fetch` is a method on
/// `PgCursor` taking `&mut PgConnection` while both `cursor` and `conn` are
/// fields of the same struct (the borrow checker cannot see they don't alias
/// through a shared `&mut self`).
pub async
/// Issue `CLOSE <cursor_name>` on `conn`.
///
/// Free function for the same split-borrow reason as [`cursor_fetch`].
pub async