| 1 | module pg |
| 2 | |
| 3 | import io |
| 4 | import time |
| 5 | |
| 6 | // DB is a thread-safe handle to a PostgreSQL database, backed by a pool of |
| 7 | // `Conn` objects. It mirrors Go's `database/sql.DB` design: methods on `DB` |
| 8 | // transparently acquire a conn for the call, then release it back to the |
| 9 | // pool. For operations that must run on the same physical connection |
| 10 | // (LISTEN/NOTIFY, session-scoped prepared statements, manual transactions), |
| 11 | // use `db.conn()` to pin a conn or `db.begin()` to start a transaction. |
| 12 | pub struct DB { |
| 13 | mut: |
| 14 | pool &Pool = unsafe { nil } |
| 15 | } |
| 16 | |
| 17 | // connect creates a new pool and opens an initial connection to verify the |
| 18 | // config works. The returned `&DB` is safe to share between threads. |
| 19 | pub fn connect(config Config, pcfg PoolConfig) !&DB { |
| 20 | return connect_with_conninfo(config.conninfo()!, pcfg)! |
| 21 | } |
| 22 | |
| 23 | // connect_with_conninfo is the conninfo-string variant of `connect`. |
| 24 | pub fn connect_with_conninfo(conninfo string, pcfg PoolConfig) !&DB { |
| 25 | mut db := &DB{ |
| 26 | pool: new_pool(conninfo, pcfg) |
| 27 | } |
| 28 | // Fail fast if the conninfo is wrong, rather than at first query. |
| 29 | probe := db.pool.acquire()! |
| 30 | db.pool.release(probe) |
| 31 | return db |
| 32 | } |
| 33 | |
| 34 | // close shuts down the pool and tears down all idle connections. |
| 35 | // In-flight conns will be closed when released. |
| 36 | pub fn (mut db DB) close() ! { |
| 37 | if isnil(db.pool) { |
| 38 | return |
| 39 | } |
| 40 | db.pool.close() |
| 41 | } |
| 42 | |
| 43 | // stats returns a snapshot of the pool state. |
| 44 | pub fn (mut db DB) stats() PoolStats { |
| 45 | return db.pool.stats() |
| 46 | } |
| 47 | |
| 48 | // set_max_open_conns caps the total number of open connections. |
| 49 | // A value of 0 means unlimited (the default, like Go). |
| 50 | pub fn (mut db DB) set_max_open_conns(n int) { |
| 51 | db.pool.set_max_open(n) |
| 52 | } |
| 53 | |
| 54 | // set_max_idle_conns caps the number of idle connections kept warm. |
| 55 | pub fn (mut db DB) set_max_idle_conns(n int) { |
| 56 | db.pool.set_max_idle(n) |
| 57 | } |
| 58 | |
| 59 | // set_conn_max_lifetime sets the maximum amount of time a conn may be reused. |
| 60 | // A value of zero means conns are reused indefinitely. |
| 61 | pub fn (mut db DB) set_conn_max_lifetime(d time.Duration) { |
| 62 | db.pool.set_conn_max_lifetime(d) |
| 63 | } |
| 64 | |
| 65 | // conn checks a conn out of the pool. The caller is responsible for calling |
| 66 | // `conn.close()` when done; failing to do so leaks the conn. Use this when |
| 67 | // you need session-bound operations like LISTEN/NOTIFY. |
| 68 | pub fn (mut db DB) conn() !&Conn { |
| 69 | return db.pool.acquire() |
| 70 | } |
| 71 | |
| 72 | // validate borrows a conn from the pool and checks it is alive. |
| 73 | pub fn (mut db DB) validate() !bool { |
| 74 | mut c := db.pool.acquire()! |
| 75 | defer { c.close() or {} } |
| 76 | return c.validate() |
| 77 | } |
| 78 | |
| 79 | // reset is a no-op kept for ORM compatibility. |
| 80 | pub fn (mut db DB) reset() ! { |
| 81 | } |
| 82 | |
| 83 | // ---- exec/query helpers (acquire-use-release) ---- |
| 84 | |
| 85 | // exec runs `query` on a pooled conn and returns the rows. |
| 86 | pub fn (mut db DB) exec(query string) ![]Row { |
| 87 | mut c := db.pool.acquire()! |
| 88 | defer { c.close() or {} } |
| 89 | return c.exec(query) |
| 90 | } |
| 91 | |
| 92 | // exec_no_null runs `query` and returns rows with no nullable fields. |
| 93 | pub fn (mut db DB) exec_no_null(query string) ![]RowNoNull { |
| 94 | mut c := db.pool.acquire()! |
| 95 | defer { c.close() or {} } |
| 96 | return c.exec_no_null(query) |
| 97 | } |
| 98 | |
| 99 | // exec_result runs `query` and returns a `Result` (rows + column index). |
| 100 | pub fn (mut db DB) exec_result(query string) !Result { |
| 101 | mut c := db.pool.acquire()! |
| 102 | defer { c.close() or {} } |
| 103 | return c.exec_result(query) |
| 104 | } |
| 105 | |
| 106 | // exec_one runs `query` and returns its first row. |
| 107 | pub fn (mut db DB) exec_one(query string) !Row { |
| 108 | mut c := db.pool.acquire()! |
| 109 | defer { c.close() or {} } |
| 110 | return c.exec_one(query) |
| 111 | } |
| 112 | |
| 113 | // exec_param_many runs `query` with the given parameters. |
| 114 | pub fn (mut db DB) exec_param_many(query string, params []string) ![]Row { |
| 115 | mut c := db.pool.acquire()! |
| 116 | defer { c.close() or {} } |
| 117 | return c.exec_param_many(query, params) |
| 118 | } |
| 119 | |
| 120 | // exec_param_many_result runs `query` with parameters and returns a `Result`. |
| 121 | pub fn (mut db DB) exec_param_many_result(query string, params []string) !Result { |
| 122 | mut c := db.pool.acquire()! |
| 123 | defer { c.close() or {} } |
| 124 | return c.exec_param_many_result(query, params) |
| 125 | } |
| 126 | |
| 127 | // exec_param runs `query` with a single `$1` parameter. |
| 128 | pub fn (mut db DB) exec_param(query string, param string) ![]Row { |
| 129 | return db.exec_param_many(query, [param]) |
| 130 | } |
| 131 | |
| 132 | // exec_param2 runs `query` with two parameters (`$1`, `$2`). |
| 133 | pub fn (mut db DB) exec_param2(query string, param string, param2 string) ![]Row { |
| 134 | return db.exec_param_many(query, [param, param2]) |
| 135 | } |
| 136 | |
| 137 | // q_int runs `query` and returns the first column of the first row as int. |
| 138 | pub fn (mut db DB) q_int(query string) !int { |
| 139 | mut c := db.pool.acquire()! |
| 140 | defer { c.close() or {} } |
| 141 | return c.q_int(query) |
| 142 | } |
| 143 | |
| 144 | // q_string runs `query` and returns the first column of the first row as string. |
| 145 | pub fn (mut db DB) q_string(query string) !string { |
| 146 | mut c := db.pool.acquire()! |
| 147 | defer { c.close() or {} } |
| 148 | return c.q_string(query) |
| 149 | } |
| 150 | |
| 151 | // q_strings runs `query` and returns the full row set (alias of `exec`). |
| 152 | pub fn (mut db DB) q_strings(query string) ![]Row { |
| 153 | return db.exec(query) |
| 154 | } |
| 155 | |
| 156 | // copy_expert runs a COPY command on a pooled conn. |
| 157 | pub fn (mut db DB) copy_expert(query string, mut file io.ReaderWriter) !int { |
| 158 | mut c := db.pool.acquire()! |
| 159 | defer { c.close() or {} } |
| 160 | return c.copy_expert(query, mut file) |
| 161 | } |
| 162 | |
| 163 | // ---- prepared statements ---- |
| 164 | // |
| 165 | // NOTE: prepared statements are session-scoped. Calling `prepare` on `DB` |
| 166 | // only registers the statement on the conn that happened to serve the call. |
| 167 | // Use `db.conn()` to pin a conn for prepare+exec_prepared cycles. |
| 168 | |
| 169 | // prepare registers a prepared statement on a transient conn. |
| 170 | // For repeated use, pin a conn via `db.conn()`. |
| 171 | pub fn (mut db DB) prepare(name string, query string, num_params int) ! { |
| 172 | mut c := db.pool.acquire()! |
| 173 | defer { c.close() or {} } |
| 174 | return c.prepare(name, query, num_params) |
| 175 | } |
| 176 | |
| 177 | // exec_prepared runs a previously-prepared statement. |
| 178 | pub fn (mut db DB) exec_prepared(name string, params []string) ![]Row { |
| 179 | mut c := db.pool.acquire()! |
| 180 | defer { c.close() or {} } |
| 181 | return c.exec_prepared(name, params) |
| 182 | } |
| 183 | |
| 184 | // exec_prepared_result runs a previously-prepared statement and returns a `Result`. |
| 185 | pub fn (mut db DB) exec_prepared_result(name string, params []string) !Result { |
| 186 | mut c := db.pool.acquire()! |
| 187 | defer { c.close() or {} } |
| 188 | return c.exec_prepared_result(name, params) |
| 189 | } |
| 190 | |
| 191 | // ---- transactions ---- |
| 192 | |
| 193 | // begin starts a new transaction and returns a `Tx` that pins a conn from |
| 194 | // the pool. The conn is released when `Tx.commit()` or `Tx.rollback()` is |
| 195 | // called. The default isolation level is REPEATABLE READ (matching the old |
| 196 | // single-conn API); pass `PQTransactionParam{ transaction_level: ... }` to |
| 197 | // override. |
| 198 | pub fn (mut db DB) begin(param PQTransactionParam) !&Tx { |
| 199 | mut c := db.pool.acquire()! |
| 200 | c.begin_on_conn(param) or { |
| 201 | c.close() or {} |
| 202 | return err |
| 203 | } |
| 204 | return &Tx{ |
| 205 | conn: c |
| 206 | } |
| 207 | } |
| 208 | |