# # # Nim's Runtime Library # (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## A higher level `mySQL`:idx: database wrapper. The same interface is ## implemented for other databases too. ## ## See also: `db_odbc `_, `db_sqlite `_, ## `db_postgres `_. ## ## Parameter substitution ## ====================== ## ## All ``db_*`` modules support the same form of parameter substitution. ## That is, using the ``?`` (question mark) to signify the place where a ## value should be placed. For example: ## ## .. code-block:: Nim ## sql"INSERT INTO myTable (colA, colB, colC) VALUES (?, ?, ?)" ## ## ## Examples ## ======== ## ## Opening a connection to a database ## ---------------------------------- ## ## .. code-block:: Nim ## import db_mysql ## let db = open("localhost", "user", "password", "dbname") ## db.close() ## ## Creating a table ## ---------------- ## ## .. code-block:: Nim ## db.exec(sql"DROP TABLE IF EXISTS myTable") ## db.exec(sql("""CREATE TABLE myTable ( ## id integer, ## name varchar(50) not null)""")) ## ## Inserting data ## -------------- ## ## .. code-block:: Nim ## db.exec(sql"INSERT INTO myTable (id, name) VALUES (0, ?)", ## "Dominik") ## ## Larger example ## -------------- ## ## .. code-block:: Nim ## ## import db_mysql, math ## ## let theDb = open("localhost", "nim", "nim", "test") ## ## theDb.exec(sql"Drop table if exists myTestTbl") ## theDb.exec(sql("create table myTestTbl (" & ## " Id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, " & ## " Name VARCHAR(50) NOT NULL, " & ## " i INT(11), " & ## " f DECIMAL(18,10))")) ## ## theDb.exec(sql"START TRANSACTION") ## for i in 1..1000: ## theDb.exec(sql"INSERT INTO myTestTbl (name,i,f) VALUES (?,?,?)", ## "Item#" & $i, i, sqrt(i.float)) ## theDb.exec(sql"COMMIT") ## ## for x in theDb.fastRows(sql"select * from myTestTbl"): ## echo x ## ## let id = theDb.tryInsertId(sql"INSERT INTO myTestTbl (name,i,f) VALUES (?,?,?)", ## "Item#1001", 1001, sqrt(1001.0)) ## echo "Inserted item: ", theDb.getValue(sql"SELECT name FROM myTestTbl WHERE id=?", id) ## ## theDb.close() import strutils, mysql import db_common export db_common type DbConn* = distinct PMySQL ## encapsulates a database connection Row* = seq[string] ## a row of a dataset. NULL database values will be ## converted to nil. InstantRow* = object ## a handle that can be used to get a row's ## column text on demand row: cstringArray len: int proc dbError*(db: DbConn) {.noreturn.} = ## raises a DbError exception. var e: ref DbError new(e) e.msg = $mysql.error(PMySQL db) raise e when false: proc dbQueryOpt*(db: DbConn, query: string, args: varargs[string, `$`]) = var stmt = mysql_stmt_init(db) if stmt == nil: dbError(db) if mysql_stmt_prepare(stmt, query, len(query)) != 0: dbError(db) var binding: seq[MYSQL_BIND] discard mysql_stmt_close(stmt) proc dbQuote*(s: string): string = ## DB quotes the string. 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 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 q = dbFormat(query, args) return mysql.realQuery(PMySQL db, q, q.len) == 0'i32 proc rawExec(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) = var q = dbFormat(query, args) if mysql.realQuery(PMySQL db, q, q.len) != 0'i32: dbError(db) proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) {. tags: [ReadDbEffect, WriteDbEffect].} = ## executes the query and raises EDB if not successful. var q = dbFormat(query, args) if mysql.realQuery(PMySQL db, q, q.len) != 0'i32: dbError(db) proc newRow(L: int): Row = newSeq(result, L) for i in 0..L-1: result[i] = "" proc properFreeResult(sqlres: mysql.PRES, row: cstringArray) = if row != nil: while mysql.fetchRow(sqlres) != nil: discard mysql.freeResult(sqlres) 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 potentially dangerous. Use this iterator only ## if you require **ALL** the rows. ## ## Breaking the fastRows() iterator during a loop will cause the next ## database query to raise an [EDb] exception ``Commands out of sync``. rawExec(db, query, args) var sqlres = mysql.useResult(PMySQL db) if sqlres != nil: var L = int(mysql.numFields(sqlres)) row: cstringArray result: Row backup: Row newSeq(result, L) while true: row = mysql.fetchRow(sqlres) if row == nil: break for i in 0..L-1: setLen(result[i], 0) result[i].add row[i] yield result properFreeResult(sqlres, row) 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 the iterator body. rawExec(db, query, args) var sqlres = mysql.useResult(PMySQL db) if sqlres != nil: let L = int(mysql.numFields(sqlres)) var row: cstringArray while true: row = mysql.fetchRow(sqlres) if row == nil: break yield InstantRow(row: row, len: L) properFreeResult(sqlres, row) proc setTypeName(t: var DbType; f: PFIELD) = t.name = $f.name t.maxReprLen = Natural(f.max_length) if (NOT_NULL_FLAG and f.flags) != 0: t.notNull = true case f.ftype of TYPE_DECIMAL: t.kind = dbDecimal of TYPE_TINY: t.kind = dbInt t.size = 1 of TYPE_SHORT: t.kind = dbInt t.size = 2 of TYPE_LONG: t.kind = dbInt t.size = 4 of TYPE_FLOAT: t.kind = dbFloat t.size = 4 of TYPE_DOUBLE: t.kind = dbFloat t.size = 8 of TYPE_NULL: t.kind = dbNull of TYPE_TIMESTAMP: t.kind = dbTimestamp of TYPE_LONGLONG: t.kind = dbInt t.size = 8 of TYPE_INT24: t.kind = dbInt t.size = 3 of TYPE_DATE: t.kind = dbDate of TYPE_TIME: t.kind = dbTime of TYPE_DATETIME: t.kind = dbDatetime of TYPE_YEAR: t.kind = dbDate of TYPE_NEWDATE: t.kind = dbDate of TYPE_VARCHAR, TYPE_VAR_STRING, TYPE_STRING: t.kind = dbVarchar of TYPE_BIT: t.kind = dbBit of TYPE_NEWDECIMAL: t.kind = dbDecimal of TYPE_ENUM: t.kind = dbEnum of TYPE_SET: t.kind = dbSet of TYPE_TINY_BLOB, TYPE_MEDIUM_BLOB, TYPE_LONG_BLOB, TYPE_BLOB: t.kind = dbBlob of TYPE_GEOMETRY: t.kind = dbGeometry proc setColumnInfo(columns: var DbColumns; res: PRES; L: int) = setLen(columns, L) for i in 0..