Improve db_postgres iterators (#18144)
* Fix pqSetSingleRowMode case. Add links to the docs * Add missing PGContextVisibility enum * Remove unused PGContextVisibility enum * Improve db_postgres iterators * Fix instantRows with DbColumns. Cosmetics. * Reduce copy&paste in db_postgres * Move pqclear inside loop
This commit is contained in:
parent
9df631a379
commit
3e57c2f780
1 changed files with 117 additions and 62 deletions
|
|
@ -96,7 +96,6 @@ type
|
||||||
## converted to nil.
|
## converted to nil.
|
||||||
InstantRow* = object ## a handle that can be
|
InstantRow* = object ## a handle that can be
|
||||||
res: PPGresult ## used to get a row's
|
res: PPGresult ## used to get a row's
|
||||||
line: int ## column text on demand
|
|
||||||
SqlPrepared* = distinct string ## a identifier for the prepared queries
|
SqlPrepared* = distinct string ## a identifier for the prepared queries
|
||||||
|
|
||||||
proc dbError*(db: DbConn) {.noreturn.} =
|
proc dbError*(db: DbConn) {.noreturn.} =
|
||||||
|
|
@ -184,6 +183,22 @@ proc setupQuery(db: DbConn, stmtName: SqlPrepared,
|
||||||
deallocCStringArray(arr)
|
deallocCStringArray(arr)
|
||||||
if pqResultStatus(result) != PGRES_TUPLES_OK: dbError(db)
|
if pqResultStatus(result) != PGRES_TUPLES_OK: dbError(db)
|
||||||
|
|
||||||
|
proc setupSingeRowQuery(db: DbConn, query: SqlQuery,
|
||||||
|
args: varargs[string]) =
|
||||||
|
if pqsendquery(db, dbFormat(query, args)) != 1:
|
||||||
|
dbError(db)
|
||||||
|
if pqSetSingleRowMode(db) != 1:
|
||||||
|
dbError(db)
|
||||||
|
|
||||||
|
proc setupSingeRowQuery(db: DbConn, stmtName: SqlPrepared,
|
||||||
|
args: varargs[string]) =
|
||||||
|
var arr = allocCStringArray(args)
|
||||||
|
if pqsendqueryprepared(db, stmtName.string, int32(args.len), arr, nil, nil, 0) != 1:
|
||||||
|
dbError(db)
|
||||||
|
if pqSetSingleRowMode(db) != 1:
|
||||||
|
dbError(db)
|
||||||
|
deallocCStringArray(arr)
|
||||||
|
|
||||||
proc prepare*(db: DbConn; stmtName: string, query: SqlQuery;
|
proc prepare*(db: DbConn; stmtName: string, query: SqlQuery;
|
||||||
nParams: int): SqlPrepared =
|
nParams: int): SqlPrepared =
|
||||||
## Creates a new `SqlPrepared` statement. Parameter substitution is done
|
## Creates a new `SqlPrepared` statement. Parameter substitution is done
|
||||||
|
|
@ -203,49 +218,70 @@ proc setRow(res: PPGresult, r: var Row, line, cols: int32) =
|
||||||
else:
|
else:
|
||||||
add(r[col], x)
|
add(r[col], x)
|
||||||
|
|
||||||
|
template fetchRows(db: DbConn): untyped =
|
||||||
|
var res: PPGresult = nil
|
||||||
|
while true:
|
||||||
|
res = pqgetresult(db)
|
||||||
|
if res == nil:
|
||||||
|
break
|
||||||
|
let status = pqresultStatus(res)
|
||||||
|
if status == PGRES_TUPLES_OK:
|
||||||
|
discard
|
||||||
|
elif status != PGRES_SINGLE_TUPLE:
|
||||||
|
dbError(db)
|
||||||
|
else:
|
||||||
|
let L = pqNfields(res)
|
||||||
|
var result = newRow(L)
|
||||||
|
setRow(res, result, 0, L)
|
||||||
|
yield result
|
||||||
|
pqclear(res)
|
||||||
|
|
||||||
iterator fastRows*(db: DbConn, query: SqlQuery,
|
iterator fastRows*(db: DbConn, query: SqlQuery,
|
||||||
args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
|
args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
|
||||||
## executes the query and iterates over the result dataset. This is very
|
## executes the query and iterates over the result dataset. This is very
|
||||||
## fast, but potentially dangerous: If the for-loop-body executes another
|
## fast, but potentially dangerous: If the for-loop-body executes another
|
||||||
## query, the results can be undefined. For Postgres it is safe though.
|
## query, the results can be undefined. For Postgres it is safe though.
|
||||||
var res = setupQuery(db, query, args)
|
setupSingeRowQuery(db, query, args)
|
||||||
var L = pqnfields(res)
|
fetchRows(db)
|
||||||
var result = newRow(L)
|
|
||||||
for i in 0'i32..pqntuples(res)-1:
|
|
||||||
setRow(res, result, i, L)
|
|
||||||
yield result
|
|
||||||
pqclear(res)
|
|
||||||
|
|
||||||
iterator fastRows*(db: DbConn, stmtName: SqlPrepared,
|
iterator fastRows*(db: DbConn, stmtName: SqlPrepared,
|
||||||
args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
|
args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
|
||||||
## executes the prepared query and iterates over the result dataset.
|
## executes the query and iterates over the result dataset. This is very
|
||||||
var res = setupQuery(db, stmtName, args)
|
## fast, but potentially dangerous: If the for-loop-body executes another
|
||||||
var L = pqNfields(res)
|
## query, the results can be undefined. For Postgres it is safe though.
|
||||||
var result = newRow(L)
|
setupSingeRowQuery(db, stmtName, args)
|
||||||
for i in 0'i32..pqNtuples(res)-1:
|
fetchRows(db)
|
||||||
setRow(res, result, i, L)
|
|
||||||
yield result
|
template fetchinstantRows(db: DbConn): untyped =
|
||||||
pqClear(res)
|
var res: PPGresult = nil
|
||||||
|
while true:
|
||||||
|
res = pqgetresult(db)
|
||||||
|
if res == nil:
|
||||||
|
break
|
||||||
|
let status = pqresultStatus(res)
|
||||||
|
if status == PGRES_TUPLES_OK:
|
||||||
|
discard
|
||||||
|
elif status != PGRES_SINGLE_TUPLE:
|
||||||
|
dbError(db)
|
||||||
|
else:
|
||||||
|
yield InstantRow(res: res)
|
||||||
|
pqclear(res)
|
||||||
|
|
||||||
iterator instantRows*(db: DbConn, query: SqlQuery,
|
iterator instantRows*(db: DbConn, query: SqlQuery,
|
||||||
args: varargs[string, `$`]): InstantRow
|
args: varargs[string, `$`]): InstantRow
|
||||||
{.tags: [ReadDbEffect].} =
|
{.tags: [ReadDbEffect].} =
|
||||||
## same as fastRows but returns a handle that can be used to get column text
|
## same as fastRows but returns a handle that can be used to get column text
|
||||||
## on demand using []. Returned handle is valid only within iterator body.
|
## on demand using []. Returned handle is valid only within iterator body.
|
||||||
var res = setupQuery(db, query, args)
|
setupSingeRowQuery(db, query, args)
|
||||||
for i in 0'i32..pqNtuples(res)-1:
|
fetchinstantRows(db)
|
||||||
yield InstantRow(res: res, line: i)
|
|
||||||
pqClear(res)
|
|
||||||
|
|
||||||
iterator instantRows*(db: DbConn, stmtName: SqlPrepared,
|
iterator instantRows*(db: DbConn, stmtName: SqlPrepared,
|
||||||
args: varargs[string, `$`]): InstantRow
|
args: varargs[string, `$`]): InstantRow
|
||||||
{.tags: [ReadDbEffect].} =
|
{.tags: [ReadDbEffect].} =
|
||||||
## same as fastRows but returns a handle that can be used to get column text
|
## same as fastRows but returns a handle that can be used to get column text
|
||||||
## on demand using []. Returned handle is valid only within iterator body.
|
## on demand using []. Returned handle is valid only within iterator body.
|
||||||
var res = setupQuery(db, stmtName, args)
|
setupSingeRowQuery(db, stmtName, args)
|
||||||
for i in 0'i32..pqNtuples(res)-1:
|
fetchinstantRows(db)
|
||||||
yield InstantRow(res: res, line: i)
|
|
||||||
pqClear(res)
|
|
||||||
|
|
||||||
proc getColumnType(res: PPGresult, col: int) : DbType =
|
proc getColumnType(res: PPGresult, col: int) : DbType =
|
||||||
## returns DbType for given column in the row
|
## returns DbType for given column in the row
|
||||||
|
|
@ -382,7 +418,7 @@ proc getColumnType(res: PPGresult, col: int) : DbType =
|
||||||
of 705: return DbType(kind: DbTypeKind.dbUnknown, name: "unknown")
|
of 705: return DbType(kind: DbTypeKind.dbUnknown, name: "unknown")
|
||||||
else: return DbType(kind: DbTypeKind.dbUnknown, name: $oid) ## Query the system table pg_type to determine exactly which type is referenced.
|
else: return DbType(kind: DbTypeKind.dbUnknown, name: $oid) ## Query the system table pg_type to determine exactly which type is referenced.
|
||||||
|
|
||||||
proc setColumnInfo(columns: var DbColumns; res: PPGresult; L: int32) =
|
proc setColumnInfo(columns: var DbColumns; res: PPGresult, L: int32) =
|
||||||
setLen(columns, L)
|
setLen(columns, L)
|
||||||
for i in 0'i32..<L:
|
for i in 0'i32..<L:
|
||||||
columns[i].name = $pqfname(res, i)
|
columns[i].name = $pqfname(res, i)
|
||||||
|
|
@ -395,59 +431,79 @@ proc setColumnInfo(columns: var DbColumns; res: PPGresult; L: int32) =
|
||||||
iterator instantRows*(db: DbConn; columns: var DbColumns; query: SqlQuery;
|
iterator instantRows*(db: DbConn; columns: var DbColumns; query: SqlQuery;
|
||||||
args: varargs[string, `$`]): InstantRow
|
args: varargs[string, `$`]): InstantRow
|
||||||
{.tags: [ReadDbEffect].} =
|
{.tags: [ReadDbEffect].} =
|
||||||
var res = setupQuery(db, query, args)
|
setupSingeRowQuery(db, query, args)
|
||||||
setColumnInfo(columns, res, pqnfields(res))
|
var res: PPGresult = nil
|
||||||
for i in 0'i32..<pqntuples(res):
|
var colsObtained = false
|
||||||
yield InstantRow(res: res, line: i)
|
while true:
|
||||||
pqClear(res)
|
res = pqgetresult(db)
|
||||||
|
if not colsObtained:
|
||||||
|
setColumnInfo(columns, res, pqnfields(res))
|
||||||
|
colsObtained = true
|
||||||
|
if res == nil:
|
||||||
|
break
|
||||||
|
let status = pqresultStatus(res)
|
||||||
|
if status == PGRES_TUPLES_OK:
|
||||||
|
discard
|
||||||
|
elif status != PGRES_SINGLE_TUPLE:
|
||||||
|
dbError(db)
|
||||||
|
else:
|
||||||
|
yield InstantRow(res: res)
|
||||||
|
pqclear(res)
|
||||||
|
|
||||||
proc `[]`*(row: InstantRow; col: int): string {.inline.} =
|
proc `[]`*(row: InstantRow; col: int): string {.inline.} =
|
||||||
## returns text for given column of the row
|
## returns text for given column of the row
|
||||||
$pqgetvalue(row.res, int32(row.line), int32(col))
|
$pqgetvalue(row.res, int32(0), int32(col))
|
||||||
|
|
||||||
proc unsafeColumnAt*(row: InstantRow, index: int): cstring {.inline.} =
|
proc unsafeColumnAt*(row: InstantRow, index: int): cstring {.inline.} =
|
||||||
## Return cstring of given column of the row
|
## Return cstring of given column of the row
|
||||||
pqgetvalue(row.res, int32(row.line), int32(index))
|
pqgetvalue(row.res, int32(0), int32(index))
|
||||||
|
|
||||||
proc len*(row: InstantRow): int {.inline.} =
|
proc len*(row: InstantRow): int {.inline.} =
|
||||||
## returns number of columns in the row
|
## returns number of columns in the row
|
||||||
int(pqNfields(row.res))
|
int(pqNfields(row.res))
|
||||||
|
|
||||||
proc getRow*(db: DbConn, query: SqlQuery,
|
proc getRow(res: PPGresult): Row =
|
||||||
args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
|
let L = pqnfields(res)
|
||||||
## retrieves a single row. If the query doesn't return any rows, this proc
|
|
||||||
## will return a Row with empty strings for each column.
|
|
||||||
var res = setupQuery(db, query, args)
|
|
||||||
var L = pqnfields(res)
|
|
||||||
result = newRow(L)
|
result = newRow(L)
|
||||||
if pqntuples(res) > 0:
|
if pqntuples(res) > 0:
|
||||||
setRow(res, result, 0, L)
|
setRow(res, result, 0, L)
|
||||||
pqclear(res)
|
pqclear(res)
|
||||||
|
|
||||||
|
proc getRow*(db: DbConn, query: SqlQuery,
|
||||||
|
args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
|
||||||
|
## retrieves a single row. If the query doesn't return any rows, this proc
|
||||||
|
## will return a Row with empty strings for each column.
|
||||||
|
let res = setupQuery(db, query, args)
|
||||||
|
getRow(res)
|
||||||
|
|
||||||
proc getRow*(db: DbConn, stmtName: SqlPrepared,
|
proc getRow*(db: DbConn, stmtName: SqlPrepared,
|
||||||
args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
|
args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
|
||||||
var res = setupQuery(db, stmtName, args)
|
let res = setupQuery(db, stmtName, args)
|
||||||
var L = pqNfields(res)
|
getRow(res)
|
||||||
result = newRow(L)
|
|
||||||
if pqntuples(res) > 0:
|
proc getAllRows(res: PPGresult): seq[Row] =
|
||||||
setRow(res, result, 0, L)
|
let N = pqntuples(res)
|
||||||
pqClear(res)
|
let L = pqnfields(res)
|
||||||
|
result = newSeqOfCap[Row](N)
|
||||||
|
var row = newRow(L)
|
||||||
|
for i in 0'i32..N-1:
|
||||||
|
setRow(res, row, i, L)
|
||||||
|
result.add(row)
|
||||||
|
pqclear(res)
|
||||||
|
|
||||||
proc getAllRows*(db: DbConn, query: SqlQuery,
|
proc getAllRows*(db: DbConn, query: SqlQuery,
|
||||||
args: varargs[string, `$`]): seq[Row] {.
|
args: varargs[string, `$`]): seq[Row] {.
|
||||||
tags: [ReadDbEffect].} =
|
tags: [ReadDbEffect].} =
|
||||||
## executes the query and returns the whole result dataset.
|
## executes the query and returns the whole result dataset.
|
||||||
result = @[]
|
let res = setupQuery(db, query, args)
|
||||||
for r in fastRows(db, query, args):
|
getAllRows(res)
|
||||||
result.add(r)
|
|
||||||
|
|
||||||
proc getAllRows*(db: DbConn, stmtName: SqlPrepared,
|
proc getAllRows*(db: DbConn, stmtName: SqlPrepared,
|
||||||
args: varargs[string, `$`]): seq[Row] {.tags:
|
args: varargs[string, `$`]): seq[Row] {.tags:
|
||||||
[ReadDbEffect].} =
|
[ReadDbEffect].} =
|
||||||
## executes the prepared query and returns the whole result dataset.
|
## executes the prepared query and returns the whole result dataset.
|
||||||
result = @[]
|
let res = setupQuery(db, stmtName, args)
|
||||||
for r in fastRows(db, stmtName, args):
|
getAllRows(res)
|
||||||
result.add(r)
|
|
||||||
|
|
||||||
iterator rows*(db: DbConn, query: SqlQuery,
|
iterator rows*(db: DbConn, query: SqlQuery,
|
||||||
args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
|
args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
|
||||||
|
|
@ -459,18 +515,21 @@ iterator rows*(db: DbConn, stmtName: SqlPrepared,
|
||||||
## same as `fastRows`, but slower and safe.
|
## same as `fastRows`, but slower and safe.
|
||||||
for r in items(getAllRows(db, stmtName, args)): yield r
|
for r in items(getAllRows(db, stmtName, args)): yield r
|
||||||
|
|
||||||
|
proc getValue(res: PPGresult): string =
|
||||||
|
if pqntuples(res) > 0:
|
||||||
|
var x = pqgetvalue(res, 0, 0)
|
||||||
|
result = if isNil(x): "" else: $x
|
||||||
|
else:
|
||||||
|
result = ""
|
||||||
|
|
||||||
proc getValue*(db: DbConn, query: SqlQuery,
|
proc getValue*(db: DbConn, query: SqlQuery,
|
||||||
args: varargs[string, `$`]): string {.
|
args: varargs[string, `$`]): string {.
|
||||||
tags: [ReadDbEffect].} =
|
tags: [ReadDbEffect].} =
|
||||||
## executes the query and returns the first column of the first row of the
|
## executes the query and returns the first column of the first row of the
|
||||||
## result dataset. Returns "" if the dataset contains no rows or the database
|
## result dataset. Returns "" if the dataset contains no rows or the database
|
||||||
## value is NULL.
|
## value is NULL.
|
||||||
var res = setupQuery(db, query, args)
|
let res = setupQuery(db, query, args)
|
||||||
if pqntuples(res) > 0:
|
getValue(res)
|
||||||
var x = pqgetvalue(res, 0, 0)
|
|
||||||
result = if isNil(x): "" else: $x
|
|
||||||
else:
|
|
||||||
result = ""
|
|
||||||
|
|
||||||
proc getValue*(db: DbConn, stmtName: SqlPrepared,
|
proc getValue*(db: DbConn, stmtName: SqlPrepared,
|
||||||
args: varargs[string, `$`]): string {.
|
args: varargs[string, `$`]): string {.
|
||||||
|
|
@ -478,12 +537,8 @@ proc getValue*(db: DbConn, stmtName: SqlPrepared,
|
||||||
## executes the query and returns the first column of the first row of the
|
## executes the query and returns the first column of the first row of the
|
||||||
## result dataset. Returns "" if the dataset contains no rows or the database
|
## result dataset. Returns "" if the dataset contains no rows or the database
|
||||||
## value is NULL.
|
## value is NULL.
|
||||||
var res = setupQuery(db, stmtName, args)
|
let res = setupQuery(db, stmtName, args)
|
||||||
if pqntuples(res) > 0:
|
getValue(res)
|
||||||
var x = pqgetvalue(res, 0, 0)
|
|
||||||
result = if isNil(x): "" else: $x
|
|
||||||
else:
|
|
||||||
result = ""
|
|
||||||
|
|
||||||
proc tryInsertID*(db: DbConn, query: SqlQuery,
|
proc tryInsertID*(db: DbConn, query: SqlQuery,
|
||||||
args: varargs[string, `$`]): int64 {.
|
args: varargs[string, `$`]): int64 {.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue