v4 / vlib / db / pg / pg.c.v
1012 lines · 879 sloc · 29.62 KB · f581cbb1c964f89133439f4e303136998ddcfcfb
Raw
1module pg
2
3import io
4import orm
5import time
6
7$if $pkgconfig('libpq') {
8 #pkgconfig --cflags --libs libpq
9} $else {
10 $if msvc {
11 #flag -llibpq
12 } $else {
13 #flag -lpq
14 }
15 #flag linux -I/usr/include/postgresql
16 //#flag linux -Ipostgresql // cross compiling to linux
17
18 #flag darwin -I/opt/local/include/postgresql11
19 #flag darwin -L/opt/local/lib/postgresql11
20
21 #flag darwin -I/usr/local/opt/libpq/include
22 #flag darwin -L/usr/local/opt/libpq/lib
23
24 #flag darwin -I/opt/homebrew/include
25 #flag darwin -L/opt/homebrew/lib
26
27 #flag darwin -I/opt/homebrew/opt/libpq/include
28 #flag darwin -L/opt/homebrew/opt/libpq/lib
29
30 #flag windows -I @VEXEROOT/thirdparty/pg/libpq
31 #flag windows -L @VEXEROOT/thirdparty/pg/win64
32
33 #flag freebsd -I/usr/local/include
34 #flag freebsd -L/usr/local/lib
35
36 #flag openbsd -I/usr/local/include/postgresql
37 #flag openbsd -L/usr/local/lib
38}
39
40$if cross_compile ? && linux {
41 #include <libpq/libpq-fe.h>
42 #include <libpq/pg_config.h>
43
44 //#flag -lpq // libpq.a is located in LINUXROOT/lib/x86_64-linux-gnu/libpq.a
45 // The bundled linuxroot ships libpq.a but no libpgcommon.a / libpgport.a,
46 // so libpq's references to pg_snprintf, strlcpy, pg_freeaddrinfo_all etc.
47 // are unresolved at link time. Provide minimal stubs that delegate to libc.
48 #flag @VEXEROOT/thirdparty/pg/pgport_stubs.c
49} $else {
50 // PostgreSQL Source Code
51 // https://doxygen.postgresql.org/libpq-fe_8h.html
52 #include <libpq-fe.h>
53
54 // for PG_VERSION_NUM, which is defined everywhere at least since PG 9.5
55 #include <pg_config.h>
56}
57
58// for orm
59$if windows {
60 #include <winsock2.h>
61} $else {
62 #include <arpa/inet.h>
63}
64
65#include "@VMODROOT/vlib/db/pg/compatibility.h"
66
67// Conn is a single libpq connection. It is NOT safe for concurrent use by
68// multiple V threads (libpq enforces serial use of `PGconn*`). Use it pinned
69// for operations that require a specific connection (LISTEN/NOTIFY, prepared
70// statements scoped to the session, manual transactions). For pooled,
71// thread-safe access prefer `DB`, which checks out a Conn per call.
72@[heap]
73pub struct Conn {
74mut:
75 conn voidptr = unsafe { nil }
76 pool &Pool = unsafe { nil }
77 created_at time.Time
78 bad bool
79}
80
81pub struct Row {
82pub mut:
83 vals []?string
84}
85
86// val returns the value at `index`, flattening SQL NULL to an empty string.
87pub fn (row Row) val(index int) string {
88 if val := row.vals[index] {
89 return val
90 }
91 return ''
92}
93
94// values returns all row values, flattening SQL NULL to empty strings.
95pub fn (row Row) values() []string {
96 mut values := []string{cap: row.vals.len}
97 for val in row.vals {
98 values << if value := val { value } else { '' }
99 }
100 return values
101}
102
103// val_opt returns the raw optional value at `index`.
104pub fn (row Row) val_opt(index int) ?string {
105 return row.vals[index]
106}
107
108pub struct RowNoNull {
109pub mut:
110 vals []string
111}
112
113pub struct Result {
114pub:
115 cols map[string]int
116 names []string
117 rows []Row
118}
119
120// Notification represents a notification received from the server via LISTEN/NOTIFY
121pub struct Notification {
122pub:
123 channel string // notification channel name
124 pid int // process ID of notifying server process
125 payload string // notification payload string (may be empty)
126}
127
128pub struct Config {
129pub:
130 host string = 'localhost'
131 port int = 5432
132 user string
133 username string
134 password string
135 dbname string
136}
137
138//
139
140pub struct C.pg_result {}
141
142pub struct C.pg_conn {}
143
144@[typedef]
145pub struct C.PGresult {}
146
147@[typedef]
148pub struct C.PGconn {}
149
150// PGnotify represents a notification received from the server via LISTEN/NOTIFY
151@[typedef]
152pub struct C.PGnotify {
153 relname &char // notification channel name
154 be_pid int // process ID of notifying server process
155 extra &char // notification payload string
156}
157
158pub enum ConnStatusType {
159 ok = C.CONNECTION_OK
160 bad = C.CONNECTION_BAD
161 // Non-blocking mode only below here
162 // The existence of these should never be relied upon - they should only be used for user feedback or similar purposes.
163 started = C.CONNECTION_STARTED // Waiting for connection to be made.
164 made = C.CONNECTION_MADE // Connection OK; waiting to send.
165 awaiting_response = C.CONNECTION_AWAITING_RESPONSE // Waiting for a response from the postmaster.
166 auth_ok = C.CONNECTION_AUTH_OK // Received authentication; waiting for backend startup.
167 setenv = C.CONNECTION_SETENV // Negotiating environment.
168 ssl_startup = C.CONNECTION_SSL_STARTUP // Negotiating SSL.
169 needed = C.CONNECTION_NEEDED // Internal state: connect() needed . Available in PG 8
170 check_writable = C.CONNECTION_CHECK_WRITABLE // Check if we could make a writable connection. Available since PG 10
171 consume = C.CONNECTION_CONSUME // Wait for any pending message and consume them. Available since PG 10
172 gss_startup = C.CONNECTION_GSS_STARTUP // Negotiating GSSAPI; available since PG 12
173}
174
175@[typedef]
176pub enum ExecStatusType {
177 empty_query = C.PGRES_EMPTY_QUERY // empty query string was executed
178 command_ok = C.PGRES_COMMAND_OK // a query command that doesn't return anything was executed properly by the backend
179 tuples_ok = C.PGRES_TUPLES_OK // a query command that returns tuples was executed properly by the backend, PGresult contains the result tuples
180 copy_out = C.PGRES_COPY_OUT // Copy Out data transfer in progress
181 copy_in = C.PGRES_COPY_IN // Copy In data transfer in progress
182 bad_response = C.PGRES_BAD_RESPONSE // an unexpected response was recv'd from the backend
183 nonfatal_error = C.PGRES_NONFATAL_ERROR // notice or warning message
184 fatal_error = C.PGRES_FATAL_ERROR // query failed
185 copy_both = C.PGRES_COPY_BOTH // Copy In/Out data transfer in progress
186 single_tuple = C.PGRES_SINGLE_TUPLE // single tuple from larger resultset
187}
188
189//
190
191fn C.PQconnectdb(const_conninfo &char) &C.PGconn
192
193fn C.PQstatus(const_conn &C.PGconn) i32
194
195fn C.PQtransactionStatus(const_conn &C.PGconn) i32
196
197fn C.PQerrorMessage(const_conn &C.PGconn) &char
198
199fn C.PQexec(res &C.PGconn, const_query &char) &C.PGresult
200
201//
202
203fn C.PQgetisnull(const_res &C.PGresult, i32, i32) i32
204
205fn C.PQgetvalue(const_res &C.PGresult, i32, i32) &char
206
207fn C.PQresultStatus(const_res &C.PGresult) i32
208
209fn C.PQntuples(const_res &C.PGresult) i32
210
211fn C.PQnfields(const_res &C.PGresult) i32
212
213fn C.PQfname(const_res &C.PGresult, i32) &char
214
215// Params:
216// const Oid *paramTypes
217// const char *const *paramValues
218// const int *paramLengths
219// const int *paramFormats
220fn C.PQexecParams(conn &C.PGconn, const_command &char, nParams i32, const_paramTypes &int, const_paramValues &char,
221 const_paramLengths &int, const_paramFormats &int, resultFormat i32) &C.PGresult
222
223fn C.PQputCopyData(conn &C.PGconn, const_buffer &char, nbytes i32) i32
224
225fn C.PQputCopyEnd(conn &C.PGconn, const_errmsg &char) i32
226
227fn C.PQgetCopyData(conn &C.PGconn, buffer &&char, async i32) i32
228
229fn C.PQprepare(conn &C.PGconn, const_stmtName &char, const_query &char, nParams i32, const_param_types &&char) &C.PGresult
230
231fn C.PQexecPrepared(conn &C.PGconn, const_stmtName &char, nParams i32, const_paramValues &char,
232 const_paramLengths &int, const_paramFormats &int, resultFormat i32) &C.PGresult
233
234// cleanup
235
236fn C.PQclear(res &C.PGresult)
237
238fn C.PQfreemem(ptr voidptr)
239
240fn C.PQfinish(conn &C.PGconn)
241
242// LISTEN/NOTIFY support
243fn C.PQnotifies(conn &C.PGconn) &C.PGnotify
244
245fn C.PQconsumeInput(conn &C.PGconn) i32
246
247fn C.PQsocket(conn &C.PGconn) i32
248
249fn C.PQescapeLiteral(conn &C.PGconn, str &char, length usize) &char
250
251fn conninfo_needs_quotes(value string) bool {
252 for ch in value {
253 if ch.is_space() || ch == `'` || ch == `\\` {
254 return true
255 }
256 }
257 return false
258}
259
260fn escape_conninfo_value(value string) string {
261 if !conninfo_needs_quotes(value) {
262 return value
263 }
264 mut escaped := []u8{cap: value.len + 2}
265 escaped << `'`
266 for ch in value {
267 if ch == `\\` || ch == `'` {
268 escaped << `\\`
269 }
270 escaped << ch
271 }
272 escaped << `'`
273 return escaped.bytestr()
274}
275
276// connection_user returns the configured username, accepting both `user` and `username`.
277pub fn (config Config) connection_user() !string {
278 if config.user != '' && config.username != '' && config.user != config.username {
279 return error('db.pg: Config.user and Config.username must match when both are set')
280 }
281 if config.user != '' {
282 return config.user
283 }
284 return config.username
285}
286
287fn (config Config) conninfo() !string {
288 mut parts := []string{cap: 5}
289 if config.host != '' {
290 parts << 'host=${escape_conninfo_value(config.host)}'
291 }
292 if config.port > 0 {
293 parts << 'port=${config.port}'
294 }
295 user := config.connection_user()!
296 if user != '' {
297 parts << 'user=${escape_conninfo_value(user)}'
298 }
299 if config.dbname != '' {
300 parts << 'dbname=${escape_conninfo_value(config.dbname)}'
301 }
302 if config.password != '' {
303 parts << 'password=${escape_conninfo_value(config.password)}'
304 }
305 return parts.join(' ')
306}
307
308// connect_slot opens a single libpq connection and returns it as an
309// `IdleSlot` (raw handle + creation timestamp). The pool wraps the slot in a
310// fresh `&Conn` per checkout so the wrapper cannot be revived by a stale
311// reference after release.
312fn connect_slot(conninfo string) !IdleSlot {
313 conn := C.PQconnectdb(&char(conninfo.str))
314 if conn == 0 {
315 return error('libpq memory allocation error')
316 }
317 status := unsafe { ConnStatusType(C.PQstatus(conn)) }
318 if status != .ok {
319 // We force the construction of a new string as the
320 // error message will be freed by the next `PQfinish` call
321 c_error_msg := unsafe { C.PQerrorMessage(conn).vstring() }
322 error_msg := '${c_error_msg}'
323 C.PQfinish(conn)
324 return error('Connection to a PG database failed: ${error_msg}')
325 }
326 return IdleSlot{
327 handle: conn
328 created_at: time.now()
329 }
330}
331
332// physical_close_handle tears down a raw libpq handle. Used by the pool when
333// it discards an expired/broken slot or unwinds during shutdown.
334fn physical_close_handle(handle voidptr) {
335 if handle != unsafe { nil } {
336 C.PQfinish(handle)
337 }
338}
339
340// slot_expired reports whether `slot` has lived longer than `max_lifetime`.
341// A `max_lifetime` of zero means "no limit".
342fn slot_expired(slot IdleSlot, max_lifetime time.Duration) bool {
343 if max_lifetime <= 0 {
344 return false
345 }
346 return time.now() - slot.created_at > max_lifetime
347}
348
349// slot_bad reports whether the libpq handle in `slot` is no longer usable.
350fn slot_bad(slot IdleSlot) bool {
351 if slot.bad {
352 return true
353 }
354 if slot.handle == unsafe { nil } {
355 return true
356 }
357 status := unsafe { ConnStatusType(C.PQstatus(slot.handle)) }
358 return status == .bad
359}
360
361// ensure_active errors if this wrapper has been detached from its handle.
362// The pool nils `c.conn` during release() so any stale `&Conn` kept by user
363// code becomes inert here — it can never reach the underlying PGconn*, even
364// if the pool has since handed that same physical handle to another caller.
365fn (c &Conn) ensure_active() ! {
366 if isnil(c.conn) {
367 return error('pg: operation on released Conn (was close() called?)')
368 }
369}
370
371// is_bad reports whether the underlying libpq connection has gone bad
372// (e.g. dropped TCP, server idle-timeout).
373pub fn (c &Conn) is_bad() bool {
374 if c.bad {
375 return true
376 }
377 if c.conn == unsafe { nil } {
378 return true
379 }
380 status := unsafe { ConnStatusType(C.PQstatus(c.conn)) }
381 return status == .bad
382}
383
384// is_expired reports whether the conn has lived longer than `max_lifetime`.
385// A `max_lifetime` of zero means "no limit".
386pub fn (c &Conn) is_expired(max_lifetime time.Duration) bool {
387 if max_lifetime <= 0 {
388 return false
389 }
390 return time.now() - c.created_at > max_lifetime
391}
392
393// physical_close unconditionally tears down the libpq connection.
394// It is unsafe because the caller must not use the conn afterwards.
395@[unsafe]
396fn (mut c Conn) physical_close() {
397 if c.conn != unsafe { nil } {
398 C.PQfinish(c.conn)
399 c.conn = unsafe { nil }
400 }
401}
402
403fn res_to_rows(res voidptr) []Row {
404 nr_rows := C.PQntuples(res)
405 nr_cols := C.PQnfields(res)
406
407 mut rows := []Row{}
408 for i in 0 .. nr_rows {
409 mut row := Row{}
410 for j in 0 .. nr_cols {
411 if C.PQgetisnull(res, i, j) != 0 {
412 row.vals << none
413 } else {
414 val := C.PQgetvalue(res, i, j)
415 row.vals << unsafe { cstring_to_vstring(val) }
416 }
417 }
418 rows << row
419 }
420
421 C.PQclear(res)
422 return rows
423}
424
425fn res_to_rows_no_null(res voidptr) []RowNoNull {
426 nr_rows := C.PQntuples(res)
427 nr_cols := C.PQnfields(res)
428 mut rows := []RowNoNull{}
429 for i in 0 .. nr_rows {
430 mut row := RowNoNull{}
431 for j in 0 .. nr_cols {
432 val := C.PQgetvalue(res, i, j)
433 row.vals << unsafe { cstring_to_vstring(val) }
434 }
435 rows << row
436 }
437 C.PQclear(res)
438 return rows
439}
440
441// res_to_result creates a `Result` struct out of a `C.PGresult` pointer
442fn res_to_result(res voidptr) Result {
443 nr_rows := C.PQntuples(res)
444 nr_cols := C.PQnfields(res)
445
446 mut cols := map[string]int{}
447 mut names := []string{}
448 for j in 0 .. nr_cols {
449 field_name := unsafe { cstring_to_vstring(C.PQfname(res, j)) }
450 cols[field_name] = j
451 names << field_name
452 }
453 mut rows := []Row{}
454 for i in 0 .. nr_rows {
455 mut row := Row{}
456 for j in 0 .. nr_cols {
457 if C.PQgetisnull(res, i, j) != 0 {
458 row.vals << none
459 } else {
460 val := C.PQgetvalue(res, i, j)
461 row.vals << unsafe { cstring_to_vstring(val) }
462 }
463 }
464 rows << row
465 }
466
467 C.PQclear(res)
468 return Result{cols, names, rows}
469}
470
471// close releases this conn back to its pool. Safe to call more than once:
472// `release` detaches the wrapper from the underlying handle on the first
473// call (nils `c.conn`), so any subsequent close or method invocation on the
474// same `&Conn` is a no-op or a benign error rather than a use-after-free.
475pub fn (mut c Conn) close() ! {
476 if isnil(c.pool) {
477 unsafe { c.physical_close() }
478 return
479 }
480 mut pool := unsafe { c.pool }
481 pool.release(c)
482}
483
484// q_int submit a command to the database server and
485// returns an the first field in the first tuple
486// converted to an int. If no row is found or on
487// command failure, an error is returned
488pub fn (c &Conn) q_int(query string) !int {
489 rows := c.exec(query)!
490 if rows.len == 0 {
491 return error('q_int "${query}" not found')
492 }
493 row := rows[0]
494 if row.vals.len == 0 {
495 return 0
496 }
497 val := row.vals[0]
498 return val or { '0' }.int()
499}
500
501// q_string submit a command to the database server and
502// returns an the first field in the first tuple
503// as a string. If no row is found or on
504// command failure, an error is returned
505pub fn (c &Conn) q_string(query string) !string {
506 rows := c.exec(query)!
507 if rows.len == 0 {
508 return error('q_string "${query}" not found')
509 }
510 row := rows[0]
511 if row.vals.len == 0 {
512 return ''
513 }
514 val := row.vals[0]
515 return val or { '' }
516}
517
518// q_strings submit a command to the database server and
519// returns the resulting row set. Alias of `exec`
520pub fn (c &Conn) q_strings(query string) ![]Row {
521 return c.exec(query)
522}
523
524// exec submits a command to the database server and wait for the result, returning an error on failure and a row set on success
525pub fn (c &Conn) exec(query string) ![]Row {
526 c.ensure_active()!
527 res := C.PQexec(c.conn, &char(query.str))
528 return c.handle_error_or_rows(res, 'exec')
529}
530
531// exec_no_null works like exec, but the fields can't be NULL, no optionals
532pub fn (c &Conn) exec_no_null(query string) ![]RowNoNull {
533 c.ensure_active()!
534 res := C.PQexec(c.conn, &char(query.str))
535 return c.handle_error_or_rows_no_null(res, 'exec')
536}
537
538// exec_result submits a command to the database server and wait for the result, returning an error on failure and a `Result` set on success
539pub fn (c &Conn) exec_result(query string) !Result {
540 c.ensure_active()!
541 res := C.PQexec(c.conn, &char(query.str))
542 return c.handle_error_or_result(res, 'exec_result')
543}
544
545fn rows_first_or_empty(rows []Row) !Row {
546 if rows.len == 0 {
547 return error('no row')
548 }
549 return rows[0]
550}
551
552// exec_one executes a query and returns its first row as a result, or an error on failure
553pub fn (c &Conn) exec_one(query string) !Row {
554 c.ensure_active()!
555 res := C.PQexec(c.conn, &char(query.str))
556 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
557 if e != '' {
558 c.mark_bad_if_disconnected()
559 return error('pg exec error: "${e}"')
560 }
561 row := rows_first_or_empty(res_to_rows(res))!
562 return row
563}
564
565// exec_param_many executes a query with the parameters provided as ($1), ($2), ($n)
566pub fn (c &Conn) exec_param_many(query string, params []string) ![]Row {
567 c.ensure_active()!
568 unsafe {
569 mut param_vals := []&char{len: params.len}
570 for i in 0 .. params.len {
571 param_vals[i] = &char(params[i].str)
572 }
573
574 res := C.PQexecParams(c.conn, &char(query.str), params.len, 0, param_vals.data, 0, 0, 0)
575 return c.handle_error_or_rows(res, 'exec_param_many')
576 }
577}
578
579// exec_param_many executes a query with the parameters provided as ($1), ($2), ($n) and returns a `Result`
580pub fn (c &Conn) exec_param_many_result(query string, params []string) !Result {
581 c.ensure_active()!
582 unsafe {
583 mut param_vals := []&char{len: params.len}
584 for i in 0 .. params.len {
585 param_vals[i] = &char(params[i].str)
586 }
587
588 res := C.PQexecParams(c.conn, &char(query.str), params.len, 0, param_vals.data, 0, 0, 0)
589 return c.handle_error_or_result(res, 'exec_param_many_result')
590 }
591}
592
593// exec_param executes a query with 1 parameter ($1), and returns either an error on failure, or the full result set on success
594pub fn (c &Conn) exec_param(query string, param string) ![]Row {
595 return c.exec_param_many(query, [param])
596}
597
598// exec_param2 executes a query with 2 parameters ($1) and ($2), and returns either an error on failure, or the full result set on success
599pub fn (c &Conn) exec_param2(query string, param string, param2 string) ![]Row {
600 return c.exec_param_many(query, [param, param2])
601}
602
603// prepare submits a request to create a prepared statement with the given parameters, and waits for completion. You must provide the number of parameters (`$1, $2, $3 ...`) used in the statement
604pub fn (c &Conn) prepare(name string, query string, num_params int) ! {
605 c.ensure_active()!
606 res :=
607 C.PQprepare(c.conn, &char(name.str), &char(query.str), num_params, 0) // defining param types is optional
608
609 return c.handle_error(res, 'prepare')
610}
611
612// exec_prepared sends a request to execute a prepared statement with given parameters, and waits for the result. The number of parameters must match with the parameters declared in the prepared statement.
613pub fn (c &Conn) exec_prepared(name string, params []string) ![]Row {
614 c.ensure_active()!
615 unsafe {
616 mut param_vals := []&char{len: params.len}
617 for i in 0 .. params.len {
618 param_vals[i] = &char(params[i].str)
619 }
620
621 res := C.PQexecPrepared(c.conn, &char(name.str), params.len, param_vals.data, 0, 0, 0)
622 return c.handle_error_or_rows(res, 'exec_prepared')
623 }
624}
625
626// exec_prepared sends a request to execute a prepared statement with given parameters, and waits for the result. The number of parameters must match with the parameters declared in the prepared statement.
627// returns `Result`
628pub fn (c &Conn) exec_prepared_result(name string, params []string) !Result {
629 c.ensure_active()!
630 unsafe {
631 mut param_vals := []&char{len: params.len}
632 for i in 0 .. params.len {
633 param_vals[i] = &char(params[i].str)
634 }
635
636 res := C.PQexecPrepared(c.conn, &char(name.str), params.len, param_vals.data, 0, 0, 0)
637 return c.handle_error_or_result(res, 'exec_prepared_result')
638 }
639}
640
641fn (c &Conn) mark_bad_if_disconnected() {
642 status := unsafe { ConnStatusType(C.PQstatus(c.conn)) }
643 if status == .bad {
644 unsafe {
645 mut mc := c
646 mc.bad = true
647 }
648 }
649}
650
651fn (c &Conn) handle_error_or_rows(res voidptr, elabel string) ![]Row {
652 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
653 if e != '' {
654 C.PQclear(res)
655 c.mark_bad_if_disconnected()
656 $if trace_pg_error ? {
657 eprintln('pg error: ${e}')
658 }
659 return error('pg ${elabel} error:\n${e}')
660 }
661 return res_to_rows(res)
662}
663
664fn (c &Conn) handle_error_or_rows_no_null(res voidptr, elabel string) ![]RowNoNull {
665 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
666 if e != '' {
667 C.PQclear(res)
668 c.mark_bad_if_disconnected()
669 $if trace_pg_error ? {
670 eprintln('pg error: ${e}')
671 }
672 return error('pg ${elabel} error:\n${e}')
673 }
674 return res_to_rows_no_null(res)
675}
676
677// hande_error_or_result is an internal function similar to handle_error_or_rows that returns `Result` instead of `[]Row`
678fn (c &Conn) handle_error_or_result(res voidptr, elabel string) !Result {
679 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
680 if e != '' {
681 C.PQclear(res)
682 c.mark_bad_if_disconnected()
683 $if trace_pg_error ? {
684 eprintln('pg error: ${e}')
685 }
686 return error('pg ${elabel} error:\n${e}')
687 }
688 return res_to_result(res)
689}
690
691fn (c &Conn) handle_error(res voidptr, elabel string) ! {
692 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
693 if e != '' {
694 C.PQclear(res)
695 c.mark_bad_if_disconnected()
696 $if trace_pg_error ? {
697 eprintln('pg error: ${e}')
698 }
699 return error('pg ${elabel} error:\n${e}')
700 }
701}
702
703// copy_expert executes COPY command
704// https://www.postgresql.org/docs/9.5/libpq-copy.html
705pub fn (c &Conn) copy_expert(query string, mut file io.ReaderWriter) !int {
706 c.ensure_active()!
707 mut res := C.PQexec(c.conn, &char(query.str))
708 status := unsafe { ExecStatusType(C.PQresultStatus(res)) }
709 defer {
710 C.PQclear(res)
711 }
712
713 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
714 if e != '' {
715 return error('pg copy error:\n${e}')
716 }
717
718 if status == .copy_in {
719 mut buf := []u8{len: 4 * 1024}
720 for {
721 n := file.read(mut buf) or {
722 msg := 'pg copy error: Failed to read from input'
723 C.PQputCopyEnd(c.conn, &char(msg.str))
724 return err
725 }
726 if n <= 0 {
727 break
728 }
729
730 code := C.PQputCopyData(c.conn, buf.data, n)
731 if code == -1 {
732 return error('pg copy error: Failed to send data, code=${code}')
733 }
734 }
735
736 code := C.PQputCopyEnd(c.conn, &char(unsafe { nil }))
737
738 if code != 1 {
739 return error('pg copy error: Failed to finish copy command, code: ${code}')
740 }
741 } else if status == .copy_out {
742 for {
743 address := &char(unsafe { nil })
744 n_bytes := C.PQgetCopyData(c.conn, &address, 0)
745 if n_bytes > 0 {
746 mut local_buf := []u8{len: n_bytes}
747 unsafe { C.memcpy(&u8(local_buf.data), address, n_bytes) }
748 file.write(local_buf) or {
749 C.PQfreemem(address)
750 return err
751 }
752 } else if n_bytes == -1 {
753 break
754 } else if n_bytes == -2 {
755 // consult PQerrorMessage for the reason
756 return error('pg copy error: read error')
757 }
758 if address != 0 {
759 C.PQfreemem(address)
760 }
761 }
762 }
763
764 return 0
765}
766
767fn pg_stmt_worker(c &Conn, query string, data orm.QueryData, where orm.QueryData) ![]Row {
768 mut param_types := []u32{}
769 mut param_vals := []&char{}
770 mut param_lens := []int{}
771 mut param_formats := []int{}
772
773 pg_stmt_binder(mut param_types, mut param_vals, mut param_lens, mut param_formats, data)
774 pg_stmt_binder(mut param_types, mut param_vals, mut param_lens, mut param_formats, where)
775
776 res := C.PQexecParams(c.conn, &char(query.str), param_vals.len, param_types.data,
777 param_vals.data, param_lens.data, param_formats.data, 0) // here, the last 0 means require text results, 1 - binary results
778 return c.handle_error_or_rows(res, 'orm_stmt_worker')
779}
780
781pub enum PQTransactionLevel {
782 read_uncommitted
783 read_committed
784 repeatable_read
785 serializable
786}
787
788@[params]
789pub struct PQTransactionParam {
790pub:
791 transaction_level PQTransactionLevel = .repeatable_read
792}
793
794// begin_on_conn begins a transaction on this single connection. Most callers
795// should use `DB.begin()` instead, which returns a `Tx` that owns the
796// underlying conn for the lifetime of the transaction.
797pub fn (c &Conn) begin_on_conn(param PQTransactionParam) ! {
798 c.ensure_active()!
799 mut sql_stmt := 'BEGIN TRANSACTION ISOLATION LEVEL '
800 match param.transaction_level {
801 .read_uncommitted { sql_stmt += 'READ UNCOMMITTED' }
802 .read_committed { sql_stmt += 'READ COMMITTED' }
803 .repeatable_read { sql_stmt += 'REPEATABLE READ' }
804 .serializable { sql_stmt += 'SERIALIZABLE' }
805 }
806
807 _ := C.PQexec(c.conn, &char(sql_stmt.str))
808 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
809 if e != '' {
810 c.mark_bad_if_disconnected()
811 return error('pg exec error: "${e}"')
812 }
813}
814
815// commit commits the current transaction on this connection.
816pub fn (c &Conn) commit() ! {
817 c.ensure_active()!
818 _ := C.PQexec(c.conn, c'COMMIT;')
819 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
820 if e != '' {
821 c.mark_bad_if_disconnected()
822 return error('pg exec error: "${e}"')
823 }
824}
825
826// rollback rolls back the current transaction on this connection.
827pub fn (c &Conn) rollback() ! {
828 c.ensure_active()!
829 _ := C.PQexec(c.conn, c'ROLLBACK;')
830 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
831 if e != '' {
832 c.mark_bad_if_disconnected()
833 return error('pg exec error: "${e}"')
834 }
835}
836
837// rollback_to rolls back to a specified savepoint on this connection.
838pub fn (c &Conn) rollback_to(savepoint string) ! {
839 c.ensure_active()!
840 if !savepoint.is_identifier() {
841 return error('savepoint should be a identifier string')
842 }
843 sql_stmt := 'ROLLBACK TO SAVEPOINT ${savepoint};'
844 _ := C.PQexec(c.conn, &char(sql_stmt.str))
845 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
846 if e != '' {
847 c.mark_bad_if_disconnected()
848 return error('pg exec error: "${e}"')
849 }
850}
851
852// savepoint creates a new savepoint on this connection.
853pub fn (c &Conn) savepoint(savepoint string) ! {
854 c.ensure_active()!
855 if !savepoint.is_identifier() {
856 return error('savepoint should be a identifier string')
857 }
858 sql_stmt := 'SAVEPOINT ${savepoint};'
859 _ := C.PQexec(c.conn, &char(sql_stmt.str))
860 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
861 if e != '' {
862 c.mark_bad_if_disconnected()
863 return error('pg exec error: "${e}"')
864 }
865}
866
867// release_savepoint releases a specified savepoint on this connection.
868pub fn (c &Conn) release_savepoint(savepoint string) ! {
869 c.ensure_active()!
870 if !savepoint.is_identifier() {
871 return error('savepoint should be a identifier string')
872 }
873 sql_stmt := 'RELEASE SAVEPOINT ${savepoint};'
874 _ := C.PQexec(c.conn, &char(sql_stmt.str))
875 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
876 if e != '' {
877 c.mark_bad_if_disconnected()
878 return error('pg exec error: "${e}"')
879 }
880}
881
882// validate checks if the connection is still usable
883pub fn (c &Conn) validate() !bool {
884 c.exec_one('SELECT 1')!
885 return true
886}
887
888// reset returns the connection to initial state for reuse
889pub fn (c &Conn) reset() ! {
890}
891
892// as_structs is a `Result` method that maps the results' rows based on the provided mapping function
893pub fn (res Result) as_structs[T](mapper fn (Result, Row) !T) ![]T {
894 mut typed := []T{}
895 for r in res.rows {
896 typed << mapper(res, r)!
897 }
898
899 return typed
900}
901
902// listen registers the connection to receive notifications on the specified channel.
903// After calling this, use consume_input() and get_notification() to receive notifications.
904pub fn (c &Conn) listen(channel string) ! {
905 c.ensure_active()!
906 if !channel.is_identifier() {
907 return error('channel name should be a valid identifier')
908 }
909 sql_stmt := 'LISTEN ${channel};'
910 _ := C.PQexec(c.conn, &char(sql_stmt.str))
911 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
912 if e != '' {
913 c.mark_bad_if_disconnected()
914 return error('pg listen error: "${e}"')
915 }
916}
917
918// unlisten unregisters the connection from receiving notifications on the specified channel.
919// Use unlisten_all() to unregister from all channels.
920pub fn (c &Conn) unlisten(channel string) ! {
921 c.ensure_active()!
922 if !channel.is_identifier() {
923 return error('channel name should be a valid identifier')
924 }
925 sql_stmt := 'UNLISTEN ${channel};'
926 _ := C.PQexec(c.conn, &char(sql_stmt.str))
927 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
928 if e != '' {
929 c.mark_bad_if_disconnected()
930 return error('pg unlisten error: "${e}"')
931 }
932}
933
934// unlisten_all unregisters the connection from all notification channels.
935pub fn (c &Conn) unlisten_all() ! {
936 c.ensure_active()!
937 _ := C.PQexec(c.conn, c'UNLISTEN *;')
938 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
939 if e != '' {
940 c.mark_bad_if_disconnected()
941 return error('pg unlisten error: "${e}"')
942 }
943}
944
945// notify sends a notification on the specified channel with an optional payload.
946// All connections currently listening on that channel will receive the notification.
947pub fn (c &Conn) notify(channel string, payload string) ! {
948 c.ensure_active()!
949 if !channel.is_identifier() {
950 return error('channel name should be a valid identifier')
951 }
952 mut sql_stmt := ''
953 if payload.len > 0 {
954 // Use PQescapeLiteral to safely escape the payload
955 escaped := C.PQescapeLiteral(c.conn, &char(payload.str), usize(payload.len))
956 if escaped == unsafe { nil } {
957 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
958 return error('pg notify error: failed to escape payload: "${e}"')
959 }
960 sql_stmt = unsafe { 'NOTIFY ${channel}, ' + escaped.vstring() + ';' }
961 C.PQfreemem(escaped)
962 } else {
963 sql_stmt = 'NOTIFY ${channel};'
964 }
965 _ := C.PQexec(c.conn, &char(sql_stmt.str))
966 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
967 if e != '' {
968 c.mark_bad_if_disconnected()
969 return error('pg notify error: "${e}"')
970 }
971}
972
973// consume_input reads any available input from the server.
974// This must be called before get_notification() to ensure pending notifications are processed.
975// Returns true on success, false if there was an error reading from the connection.
976pub fn (c &Conn) consume_input() !bool {
977 c.ensure_active()!
978 result := C.PQconsumeInput(c.conn)
979 if result == 0 {
980 e := unsafe { C.PQerrorMessage(c.conn).vstring() }
981 c.mark_bad_if_disconnected()
982 return error('pg consume_input error: "${e}"')
983 }
984 return true
985}
986
987// get_notification returns the next pending notification from the server, if any.
988// Returns none if there are no pending notifications.
989// You should call consume_input() before this to ensure all pending notifications are available.
990pub fn (c &Conn) get_notification() ?Notification {
991 c.ensure_active() or { return none }
992 notify := C.PQnotifies(c.conn)
993 if notify == unsafe { nil } {
994 return none
995 }
996 defer {
997 C.PQfreemem(notify)
998 }
999 return Notification{
1000 channel: unsafe { notify.relname.vstring() }
1001 pid: notify.be_pid
1002 payload: unsafe { notify.extra.vstring() }
1003 }
1004}
1005
1006// socket returns the file descriptor of the connection socket to the server.
1007// This is useful for applications that want to use select() or poll() to wait
1008// for notifications without blocking. Returns -1 if no valid socket.
1009pub fn (c &Conn) socket() int {
1010 c.ensure_active() or { return -1 }
1011 return C.PQsocket(c.conn)
1012}
1013