db_mysql.nim 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  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 `mySQL`:idx: database wrapper. The same interface is
  10. ## implemented for other databases too.
  11. ##
  12. ## See also: `db_odbc <db_odbc.html>`_, `db_sqlite <db_sqlite.html>`_,
  13. ## `db_postgres <db_postgres.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. ##
  26. ## Examples
  27. ## --------
  28. ##
  29. ## Opening a connection to a database
  30. ## ==================================
  31. ##
  32. ## .. code-block:: Nim
  33. ## import db_mysql
  34. ## let db = open("localhost", "user", "password", "dbname")
  35. ## db.close()
  36. ##
  37. ## Creating a table
  38. ## ================
  39. ##
  40. ## .. code-block:: Nim
  41. ## db.exec(sql"DROP TABLE IF EXISTS myTable")
  42. ## db.exec(sql("""CREATE TABLE myTable (
  43. ## id integer,
  44. ## name varchar(50) not null)"""))
  45. ##
  46. ## Inserting data
  47. ## ==============
  48. ##
  49. ## .. code-block:: Nim
  50. ## db.exec(sql"INSERT INTO myTable (id, name) VALUES (0, ?)",
  51. ## "Dominik")
  52. ##
  53. ## Larger example
  54. ## ==============
  55. ##
  56. ## .. code-block:: Nim
  57. ##
  58. ## import db_mysql, math
  59. ##
  60. ## let theDb = open("localhost", "nim", "nim", "test")
  61. ##
  62. ## theDb.exec(sql"Drop table if exists myTestTbl")
  63. ## theDb.exec(sql("create table myTestTbl (" &
  64. ## " Id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, " &
  65. ## " Name VARCHAR(50) NOT NULL, " &
  66. ## " i INT(11), " &
  67. ## " f DECIMAL(18,10))"))
  68. ##
  69. ## theDb.exec(sql"START TRANSACTION")
  70. ## for i in 1..1000:
  71. ## theDb.exec(sql"INSERT INTO myTestTbl (name,i,f) VALUES (?,?,?)",
  72. ## "Item#" & $i, i, sqrt(i.float))
  73. ## theDb.exec(sql"COMMIT")
  74. ##
  75. ## for x in theDb.fastRows(sql"select * from myTestTbl"):
  76. ## echo x
  77. ##
  78. ## let id = theDb.tryInsertId(sql"INSERT INTO myTestTbl (name,i,f) VALUES (?,?,?)",
  79. ## "Item#1001", 1001, sqrt(1001.0))
  80. ## echo "Inserted item: ", theDb.getValue(sql"SELECT name FROM myTestTbl WHERE id=?", id)
  81. ##
  82. ## theDb.close()
  83. import strutils, mysql
  84. import db_common
  85. export db_common
  86. type
  87. DbConn* = PMySQL ## encapsulates a database connection
  88. Row* = seq[string] ## a row of a dataset. NULL database values will be
  89. ## converted to nil.
  90. InstantRow* = object ## a handle that can be used to get a row's
  91. ## column text on demand
  92. row: cstringArray
  93. len: int
  94. {.deprecated: [TRow: Row, TDbConn: DbConn].}
  95. proc dbError*(db: DbConn) {.noreturn.} =
  96. ## raises a DbError exception.
  97. var e: ref DbError
  98. new(e)
  99. e.msg = $mysql.error(db)
  100. raise e
  101. when false:
  102. proc dbQueryOpt*(db: DbConn, query: string, args: varargs[string, `$`]) =
  103. var stmt = mysql_stmt_init(db)
  104. if stmt == nil: dbError(db)
  105. if mysql_stmt_prepare(stmt, query, len(query)) != 0:
  106. dbError(db)
  107. var
  108. binding: seq[MYSQL_BIND]
  109. discard mysql_stmt_close(stmt)
  110. proc dbQuote*(s: string): string =
  111. ## DB quotes the string.
  112. result = "'"
  113. for c in items(s):
  114. if c == '\'': add(result, "''")
  115. else: add(result, c)
  116. add(result, '\'')
  117. proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string =
  118. result = ""
  119. var a = 0
  120. for c in items(string(formatstr)):
  121. if c == '?':
  122. if args[a] == nil:
  123. add(result, "NULL")
  124. else:
  125. add(result, dbQuote(args[a]))
  126. inc(a)
  127. else:
  128. add(result, c)
  129. proc tryExec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): bool {.
  130. tags: [ReadDbEffect, WriteDbEffect].} =
  131. ## tries to execute the query and returns true if successful, false otherwise.
  132. var q = dbFormat(query, args)
  133. return mysql.realQuery(db, q, q.len) == 0'i32
  134. proc rawExec(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) =
  135. var q = dbFormat(query, args)
  136. if mysql.realQuery(db, q, q.len) != 0'i32: dbError(db)
  137. proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) {.
  138. tags: [ReadDbEffect, WriteDbEffect].} =
  139. ## executes the query and raises EDB if not successful.
  140. var q = dbFormat(query, args)
  141. if mysql.realQuery(db, q, q.len) != 0'i32: dbError(db)
  142. proc newRow(L: int): Row =
  143. newSeq(result, L)
  144. for i in 0..L-1: result[i] = ""
  145. proc properFreeResult(sqlres: mysql.PRES, row: cstringArray) =
  146. if row != nil:
  147. while mysql.fetchRow(sqlres) != nil: discard
  148. mysql.freeResult(sqlres)
  149. iterator fastRows*(db: DbConn, query: SqlQuery,
  150. args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
  151. ## executes the query and iterates over the result dataset.
  152. ##
  153. ## This is very fast, but potentially dangerous. Use this iterator only
  154. ## if you require **ALL** the rows.
  155. ##
  156. ## Breaking the fastRows() iterator during a loop will cause the next
  157. ## database query to raise an [EDb] exception ``Commands out of sync``.
  158. rawExec(db, query, args)
  159. var sqlres = mysql.useResult(db)
  160. if sqlres != nil:
  161. var
  162. L = int(mysql.numFields(sqlres))
  163. row: cstringArray
  164. result: Row
  165. backup: Row
  166. newSeq(result, L)
  167. while true:
  168. row = mysql.fetchRow(sqlres)
  169. if row == nil: break
  170. for i in 0..L-1:
  171. if row[i] == nil:
  172. if backup == nil:
  173. newSeq(backup, L)
  174. if backup[i] == nil and result[i] != nil:
  175. shallowCopy(backup[i], result[i])
  176. result[i] = nil
  177. else:
  178. if result[i] == nil:
  179. if backup != nil:
  180. if backup[i] == nil:
  181. backup[i] = ""
  182. shallowCopy(result[i], backup[i])
  183. setLen(result[i], 0)
  184. else:
  185. result[i] = ""
  186. else:
  187. setLen(result[i], 0)
  188. add(result[i], row[i])
  189. yield result
  190. properFreeResult(sqlres, row)
  191. iterator instantRows*(db: DbConn, query: SqlQuery,
  192. args: varargs[string, `$`]): InstantRow
  193. {.tags: [ReadDbEffect].} =
  194. ## Same as fastRows but returns a handle that can be used to get column text
  195. ## on demand using []. Returned handle is valid only within the iterator body.
  196. rawExec(db, query, args)
  197. var sqlres = mysql.useResult(db)
  198. if sqlres != nil:
  199. let L = int(mysql.numFields(sqlres))
  200. var row: cstringArray
  201. while true:
  202. row = mysql.fetchRow(sqlres)
  203. if row == nil: break
  204. yield InstantRow(row: row, len: L)
  205. properFreeResult(sqlres, row)
  206. proc setTypeName(t: var DbType; f: PFIELD) =
  207. shallowCopy(t.name, $f.name)
  208. t.maxReprLen = Natural(f.max_length)
  209. if (NOT_NULL_FLAG and f.flags) != 0: t.notNull = true
  210. case f.ftype
  211. of TYPE_DECIMAL:
  212. t.kind = dbDecimal
  213. of TYPE_TINY:
  214. t.kind = dbInt
  215. t.size = 1
  216. of TYPE_SHORT:
  217. t.kind = dbInt
  218. t.size = 2
  219. of TYPE_LONG:
  220. t.kind = dbInt
  221. t.size = 4
  222. of TYPE_FLOAT:
  223. t.kind = dbFloat
  224. t.size = 4
  225. of TYPE_DOUBLE:
  226. t.kind = dbFloat
  227. t.size = 8
  228. of TYPE_NULL:
  229. t.kind = dbNull
  230. of TYPE_TIMESTAMP:
  231. t.kind = dbTimestamp
  232. of TYPE_LONGLONG:
  233. t.kind = dbInt
  234. t.size = 8
  235. of TYPE_INT24:
  236. t.kind = dbInt
  237. t.size = 3
  238. of TYPE_DATE:
  239. t.kind = dbDate
  240. of TYPE_TIME:
  241. t.kind = dbTime
  242. of TYPE_DATETIME:
  243. t.kind = dbDatetime
  244. of TYPE_YEAR:
  245. t.kind = dbDate
  246. of TYPE_NEWDATE:
  247. t.kind = dbDate
  248. of TYPE_VARCHAR, TYPE_VAR_STRING, TYPE_STRING:
  249. t.kind = dbVarchar
  250. of TYPE_BIT:
  251. t.kind = dbBit
  252. of TYPE_NEWDECIMAL:
  253. t.kind = dbDecimal
  254. of TYPE_ENUM: t.kind = dbEnum
  255. of TYPE_SET: t.kind = dbSet
  256. of TYPE_TINY_BLOB, TYPE_MEDIUM_BLOB, TYPE_LONG_BLOB,
  257. TYPE_BLOB: t.kind = dbBlob
  258. of TYPE_GEOMETRY:
  259. t.kind = dbGeometry
  260. proc setColumnInfo(columns: var DbColumns; res: PRES; L: int) =
  261. setLen(columns, L)
  262. for i in 0..<L:
  263. let fp = mysql.fetch_field_direct(res, cint(i))
  264. setTypeName(columns[i].typ, fp)
  265. columns[i].name = $fp.name
  266. columns[i].tableName = $fp.table
  267. columns[i].primaryKey = (fp.flags and PRI_KEY_FLAG) != 0
  268. #columns[i].foreignKey = there is no such thing in mysql
  269. iterator instantRows*(db: DbConn; columns: var DbColumns; query: SqlQuery;
  270. args: varargs[string, `$`]): InstantRow =
  271. ## Same as fastRows but returns a handle that can be used to get column text
  272. ## on demand using []. Returned handle is valid only within the iterator body.
  273. rawExec(db, query, args)
  274. var sqlres = mysql.useResult(db)
  275. if sqlres != nil:
  276. let L = int(mysql.numFields(sqlres))
  277. setColumnInfo(columns, sqlres, L)
  278. var row: cstringArray
  279. while true:
  280. row = mysql.fetchRow(sqlres)
  281. if row == nil: break
  282. yield InstantRow(row: row, len: L)
  283. properFreeResult(sqlres, row)
  284. proc `[]`*(row: InstantRow, col: int): string {.inline.} =
  285. ## Returns text for given column of the row.
  286. $row.row[col]
  287. proc len*(row: InstantRow): int {.inline.} =
  288. ## Returns number of columns in the row.
  289. row.len
  290. proc getRow*(db: DbConn, query: SqlQuery,
  291. args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
  292. ## Retrieves a single row. If the query doesn't return any rows, this proc
  293. ## will return a Row with empty strings for each column.
  294. rawExec(db, query, args)
  295. var sqlres = mysql.useResult(db)
  296. if sqlres != nil:
  297. var L = int(mysql.numFields(sqlres))
  298. result = newRow(L)
  299. var row = mysql.fetchRow(sqlres)
  300. if row != nil:
  301. for i in 0..L-1:
  302. setLen(result[i], 0)
  303. if row[i] == nil:
  304. result[i] = nil
  305. else:
  306. add(result[i], row[i])
  307. properFreeResult(sqlres, row)
  308. proc getAllRows*(db: DbConn, query: SqlQuery,
  309. args: varargs[string, `$`]): seq[Row] {.tags: [ReadDbEffect].} =
  310. ## executes the query and returns the whole result dataset.
  311. result = @[]
  312. rawExec(db, query, args)
  313. var sqlres = mysql.useResult(db)
  314. if sqlres != nil:
  315. var L = int(mysql.numFields(sqlres))
  316. var row: cstringArray
  317. var j = 0
  318. while true:
  319. row = mysql.fetchRow(sqlres)
  320. if row == nil: break
  321. setLen(result, j+1)
  322. newSeq(result[j], L)
  323. for i in 0..L-1:
  324. if row[i] == nil:
  325. result[j][i] = nil
  326. else:
  327. result[j][i] = $row[i]
  328. inc(j)
  329. mysql.freeResult(sqlres)
  330. iterator rows*(db: DbConn, query: SqlQuery,
  331. args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
  332. ## same as `fastRows`, but slower and safe.
  333. for r in items(getAllRows(db, query, args)): yield r
  334. proc getValue*(db: DbConn, query: SqlQuery,
  335. args: varargs[string, `$`]): string {.tags: [ReadDbEffect].} =
  336. ## executes the query and returns the first column of the first row of the
  337. ## result dataset. Returns "" if the dataset contains no rows or the database
  338. ## value is NULL.
  339. result = getRow(db, query, args)[0]
  340. proc tryInsertId*(db: DbConn, query: SqlQuery,
  341. args: varargs[string, `$`]): int64 {.tags: [WriteDbEffect].} =
  342. ## executes the query (typically "INSERT") and returns the
  343. ## generated ID for the row or -1 in case of an error.
  344. var q = dbFormat(query, args)
  345. if mysql.realQuery(db, q, q.len) != 0'i32:
  346. result = -1'i64
  347. else:
  348. result = mysql.insertId(db)
  349. proc insertId*(db: DbConn, query: SqlQuery,
  350. args: varargs[string, `$`]): int64 {.tags: [WriteDbEffect].} =
  351. ## executes the query (typically "INSERT") and returns the
  352. ## generated ID for the row.
  353. result = tryInsertID(db, query, args)
  354. if result < 0: dbError(db)
  355. proc execAffectedRows*(db: DbConn, query: SqlQuery,
  356. args: varargs[string, `$`]): int64 {.
  357. tags: [ReadDbEffect, WriteDbEffect].} =
  358. ## runs the query (typically "UPDATE") and returns the
  359. ## number of affected rows
  360. rawExec(db, query, args)
  361. result = mysql.affectedRows(db)
  362. proc close*(db: DbConn) {.tags: [DbEffect].} =
  363. ## closes the database connection.
  364. if db != nil: mysql.close(db)
  365. proc open*(connection, user, password, database: string): DbConn {.
  366. tags: [DbEffect].} =
  367. ## opens a database connection. Raises `EDb` if the connection could not
  368. ## be established.
  369. result = mysql.init(nil)
  370. if result == nil: dbError("could not open database connection")
  371. let
  372. colonPos = connection.find(':')
  373. host = if colonPos < 0: connection
  374. else: substr(connection, 0, colonPos-1)
  375. port: int32 = if colonPos < 0: 0'i32
  376. else: substr(connection, colonPos+1).parseInt.int32
  377. if mysql.realConnect(result, host, user, password, database,
  378. port, nil, 0) == nil:
  379. var errmsg = $mysql.error(result)
  380. db_mysql.close(result)
  381. dbError(errmsg)
  382. proc setEncoding*(connection: DbConn, encoding: string): bool {.
  383. tags: [DbEffect].} =
  384. ## sets the encoding of a database connection, returns true for
  385. ## success, false for failure.
  386. result = mysql.set_character_set(connection, encoding) == 0