db_postgres.nim 26 KB

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