db_postgres.nim 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## A higher level `PostgreSQL`:idx: database wrapper. This interface
  10. ## is implemented for other databases also.
  11. ##
  12. ## See also: `db_odbc <db_odbc.html>`_, `db_sqlite <db_sqlite.html>`_,
  13. ## `db_mysql <db_mysql.html>`_.
  14. ##
  15. ## Parameter substitution
  16. ## ======================
  17. ##
  18. ## All ``db_*`` modules support the same form of parameter substitution.
  19. ## That is, using the ``?`` (question mark) to signify the place where a
  20. ## value should be placed. For example:
  21. ##
  22. ## .. code-block:: Nim
  23. ## sql"INSERT INTO myTable (colA, colB, colC) VALUES (?, ?, ?)"
  24. ##
  25. ## **Note**: There are two approaches to parameter substitution support by
  26. ## this module.
  27. ##
  28. ## 1. ``SqlQuery`` using ``?, ?, ?, ...`` (same as all the ``db_*`` modules)
  29. ##
  30. ## 2. ``SqlPrepared`` using ``$1, $2, $3, ...``
  31. ##
  32. ## .. code-block:: Nim
  33. ## prepare(db, "myExampleInsert",
  34. ## sql"""INSERT INTO myTable
  35. ## (colA, colB, colC)
  36. ## VALUES ($1, $2, $3)""",
  37. ## 3)
  38. ##
  39. ## Examples
  40. ## ========
  41. ##
  42. ## Opening a connection to a database
  43. ## ----------------------------------
  44. ##
  45. ## .. code-block:: Nim
  46. ## import db_postgres
  47. ## let db = open("localhost", "user", "password", "dbname")
  48. ## db.close()
  49. ##
  50. ## Creating a table
  51. ## ----------------
  52. ##
  53. ## .. code-block:: Nim
  54. ## db.exec(sql"DROP TABLE IF EXISTS myTable")
  55. ## db.exec(sql("""CREATE TABLE myTable (
  56. ## id integer,
  57. ## name varchar(50) not null)"""))
  58. ##
  59. ## Inserting data
  60. ## --------------
  61. ##
  62. ## .. code-block:: Nim
  63. ## db.exec(sql"INSERT INTO myTable (id, name) VALUES (0, ?)",
  64. ## "Dominik")
  65. import strutils, postgres
  66. import db_common
  67. export db_common
  68. type
  69. DbConn* = PPGconn ## encapsulates a database connection
  70. Row* = seq[string] ## a row of a dataset. NULL database values will be
  71. ## converted to nil.
  72. InstantRow* = object ## a handle that can be
  73. res: PPGresult ## used to get a row's
  74. line: int ## column text on demand
  75. SqlPrepared* = distinct string ## a identifier for the prepared queries
  76. proc dbError*(db: DbConn) {.noreturn.} =
  77. ## raises a DbError exception.
  78. var e: ref DbError
  79. new(e)
  80. e.msg = $pqErrorMessage(db)
  81. raise e
  82. proc dbQuote*(s: string): string =
  83. ## DB quotes the string.
  84. result = "'"
  85. for c in items(s):
  86. if c == '\'': add(result, "''")
  87. else: add(result, c)
  88. add(result, '\'')
  89. proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string =
  90. result = ""
  91. var a = 0
  92. if args.len > 0 and not string(formatstr).contains("?"):
  93. dbError("""parameter substitution expects "?" """)
  94. if args.len == 0:
  95. return string(formatstr)
  96. else:
  97. for c in items(string(formatstr)):
  98. if c == '?':
  99. add(result, dbQuote(args[a]))
  100. inc(a)
  101. else:
  102. add(result, c)
  103. proc tryExec*(db: DbConn, query: SqlQuery,
  104. args: varargs[string, `$`]): bool {.tags: [ReadDbEffect, WriteDbEffect].} =
  105. ## tries to execute the query and returns true if successful, false otherwise.
  106. var res = pqexecParams(db, dbFormat(query, args), 0, nil, nil,
  107. nil, nil, 0)
  108. result = pqresultStatus(res) == PGRES_COMMAND_OK
  109. pqclear(res)
  110. proc tryExec*(db: DbConn, stmtName: SqlPrepared,
  111. args: varargs[string, `$`]): bool {.tags: [
  112. ReadDbEffect, WriteDbEffect].} =
  113. ## tries to execute the query and returns true if successful, false otherwise.
  114. var arr = allocCStringArray(args)
  115. var res = pqexecPrepared(db, stmtName.string, int32(args.len), arr,
  116. nil, nil, 0)
  117. deallocCStringArray(arr)
  118. result = pqresultStatus(res) == PGRES_COMMAND_OK
  119. pqclear(res)
  120. proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) {.
  121. tags: [ReadDbEffect, WriteDbEffect].} =
  122. ## executes the query and raises EDB if not successful.
  123. var res = pqexecParams(db, dbFormat(query, args), 0, nil, nil,
  124. nil, nil, 0)
  125. if pqresultStatus(res) != PGRES_COMMAND_OK: dbError(db)
  126. pqclear(res)
  127. proc exec*(db: DbConn, stmtName: SqlPrepared,
  128. args: varargs[string]) {.tags: [ReadDbEffect, WriteDbEffect].} =
  129. var arr = allocCStringArray(args)
  130. var res = pqexecPrepared(db, stmtName.string, int32(args.len), arr,
  131. nil, nil, 0)
  132. deallocCStringArray(arr)
  133. if pqResultStatus(res) != PGRES_COMMAND_OK: dbError(db)
  134. pqclear(res)
  135. proc newRow(L: int): Row =
  136. newSeq(result, L)
  137. for i in 0..L-1: result[i] = ""
  138. proc setupQuery(db: DbConn, query: SqlQuery,
  139. args: varargs[string]): PPGresult =
  140. result = pqexec(db, dbFormat(query, args))
  141. if pqResultStatus(result) != PGRES_TUPLES_OK: dbError(db)
  142. proc setupQuery(db: DbConn, stmtName: SqlPrepared,
  143. args: varargs[string]): PPGresult =
  144. var arr = allocCStringArray(args)
  145. result = pqexecPrepared(db, stmtName.string, int32(args.len), arr,
  146. nil, nil, 0)
  147. deallocCStringArray(arr)
  148. if pqResultStatus(result) != PGRES_TUPLES_OK: dbError(db)
  149. proc prepare*(db: DbConn; stmtName: string, query: SqlQuery;
  150. nParams: int): SqlPrepared =
  151. ## Creates a new ``SqlPrepared`` statement. Parameter substitution is done
  152. ## via ``$1``, ``$2``, ``$3``, etc.
  153. if nParams > 0 and not string(query).contains("$1"):
  154. dbError("parameter substitution expects \"$1\"")
  155. var res = pqprepare(db, stmtName, query.string, int32(nParams), nil)
  156. if pqResultStatus(res) != PGRES_COMMAND_OK: dbError(db)
  157. return SqlPrepared(stmtName)
  158. proc setRow(res: PPGresult, r: var Row, line, cols: int32) =
  159. for col in 0'i32..cols-1:
  160. setLen(r[col], 0)
  161. let x = pqgetvalue(res, line, col)
  162. if x.isNil:
  163. r[col] = ""
  164. else:
  165. add(r[col], x)
  166. iterator fastRows*(db: DbConn, query: SqlQuery,
  167. args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
  168. ## executes the query and iterates over the result dataset. This is very
  169. ## fast, but potentially dangerous: If the for-loop-body executes another
  170. ## query, the results can be undefined. For Postgres it is safe though.
  171. var res = setupQuery(db, query, args)
  172. var L = pqnfields(res)
  173. var result = newRow(L)
  174. for i in 0'i32..pqntuples(res)-1:
  175. setRow(res, result, i, L)
  176. yield result
  177. pqclear(res)
  178. iterator fastRows*(db: DbConn, stmtName: SqlPrepared,
  179. args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
  180. ## executes the prepared query and iterates over the result dataset.
  181. var res = setupQuery(db, stmtName, args)
  182. var L = pqNfields(res)
  183. var result = newRow(L)
  184. for i in 0'i32..pqNtuples(res)-1:
  185. setRow(res, result, i, L)
  186. yield result
  187. pqClear(res)
  188. iterator instantRows*(db: DbConn, query: SqlQuery,
  189. args: varargs[string, `$`]): InstantRow
  190. {.tags: [ReadDbEffect].} =
  191. ## same as fastRows but returns a handle that can be used to get column text
  192. ## on demand using []. Returned handle is valid only within iterator body.
  193. var res = setupQuery(db, query, args)
  194. for i in 0'i32..pqNtuples(res)-1:
  195. yield InstantRow(res: res, line: i)
  196. pqClear(res)
  197. iterator instantRows*(db: DbConn, stmtName: SqlPrepared,
  198. args: varargs[string, `$`]): InstantRow
  199. {.tags: [ReadDbEffect].} =
  200. ## same as fastRows but returns a handle that can be used to get column text
  201. ## on demand using []. Returned handle is valid only within iterator body.
  202. var res = setupQuery(db, stmtName, args)
  203. for i in 0'i32..pqNtuples(res)-1:
  204. yield InstantRow(res: res, line: i)
  205. pqClear(res)
  206. proc getColumnType(res: PPGresult, col: int) : DbType =
  207. ## returns DbType for given column in the row
  208. ## defined in pg_type.h file in the postgres source code
  209. ## Wire representation for types: http://www.npgsql.org/dev/types.html
  210. var oid = pqftype(res, int32(col))
  211. ## The integer returned is the internal OID number of the type
  212. case oid
  213. of 16: return DbType(kind: DbTypeKind.dbBool, name: "bool")
  214. of 17: return DbType(kind: DbTypeKind.dbBlob, name: "bytea")
  215. of 21: return DbType(kind: DbTypeKind.dbInt, name: "int2", size: 2)
  216. of 23: return DbType(kind: DbTypeKind.dbInt, name: "int4", size: 4)
  217. of 20: return DbType(kind: DbTypeKind.dbInt, name: "int8", size: 8)
  218. of 1560: return DbType(kind: DbTypeKind.dbBit, name: "bit")
  219. of 1562: return DbType(kind: DbTypeKind.dbInt, name: "varbit")
  220. of 18: return DbType(kind: DbTypeKind.dbFixedChar, name: "char")
  221. of 19: return DbType(kind: DbTypeKind.dbFixedChar, name: "name")
  222. of 1042: return DbType(kind: DbTypeKind.dbFixedChar, name: "bpchar")
  223. of 25: return DbType(kind: DbTypeKind.dbVarchar, name: "text")
  224. of 1043: return DbType(kind: DbTypeKind.dbVarChar, name: "varchar")
  225. of 2275: return DbType(kind: DbTypeKind.dbVarchar, name: "cstring")
  226. of 700: return DbType(kind: DbTypeKind.dbFloat, name: "float4")
  227. of 701: return DbType(kind: DbTypeKind.dbFloat, name: "float8")
  228. of 790: return DbType(kind: DbTypeKind.dbDecimal, name: "money")
  229. of 1700: return DbType(kind: DbTypeKind.dbDecimal, name: "numeric")
  230. of 704: return DbType(kind: DbTypeKind.dbTimeInterval, name: "tinterval")
  231. of 702: return DbType(kind: DbTypeKind.dbTimestamp, name: "abstime")
  232. of 703: return DbType(kind: DbTypeKind.dbTimeInterval, name: "reltime")
  233. of 1082: return DbType(kind: DbTypeKind.dbDate, name: "date")
  234. of 1083: return DbType(kind: DbTypeKind.dbTime, name: "time")
  235. of 1114: return DbType(kind: DbTypeKind.dbTimestamp, name: "timestamp")
  236. of 1184: return DbType(kind: DbTypeKind.dbTimestamp, name: "timestamptz")
  237. of 1186: return DbType(kind: DbTypeKind.dbTimeInterval, name: "interval")
  238. of 1266: return DbType(kind: DbTypeKind.dbTime, name: "timetz")
  239. of 114: return DbType(kind: DbTypeKind.dbJson, name: "json")
  240. of 142: return DbType(kind: DbTypeKind.dbXml, name: "xml")
  241. of 3802: return DbType(kind: DbTypeKind.dbJson, name: "jsonb")
  242. of 600: return DbType(kind: DbTypeKind.dbPoint, name: "point")
  243. of 601: return DbType(kind: DbTypeKind.dbLseg, name: "lseg")
  244. of 602: return DbType(kind: DbTypeKind.dbPath, name: "path")
  245. of 603: return DbType(kind: DbTypeKind.dbBox, name: "box")
  246. of 604: return DbType(kind: DbTypeKind.dbPolygon, name: "polygon")
  247. of 628: return DbType(kind: DbTypeKind.dbLine, name: "line")
  248. of 718: return DbType(kind: DbTypeKind.dbCircle, name: "circle")
  249. of 650: return DbType(kind: DbTypeKind.dbInet, name: "cidr")
  250. of 829: return DbType(kind: DbTypeKind.dbMacAddress, name: "macaddr")
  251. of 869: return DbType(kind: DbTypeKind.dbInet, name: "inet")
  252. of 2950: return DbType(kind: DbTypeKind.dbVarchar, name: "uuid")
  253. of 3614: return DbType(kind: DbTypeKind.dbVarchar, name: "tsvector")
  254. of 3615: return DbType(kind: DbTypeKind.dbVarchar, name: "tsquery")
  255. of 2970: return DbType(kind: DbTypeKind.dbVarchar, name: "txid_snapshot")
  256. of 27: return DbType(kind: DbTypeKind.dbComposite, name: "tid")
  257. of 1790: return DbType(kind: DbTypeKind.dbComposite, name: "refcursor")
  258. of 2249: return DbType(kind: DbTypeKind.dbComposite, name: "record")
  259. of 3904: return DbType(kind: DbTypeKind.dbComposite, name: "int4range")
  260. of 3906: return DbType(kind: DbTypeKind.dbComposite, name: "numrange")
  261. of 3908: return DbType(kind: DbTypeKind.dbComposite, name: "tsrange")
  262. of 3910: return DbType(kind: DbTypeKind.dbComposite, name: "tstzrange")
  263. of 3912: return DbType(kind: DbTypeKind.dbComposite, name: "daterange")
  264. of 3926: return DbType(kind: DbTypeKind.dbComposite, name: "int8range")
  265. of 22: return DbType(kind: DbTypeKind.dbArray, name: "int2vector")
  266. of 30: return DbType(kind: DbTypeKind.dbArray, name: "oidvector")
  267. of 143: return DbType(kind: DbTypeKind.dbArray, name: "xml[]")
  268. of 199: return DbType(kind: DbTypeKind.dbArray, name: "json[]")
  269. of 629: return DbType(kind: DbTypeKind.dbArray, name: "line[]")
  270. of 651: return DbType(kind: DbTypeKind.dbArray, name: "cidr[]")
  271. of 719: return DbType(kind: DbTypeKind.dbArray, name: "circle[]")
  272. of 791: return DbType(kind: DbTypeKind.dbArray, name: "money[]")
  273. of 1000: return DbType(kind: DbTypeKind.dbArray, name: "bool[]")
  274. of 1001: return DbType(kind: DbTypeKind.dbArray, name: "bytea[]")
  275. of 1002: return DbType(kind: DbTypeKind.dbArray, name: "char[]")
  276. of 1003: return DbType(kind: DbTypeKind.dbArray, name: "name[]")
  277. of 1005: return DbType(kind: DbTypeKind.dbArray, name: "int2[]")
  278. of 1006: return DbType(kind: DbTypeKind.dbArray, name: "int2vector[]")
  279. of 1007: return DbType(kind: DbTypeKind.dbArray, name: "int4[]")
  280. of 1008: return DbType(kind: DbTypeKind.dbArray, name: "regproc[]")
  281. of 1009: return DbType(kind: DbTypeKind.dbArray, name: "text[]")
  282. of 1028: return DbType(kind: DbTypeKind.dbArray, name: "oid[]")
  283. of 1010: return DbType(kind: DbTypeKind.dbArray, name: "tid[]")
  284. of 1011: return DbType(kind: DbTypeKind.dbArray, name: "xid[]")
  285. of 1012: return DbType(kind: DbTypeKind.dbArray, name: "cid[]")
  286. of 1013: return DbType(kind: DbTypeKind.dbArray, name: "oidvector[]")
  287. of 1014: return DbType(kind: DbTypeKind.dbArray, name: "bpchar[]")
  288. of 1015: return DbType(kind: DbTypeKind.dbArray, name: "varchar[]")
  289. of 1016: return DbType(kind: DbTypeKind.dbArray, name: "int8[]")
  290. of 1017: return DbType(kind: DbTypeKind.dbArray, name: "point[]")
  291. of 1018: return DbType(kind: DbTypeKind.dbArray, name: "lseg[]")
  292. of 1019: return DbType(kind: DbTypeKind.dbArray, name: "path[]")
  293. of 1020: return DbType(kind: DbTypeKind.dbArray, name: "box[]")
  294. of 1021: return DbType(kind: DbTypeKind.dbArray, name: "float4[]")
  295. of 1022: return DbType(kind: DbTypeKind.dbArray, name: "float8[]")
  296. of 1023: return DbType(kind: DbTypeKind.dbArray, name: "abstime[]")
  297. of 1024: return DbType(kind: DbTypeKind.dbArray, name: "reltime[]")
  298. of 1025: return DbType(kind: DbTypeKind.dbArray, name: "tinterval[]")
  299. of 1027: return DbType(kind: DbTypeKind.dbArray, name: "polygon[]")
  300. of 1040: return DbType(kind: DbTypeKind.dbArray, name: "macaddr[]")
  301. of 1041: return DbType(kind: DbTypeKind.dbArray, name: "inet[]")
  302. of 1263: return DbType(kind: DbTypeKind.dbArray, name: "cstring[]")
  303. of 1115: return DbType(kind: DbTypeKind.dbArray, name: "timestamp[]")
  304. of 1182: return DbType(kind: DbTypeKind.dbArray, name: "date[]")
  305. of 1183: return DbType(kind: DbTypeKind.dbArray, name: "time[]")
  306. of 1185: return DbType(kind: DbTypeKind.dbArray, name: "timestamptz[]")
  307. of 1187: return DbType(kind: DbTypeKind.dbArray, name: "interval[]")
  308. of 1231: return DbType(kind: DbTypeKind.dbArray, name: "numeric[]")
  309. of 1270: return DbType(kind: DbTypeKind.dbArray, name: "timetz[]")
  310. of 1561: return DbType(kind: DbTypeKind.dbArray, name: "bit[]")
  311. of 1563: return DbType(kind: DbTypeKind.dbArray, name: "varbit[]")
  312. of 2201: return DbType(kind: DbTypeKind.dbArray, name: "refcursor[]")
  313. of 2951: return DbType(kind: DbTypeKind.dbArray, name: "uuid[]")
  314. of 3643: return DbType(kind: DbTypeKind.dbArray, name: "tsvector[]")
  315. of 3645: return DbType(kind: DbTypeKind.dbArray, name: "tsquery[]")
  316. of 3807: return DbType(kind: DbTypeKind.dbArray, name: "jsonb[]")
  317. of 2949: return DbType(kind: DbTypeKind.dbArray, name: "txid_snapshot[]")
  318. of 3905: return DbType(kind: DbTypeKind.dbArray, name: "int4range[]")
  319. of 3907: return DbType(kind: DbTypeKind.dbArray, name: "numrange[]")
  320. of 3909: return DbType(kind: DbTypeKind.dbArray, name: "tsrange[]")
  321. of 3911: return DbType(kind: DbTypeKind.dbArray, name: "tstzrange[]")
  322. of 3913: return DbType(kind: DbTypeKind.dbArray, name: "daterange[]")
  323. of 3927: return DbType(kind: DbTypeKind.dbArray, name: "int8range[]")
  324. of 2287: return DbType(kind: DbTypeKind.dbArray, name: "record[]")
  325. of 705: return DbType(kind: DbTypeKind.dbUnknown, name: "unknown")
  326. else: return DbType(kind: DbTypeKind.dbUnknown, name: $oid) ## Query the system table pg_type to determine exactly which type is referenced.
  327. proc setColumnInfo(columns: var DbColumns; res: PPGresult; L: int32) =
  328. setLen(columns, L)
  329. for i in 0'i32..<L:
  330. columns[i].name = $pqfname(res, i)
  331. columns[i].typ = getColumnType(res, i)
  332. columns[i].tableName = $(pqftable(res, i)) ## Returns the OID of the table from which the given column was fetched.
  333. ## Query the system table pg_class to determine exactly which table is referenced.
  334. #columns[i].primaryKey = libpq does not have a function for that
  335. #columns[i].foreignKey = libpq does not have a function for that
  336. iterator instantRows*(db: DbConn; columns: var DbColumns; query: SqlQuery;
  337. args: varargs[string, `$`]): InstantRow
  338. {.tags: [ReadDbEffect].} =
  339. var res = setupQuery(db, query, args)
  340. setColumnInfo(columns, res, pqnfields(res))
  341. for i in 0'i32..<pqntuples(res):
  342. yield InstantRow(res: res, line: i)
  343. pqClear(res)
  344. proc `[]`*(row: InstantRow; col: int): string {.inline.} =
  345. ## returns text for given column of the row
  346. $pqgetvalue(row.res, int32(row.line), int32(col))
  347. proc unsafeColumnAt*(row: InstantRow, index: int): cstring {.inline.} =
  348. ## Return cstring of given column of the row
  349. pqgetvalue(row.res, int32(row.line), int32(index))
  350. proc len*(row: InstantRow): int {.inline.} =
  351. ## returns number of columns in the row
  352. int(pqNfields(row.res))
  353. proc getRow*(db: DbConn, query: SqlQuery,
  354. args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
  355. ## retrieves a single row. If the query doesn't return any rows, this proc
  356. ## will return a Row with empty strings for each column.
  357. var res = setupQuery(db, query, args)
  358. var L = pqnfields(res)
  359. result = newRow(L)
  360. if pqntuples(res) > 0:
  361. setRow(res, result, 0, L)
  362. pqclear(res)
  363. proc getRow*(db: DbConn, stmtName: SqlPrepared,
  364. args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
  365. var res = setupQuery(db, stmtName, args)
  366. var L = pqNfields(res)
  367. result = newRow(L)
  368. if pqntuples(res) > 0:
  369. setRow(res, result, 0, L)
  370. pqClear(res)
  371. proc getAllRows*(db: DbConn, query: SqlQuery,
  372. args: varargs[string, `$`]): seq[Row] {.
  373. tags: [ReadDbEffect].} =
  374. ## executes the query and returns the whole result dataset.
  375. result = @[]
  376. for r in fastRows(db, query, args):
  377. result.add(r)
  378. proc getAllRows*(db: DbConn, stmtName: SqlPrepared,
  379. args: varargs[string, `$`]): seq[Row] {.tags:
  380. [ReadDbEffect].} =
  381. ## executes the prepared query and returns the whole result dataset.
  382. result = @[]
  383. for r in fastRows(db, stmtName, args):
  384. result.add(r)
  385. iterator rows*(db: DbConn, query: SqlQuery,
  386. args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
  387. ## same as `fastRows`, but slower and safe.
  388. for r in items(getAllRows(db, query, args)): yield r
  389. iterator rows*(db: DbConn, stmtName: SqlPrepared,
  390. args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
  391. ## same as `fastRows`, but slower and safe.
  392. for r in items(getAllRows(db, stmtName, args)): yield r
  393. proc getValue*(db: DbConn, query: SqlQuery,
  394. args: varargs[string, `$`]): string {.
  395. tags: [ReadDbEffect].} =
  396. ## executes the query and returns the first column of the first row of the
  397. ## result dataset. Returns "" if the dataset contains no rows or the database
  398. ## value is NULL.
  399. var res = setupQuery(db, query, args)
  400. if pqntuples(res) > 0:
  401. var x = pqgetvalue(res, 0, 0)
  402. result = if isNil(x): "" else: $x
  403. else:
  404. result = ""
  405. proc getValue*(db: DbConn, stmtName: SqlPrepared,
  406. args: varargs[string, `$`]): string {.
  407. tags: [ReadDbEffect].} =
  408. ## executes the query and returns the first column of the first row of the
  409. ## result dataset. Returns "" if the dataset contains no rows or the database
  410. ## value is NULL.
  411. var res = setupQuery(db, stmtName, args)
  412. if pqntuples(res) > 0:
  413. var x = pqgetvalue(res, 0, 0)
  414. result = if isNil(x): "" else: $x
  415. else:
  416. result = ""
  417. proc tryInsertID*(db: DbConn, query: SqlQuery,
  418. args: varargs[string, `$`]): int64 {.
  419. tags: [WriteDbEffect].}=
  420. ## executes the query (typically "INSERT") and returns the
  421. ## generated ID for the row or -1 in case of an error. For Postgre this adds
  422. ## ``RETURNING id`` to the query, so it only works if your primary key is
  423. ## named ``id``.
  424. var x = pqgetvalue(setupQuery(db, SqlQuery(string(query) & " RETURNING id"),
  425. args), 0, 0)
  426. if not isNil(x):
  427. result = parseBiggestInt($x)
  428. else:
  429. result = -1
  430. proc insertID*(db: DbConn, query: SqlQuery,
  431. args: varargs[string, `$`]): int64 {.
  432. tags: [WriteDbEffect].} =
  433. ## executes the query (typically "INSERT") and returns the
  434. ## generated ID for the row. For Postgre this adds
  435. ## ``RETURNING id`` to the query, so it only works if your primary key is
  436. ## named ``id``.
  437. result = tryInsertID(db, query, args)
  438. if result < 0: dbError(db)
  439. proc execAffectedRows*(db: DbConn, query: SqlQuery,
  440. args: varargs[string, `$`]): int64 {.tags: [
  441. ReadDbEffect, WriteDbEffect].} =
  442. ## executes the query (typically "UPDATE") and returns the
  443. ## number of affected rows.
  444. var q = dbFormat(query, args)
  445. var res = pqExec(db, q)
  446. if pqresultStatus(res) != PGRES_COMMAND_OK: dbError(db)
  447. result = parseBiggestInt($pqcmdTuples(res))
  448. pqclear(res)
  449. proc execAffectedRows*(db: DbConn, stmtName: SqlPrepared,
  450. args: varargs[string, `$`]): int64 {.tags: [
  451. ReadDbEffect, WriteDbEffect].} =
  452. ## executes the query (typically "UPDATE") and returns the
  453. ## number of affected rows.
  454. var arr = allocCStringArray(args)
  455. var res = pqexecPrepared(db, stmtName.string, int32(args.len), arr,
  456. nil, nil, 0)
  457. deallocCStringArray(arr)
  458. if pqresultStatus(res) != PGRES_COMMAND_OK: dbError(db)
  459. result = parseBiggestInt($pqcmdTuples(res))
  460. pqclear(res)
  461. proc close*(db: DbConn) {.tags: [DbEffect].} =
  462. ## closes the database connection.
  463. if db != nil: pqfinish(db)
  464. proc open*(connection, user, password, database: string): DbConn {.
  465. tags: [DbEffect].} =
  466. ## opens a database connection. Raises `EDb` if the connection could not
  467. ## be established.
  468. ##
  469. ## Clients can also use Postgres keyword/value connection strings to
  470. ## connect.
  471. ##
  472. ## Example:
  473. ##
  474. ## .. code-block:: nim
  475. ##
  476. ## con = open("", "", "", "host=localhost port=5432 dbname=mydb")
  477. ##
  478. ## See http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
  479. ## for more information.
  480. let
  481. colonPos = connection.find(':')
  482. host = if colonPos < 0: connection
  483. else: substr(connection, 0, colonPos-1)
  484. port = if colonPos < 0: ""
  485. else: substr(connection, colonPos+1)
  486. result = pqsetdbLogin(host, port, nil, nil, database, user, password)
  487. if pqStatus(result) != CONNECTION_OK: dbError(result) # result = nil
  488. proc setEncoding*(connection: DbConn, encoding: string): bool {.
  489. tags: [DbEffect].} =
  490. ## sets the encoding of a database connection, returns true for
  491. ## success, false for failure.
  492. return pqsetClientEncoding(connection, encoding) == 0
  493. # Tests are in ../../tests/untestable/tpostgres.