summary refs log tree commit diff stats
path: root/lib/js/asyncjs.nim
blob: 7439b66e16f2f77f1f362b1c4cabe7ab3da2898f (plain) (blame)
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
#
#
#            Nim's Runtime Library
#        (c) Copyright 2017 Nim Authors
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.

## This module implements types and macros for writing asynchronous code
## for the JS backend. It provides tools for interaction with JavaScript async API-s
## and libraries, writing async procedures in Nim and converting callback-based code
## to promises.
##
## A Nim procedure is asynchronous when it includes the ``{.async.}`` pragma. It
## should always have a ``Future[T]`` return type or not have a return type at all.
## A ``Future[void]`` return type is assumed by default.
##
## This is roughly equivalent to the ``async`` keyword in JavaScript code.
##
## .. code-block:: nim
##  proc loadGame(name: string): Future[Game] {.async.} =
##    # code
##
## should be equivalent to
##
## .. code-block:: javascript
##   async function loadGame(name) {
##     // code
##   }
##
## A call to an asynchronous procedure usually needs ``await`` to wait for
## the completion of the ``Future``.
##
## .. code-block:: nim
##   var game = await loadGame(name)
##
## Often, you might work with callback-based API-s. You can wrap them with
## asynchronous procedures using promises and ``newPromise``:
##
## .. code-block:: nim
##   proc loadGame(name: string): Future[Game] =
##     var promise = newPromise() do (resolve: proc(response: Game)):
##       cbBasedLoadGame(name) do (game: Game):
##         resolve(game)
##     return promise
##
## Forward definitions work properly, you just need to always add the ``{.async.}`` pragma:
##
## .. code-block:: nim
##   proc loadGame(name: string): Future[Game] {.async.}
##
## JavaScript compatibility
## ~~~~~~~~~~~~~~~~~~~~~~~~~
##
## Nim currently generates `async/await` JavaScript code which is supported in modern
## EcmaScript and most modern versions of browsers, Node.js and Electron.
## If you need to use this module with older versions of JavaScript, you can
## use a tool that backports the resulting JavaScript code, as babel.

import jsffi
import macros

when not defined(js) and not defined(nimdoc) and not defined(nimsuggest):
  {.fatal: "Module asyncjs is designed to be used with the JavaScript backend.".}

type
  Future*[T] = ref object
    future*: T
  ## Wraps the return type of an asynchronous procedure.

  PromiseJs* {.importcpp: "Promise".} = ref object
  ## A JavaScript Promise


proc replaceReturn(node: var NimNode) =
  var z = 0
  for s in node:
    var son = node[z]
    let jsResolve = ident("jsResolve")
    if son.kind == nnkReturnStmt:
      let value = if son[0].kind != nnkEmpty: nnkCall.newTree(jsResolve, son[0]) else: jsResolve
      node[z] = nnkReturnStmt.newTree(value)
    elif son.kind == nnkAsgn and son[0].kind == nnkIdent and $son[0] == "result":
      node[z] = nnkAsgn.newTree(son[0], nnkCall.newTree(jsResolve, son[1]))
    else:
      replaceReturn(son)
    inc z

proc isFutureVoid(node: NimNode): bool =
  result = node.kind == nnkBracketExpr and
           node[0].kind == nnkIdent and $node[0] == "Future" and
           node[1].kind == nnkIdent and $node[1] == "void"

proc generateJsasync(arg: NimNode): NimNode =
  if arg.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}:
      error("Cannot transform this node kind into an async proc." &
            " proc/method definition or lambda node expected.")

  result = arg
  var isVoid = false
  let jsResolve = ident("jsResolve")
  if arg.params[0].kind == nnkEmpty:
    result.params[0] = nnkBracketExpr.newTree(ident("Future"), ident("void"))
    isVoid = true
  elif isFutureVoid(arg.params[0]):
    isVoid = true

  var code = result.body
  replaceReturn(code)
  result.body = nnkStmtList.newTree()

  if len(code) > 0:
    var awaitFunction = quote:
      proc await[T](f: Future[T]): T {.importcpp: "(await #)", used.}
    result.body.add(awaitFunction)

    var resolve: NimNode
    if isVoid:
      resolve = quote:
        var `jsResolve` {.importcpp: "undefined".}: Future[void]
    else:
      resolve = quote:
        proc jsResolve[T](a: T): Future[T] {.importcpp: "#", used.}
    result.body.add(resolve)
  else:
    result.body = newEmptyNode()
  for child in code:
    result.body.add(child)

  if len(code) > 0 and isVoid:
    var voidFix = quote:
      return `jsResolve`
    result.body.add(voidFix)

  let asyncPragma = quote:
    {.codegenDecl: "async function $2($3)".}

  result.addPragma(asyncPragma[0])

macro async*(arg: untyped): untyped =
  ## Macro which converts normal procedures into
  ## javascript-compatible async procedures
  if arg.kind == nnkStmtList:
    result = newStmtList()
    for oneProc in arg:
      result.add generateJsasync(oneProc)
  else:
    result = generateJsasync(arg)

proc newPromise*[T](handler: proc(resolve: proc(response: T))): Future[T] {.importcpp: "(new Promise(#))".}
  ## A helper for wrapping callback-based functions
  ## into promises and async procedures

proc newPromise*(handler: proc(resolve: proc())): Future[void] {.importcpp: "(new Promise(#))".}
  ## A helper for wrapping callback-based functions
  ## into promises and async procedures
ass="w"> result = "'" for c in items(s): if c == '\'': add(result, "''") else: add(result, c) add(result, '\'') proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string = result = "" var a = 0 if args.len > 0 and not string(formatstr).contains("?"): dbError("""parameter substitution expects "?" """) if args.len == 0: return string(formatstr) else: for c in items(string(formatstr)): if c == '?': add(result, dbQuote(args[a])) inc(a) else: add(result, c) proc tryExec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): bool {.tags: [ReadDbEffect, WriteDbEffect].} = ## tries to execute the query and returns true if successful, false otherwise. var res = pqexecParams(db, dbFormat(query, args), 0, nil, nil, nil, nil, 0) result = pqresultStatus(res) == PGRES_COMMAND_OK pqclear(res) proc tryExec*(db: DbConn, stmtName: SqlPrepared, args: varargs[string, `$`]): bool {.tags: [ ReadDbEffect, WriteDbEffect].} = ## tries to execute the query and returns true if successful, false otherwise. var arr = allocCStringArray(args) var res = pqexecPrepared(db, stmtName.string, int32(args.len), arr, nil, nil, 0) deallocCStringArray(arr) result = pqresultStatus(res) == PGRES_COMMAND_OK pqclear(res) proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) {. tags: [ReadDbEffect, WriteDbEffect].} = ## executes the query and raises EDB if not successful. var res = pqexecParams(db, dbFormat(query, args), 0, nil, nil, nil, nil, 0) if pqresultStatus(res) != PGRES_COMMAND_OK: dbError(db) pqclear(res) proc exec*(db: DbConn, stmtName: SqlPrepared, args: varargs[string]) {.tags: [ReadDbEffect, WriteDbEffect].} = var arr = allocCStringArray(args) var res = pqexecPrepared(db, stmtName.string, int32(args.len), arr, nil, nil, 0) deallocCStringArray(arr) if pqResultStatus(res) != PGRES_COMMAND_OK: dbError(db) pqclear(res) proc newRow(L: int): Row = newSeq(result, L) for i in 0..L-1: result[i] = "" proc setupQuery(db: DbConn, query: SqlQuery, args: varargs[string]): PPGresult = result = pqexec(db, dbFormat(query, args)) if pqResultStatus(result) != PGRES_TUPLES_OK: dbError(db) proc setupQuery(db: DbConn, stmtName: SqlPrepared, args: varargs[string]): PPGresult = var arr = allocCStringArray(args) result = pqexecPrepared(db, stmtName.string, int32(args.len), arr, nil, nil, 0) deallocCStringArray(arr) if pqResultStatus(result) != PGRES_TUPLES_OK: dbError(db) proc prepare*(db: DbConn; stmtName: string, query: SqlQuery; nParams: int): SqlPrepared = ## Creates a new ``SqlPrepared`` statement. Parameter substitution is done ## via ``$1``, ``$2``, ``$3``, etc. if nParams > 0 and not string(query).contains("$1"): dbError("parameter substitution expects \"$1\"") var res = pqprepare(db, stmtName, query.string, int32(nParams), nil) if pqResultStatus(res) != PGRES_COMMAND_OK: dbError(db) return SqlPrepared(stmtName) proc setRow(res: PPGresult, r: var Row, line, cols: int32) = for col in 0'i32..cols-1: setLen(r[col], 0) let x = pqgetvalue(res, line, col) if x.isNil: r[col] = "" else: add(r[col], x) iterator fastRows*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} = ## executes the query and iterates over the result dataset. This is very ## fast, but potenially dangerous: If the for-loop-body executes another ## query, the results can be undefined. For Postgres it is safe though. var res = setupQuery(db, query, args) var L = pqnfields(res) 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, args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} = ## executes the prepared query and iterates over the result dataset. var res = setupQuery(db, stmtName, args) var L = pqNfields(res) var result = newRow(L) for i in 0'i32..pqNtuples(res)-1: setRow(res, result, i, L) yield result pqClear(res) iterator instantRows*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): InstantRow {.tags: [ReadDbEffect].} = ## 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. var res = setupQuery(db, query, args) for i in 0'i32..pqNtuples(res)-1: yield InstantRow(res: res, line: i) pqClear(res) iterator instantRows*(db: DbConn, stmtName: SqlPrepared, args: varargs[string, `$`]): InstantRow {.tags: [ReadDbEffect].} = ## 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. var res = setupQuery(db, stmtName, args) for i in 0'i32..pqNtuples(res)-1: yield InstantRow(res: res, line: i) pqClear(res) proc getColumnType(res: PPGresult, col: int) : DbType = ## returns DbType for given column in the row ## defined in pg_type.h file in the postgres source code ## Wire representation for types: http://www.npgsql.org/dev/types.html var oid = pqftype(res, int32(col)) ## The integer returned is the internal OID number of the type case oid of 16: return DbType(kind: DbTypeKind.dbBool, name: "bool") of 17: return DbType(kind: DbTypeKind.dbBlob, name: "bytea") of 21: return DbType(kind: DbTypeKind.dbInt, name: "int2", size: 2) of 23: return DbType(kind: DbTypeKind.dbInt, name: "int4", size: 4) of 20: return DbType(kind: DbTypeKind.dbInt, name: "int8", size: 8) of 1560: return DbType(kind: DbTypeKind.dbBit, name: "bit") of 1562: return DbType(kind: DbTypeKind.dbInt, name: "varbit") of 18: return DbType(kind: DbTypeKind.dbFixedChar, name: "char") of 19: return DbType(kind: DbTypeKind.dbFixedChar, name: "name") of 1042: return DbType(kind: DbTypeKind.dbFixedChar, name: "bpchar") of 25: return DbType(kind: DbTypeKind.dbVarchar, name: "text") of 1043: return DbType(kind: DbTypeKind.dbVarChar, name: "varchar") of 2275: return DbType(kind: DbTypeKind.dbVarchar, name: "cstring") of 700: return DbType(kind: DbTypeKind.dbFloat, name: "float4") of 701: return DbType(kind: DbTypeKind.dbFloat, name: "float8") of 790: return DbType(kind: DbTypeKind.dbDecimal, name: "money") of 1700: return DbType(kind: DbTypeKind.dbDecimal, name: "numeric") of 704: return DbType(kind: DbTypeKind.dbTimeInterval, name: "tinterval") of 702: return DbType(kind: DbTypeKind.dbTimestamp, name: "abstime") of 703: return DbType(kind: DbTypeKind.dbTimeInterval, name: "reltime") of 1082: return DbType(kind: DbTypeKind.dbDate, name: "date") of 1083: return DbType(kind: DbTypeKind.dbTime, name: "time") of 1114: return DbType(kind: DbTypeKind.dbTimestamp, name: "timestamp") of 1184: return DbType(kind: DbTypeKind.dbTimestamp, name: "timestamptz") of 1186: return DbType(kind: DbTypeKind.dbTimeInterval, name: "interval") of 1266: return DbType(kind: DbTypeKind.dbTime, name: "timetz") of 114: return DbType(kind: DbTypeKind.dbJson, name: "json") of 142: return DbType(kind: DbTypeKind.dbXml, name: "xml") of 3802: return DbType(kind: DbTypeKind.dbJson, name: "jsonb") of 600: return DbType(kind: DbTypeKind.dbPoint, name: "point") of 601: return DbType(kind: DbTypeKind.dbLseg, name: "lseg") of 602: return DbType(kind: DbTypeKind.dbPath, name: "path") of 603: return DbType(kind: DbTypeKind.dbBox, name: "box") of 604: return DbType(kind: DbTypeKind.dbPolygon, name: "polygon") of 628: return DbType(kind: DbTypeKind.dbLine, name: "line") of 718: return DbType(kind: DbTypeKind.dbCircle, name: "circle") of 650: return DbType(kind: DbTypeKind.dbInet, name: "cidr") of 829: return DbType(kind: DbTypeKind.dbMacAddress, name: "macaddr") of 869: return DbType(kind: DbTypeKind.dbInet, name: "inet") of 2950: return DbType(kind: DbTypeKind.dbVarchar, name: "uuid") of 3614: return DbType(kind: DbTypeKind.dbVarchar, name: "tsvector") of 3615: return DbType(kind: DbTypeKind.dbVarchar, name: "tsquery") of 2970: return DbType(kind: DbTypeKind.dbVarchar, name: "txid_snapshot") of 27: return DbType(kind: DbTypeKind.dbComposite, name: "tid") of 1790: return DbType(kind: DbTypeKind.dbComposite, name: "refcursor") of 2249: return DbType(kind: DbTypeKind.dbComposite, name: "record") of 3904: return DbType(kind: DbTypeKind.dbComposite, name: "int4range") of 3906: return DbType(kind: DbTypeKind.dbComposite, name: "numrange") of 3908: return DbType(kind: DbTypeKind.dbComposite, name: "tsrange") of 3910: return DbType(kind: DbTypeKind.dbComposite, name: "tstzrange") of 3912: return DbType(kind: DbTypeKind.dbComposite, name: "daterange") of 3926: return DbType(kind: DbTypeKind.dbComposite, name: "int8range") of 22: return DbType(kind: DbTypeKind.dbArray, name: "int2vector") of 30: return DbType(kind: DbTypeKind.dbArray, name: "oidvector") of 143: return DbType(kind: DbTypeKind.dbArray, name: "xml[]") of 199: return DbType(kind: DbTypeKind.dbArray, name: "json[]") of 629: return DbType(kind: DbTypeKind.dbArray, name: "line[]") of 651: return DbType(kind: DbTypeKind.dbArray, name: "cidr[]") of 719: return DbType(kind: DbTypeKind.dbArray, name: "circle[]") of 791: return DbType(kind: DbTypeKind.dbArray, name: "money[]") of 1000: return DbType(kind: DbTypeKind.dbArray, name: "bool[]") of 1001: return DbType(kind: DbTypeKind.dbArray, name: "bytea[]") of 1002: return DbType(kind: DbTypeKind.dbArray, name: "char[]") of 1003: return DbType(kind: DbTypeKind.dbArray, name: "name[]") of 1005: return DbType(kind: DbTypeKind.dbArray, name: "int2[]") of 1006: return DbType(kind: DbTypeKind.dbArray, name: "int2vector[]") of 1007: return DbType(kind: DbTypeKind.dbArray, name: "int4[]") of 1008: return DbType(kind: DbTypeKind.dbArray, name: "regproc[]") of 1009: return DbType(kind: DbTypeKind.dbArray, name: "text[]") of 1028: return DbType(kind: DbTypeKind.dbArray, name: "oid[]") of 1010: return DbType(kind: DbTypeKind.dbArray, name: "tid[]") of 1011: return DbType(kind: DbTypeKind.dbArray, name: "xid[]") of 1012: return DbType(kind: DbTypeKind.dbArray, name: "cid[]") of 1013: return DbType(kind: DbTypeKind.dbArray, name: "oidvector[]") of 1014: return DbType(kind: DbTypeKind.dbArray, name: "bpchar[]") of 1015: return DbType(kind: DbTypeKind.dbArray, name: "varchar[]") of 1016: return DbType(kind: DbTypeKind.dbArray, name: "int8[]") of 1017: return DbType(kind: DbTypeKind.dbArray, name: "point[]") of 1018: return DbType(kind: DbTypeKind.dbArray, name: "lseg[]") of 1019: return DbType(kind: DbTypeKind.dbArray, name: "path[]") of 1020: return DbType(kind: DbTypeKind.dbArray, name: "box[]") of 1021: return DbType(kind: DbTypeKind.dbArray, name: "float4[]") of 1022: return DbType(kind: DbTypeKind.dbArray, name: "float8[]") of 1023: return DbType(kind: DbTypeKind.dbArray, name: "abstime[]") of 1024: return DbType(kind: DbTypeKind.dbArray, name: "reltime[]") of 1025: return DbType(kind: DbTypeKind.dbArray, name: "tinterval[]") of 1027: return DbType(kind: DbTypeKind.dbArray, name: "polygon[]") of 1040: return DbType(kind: DbTypeKind.dbArray, name: "macaddr[]") of 1041: return DbType(kind: DbTypeKind.dbArray, name: "inet[]") of 1263: return DbType(kind: DbTypeKind.dbArray, name: "cstring[]") of 1115: return DbType(kind: DbTypeKind.dbArray, name: "timestamp[]") of 1182: return DbType(kind: DbTypeKind.dbArray, name: "date[]") of 1183: return DbType(kind: DbTypeKind.dbArray, name: "time[]") of 1185: return DbType(kind: DbTypeKind.dbArray, name: "timestamptz[]") of 1187: return DbType(kind: DbTypeKind.dbArray, name: "interval[]") of 1231: return DbType(kind: DbTypeKind.dbArray, name: "numeric[]") of 1270: return DbType(kind: DbTypeKind.dbArray, name: "timetz[]") of 1561: return DbType(kind: DbTypeKind.dbArray, name: "bit[]") of 1563: return DbType(kind: DbTypeKind.dbArray, name: "varbit[]") of 2201: return DbType(kind: DbTypeKind.dbArray, name: "refcursor[]") of 2951: return DbType(kind: DbTypeKind.dbArray, name: "uuid[]") of 3643: return DbType(kind: DbTypeKind.dbArray, name: "tsvector[]") of 3645: return DbType(kind: DbTypeKind.dbArray, name: "tsquery[]") of 3807: return DbType(kind: DbTypeKind.dbArray, name: "jsonb[]") of 2949: return DbType(kind: DbTypeKind.dbArray, name: "txid_snapshot[]") of 3905: return DbType(kind: DbTypeKind.dbArray, name: "int4range[]") of 3907: return DbType(kind: DbTypeKind.dbArray, name: "numrange[]") of 3909: return DbType(kind: DbTypeKind.dbArray, name: "tsrange[]") of 3911: return DbType(kind: DbTypeKind.dbArray, name: "tstzrange[]") of 3913: return DbType(kind: DbTypeKind.dbArray, name: "daterange[]") of 3927: return DbType(kind: DbTypeKind.dbArray, name: "int8range[]") of 2287: return DbType(kind: DbTypeKind.dbArray, name: "record[]") 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. proc setColumnInfo(columns: var DbColumns; res: PPGresult; L: int32) = setLen(columns, L) for i in 0'i32..<L: columns[i].name = $pqfname(res, i) columns[i].typ = getColumnType(res, i) columns[i].tableName = $(pqftable(res, i)) ## Returns the OID of the table from which the given column was fetched. ## Query the system table pg_class to determine exactly which table is referenced. #columns[i].primaryKey = libpq does not have a function for that #columns[i].foreignKey = libpq does not have a function for that iterator instantRows*(db: DbConn; columns: var DbColumns; query: SqlQuery; args: varargs[string, `$`]): InstantRow {.tags: [ReadDbEffect].} = var res = setupQuery(db, query, args) setColumnInfo(columns, res, pqnfields(res)) for i in 0'i32..<pqntuples(res): yield InstantRow(res: res, line: i) pqClear(res) proc `[]`*(row: InstantRow; col: int): string {.inline.} = ## returns text for given column of the row $pqgetvalue(row.res, int32(row.line), int32(col)) proc len*(row: InstantRow): int {.inline.} = ## returns number of columns in the row int(pqNfields(row.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. var res = setupQuery(db, query, args) var L = pqnfields(res) result = newRow(L) setRow(res, result, 0, L) pqclear(res) proc getRow*(db: DbConn, stmtName: SqlPrepared, args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} = var res = setupQuery(db, stmtName, args) var L = pqNfields(res) result = newRow(L) setRow(res, result, 0, L) pqClear(res) proc getAllRows*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): seq[Row] {. tags: [ReadDbEffect].} = ## executes the query and returns the whole result dataset. result = @[] for r in fastRows(db, query, args): result.add(r) proc getAllRows*(db: DbConn, stmtName: SqlPrepared, args: varargs[string, `$`]): seq[Row] {.tags: [ReadDbEffect].} = ## executes the prepared query and returns the whole result dataset. result = @[] for r in fastRows(db, stmtName, args): result.add(r) iterator rows*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} = ## same as `fastRows`, but slower and safe. for r in items(getAllRows(db, query, args)): yield r iterator rows*(db: DbConn, stmtName: SqlPrepared, args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} = ## same as `fastRows`, but slower and safe. for r in items(getAllRows(db, stmtName, args)): yield r proc getValue*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): string {. tags: [ReadDbEffect].} = ## 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 ## value is NULL. var x = pqgetvalue(setupQuery(db, query, args), 0, 0) result = if isNil(x): "" else: $x proc getValue*(db: DbConn, stmtName: SqlPrepared, args: varargs[string, `$`]): string {. tags: [ReadDbEffect].} = ## 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 ## value is NULL. var x = pqgetvalue(setupQuery(db, stmtName, args), 0, 0) result = if isNil(x): "" else: $x proc tryInsertID*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): int64 {. tags: [WriteDbEffect].}= ## executes the query (typically "INSERT") and returns the ## generated ID for the row or -1 in case of an error. For Postgre this adds ## ``RETURNING id`` to the query, so it only works if your primary key is ## named ``id``. var x = pqgetvalue(setupQuery(db, SqlQuery(string(query) & " RETURNING id"), args), 0, 0) if not isNil(x): result = parseBiggestInt($x) else: result = -1 proc insertID*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): int64 {. tags: [WriteDbEffect].} = ## executes the query (typically "INSERT") and returns the ## generated ID for the row. For Postgre this adds ## ``RETURNING id`` to the query, so it only works if your primary key is ## named ``id``. result = tryInsertID(db, query, args) if result < 0: dbError(db) proc execAffectedRows*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): int64 {.tags: [ ReadDbEffect, WriteDbEffect].} = ## executes the query (typically "UPDATE") and returns the ## number of affected rows. var q = dbFormat(query, args) var res = pqExec(db, q) if pqresultStatus(res) != PGRES_COMMAND_OK: dbError(db) result = parseBiggestInt($pqcmdTuples(res)) pqclear(res) proc execAffectedRows*(db: DbConn, stmtName: SqlPrepared, args: varargs[string, `$`]): int64 {.tags: [ ReadDbEffect, WriteDbEffect].} = ## executes the query (typically "UPDATE") and returns the ## number of affected rows. var arr = allocCStringArray(args) var res = pqexecPrepared(db, stmtName.string, int32(args.len), arr, nil, nil, 0) deallocCStringArray(arr) if pqresultStatus(res) != PGRES_COMMAND_OK: dbError(db) result = parseBiggestInt($pqcmdTuples(res)) pqclear(res) proc close*(db: DbConn) {.tags: [DbEffect].} = ## closes the database connection. if db != nil: pqfinish(db) proc open*(connection, user, password, database: string): DbConn {. tags: [DbEffect].} = ## opens a database connection. Raises `EDb` if the connection could not ## be established. ## ## Clients can also use Postgres keyword/value connection strings to ## connect. ## ## Example: ## ## .. code-block:: nim ## ## con = open("", "", "", "host=localhost port=5432 dbname=mydb") ## ## See http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING ## for more information. let colonPos = connection.find(':') host = if colonPos < 0: connection else: substr(connection, 0, colonPos-1) port = if colonPos < 0: "" else: substr(connection, colonPos+1) result = pqsetdbLogin(host, port, nil, nil, database, user, password) if pqStatus(result) != CONNECTION_OK: dbError(result) # result = nil proc setEncoding*(connection: DbConn, encoding: string): bool {. tags: [DbEffect].} = ## sets the encoding of a database connection, returns true for ## success, false for failure. return pqsetClientEncoding(connection, encoding) == 0 # Tests are in ../../tests/untestable/tpostgres.