Mercurial > games > semicongine
comparison semiconginev2/thirdparty/db_connector/db_sqlite.nim @ 1218:56781cc0fc7c compiletime-tests
did: renamge main package
author | sam <sam@basx.dev> |
---|---|
date | Wed, 17 Jul 2024 21:01:37 +0700 |
parents | semicongine/old/thirdparty/db_connector/db_sqlite.nim@239adab121a3 |
children |
comparison
equal
deleted
inserted
replaced
1217:f819a874058f | 1218:56781cc0fc7c |
---|---|
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 | |
10 ## .. note:: In order to use this module, run `nimble install db_connector`. | |
11 ## | |
12 ## A higher level `SQLite`:idx: database wrapper. This interface | |
13 ## is implemented for other databases too. | |
14 ## | |
15 ## Basic usage | |
16 ## =========== | |
17 ## | |
18 ## The basic flow of using this module is: | |
19 ## | |
20 ## 1. Open database connection | |
21 ## 2. Execute SQL query | |
22 ## 3. Close database connection | |
23 ## | |
24 ## Parameter substitution | |
25 ## ---------------------- | |
26 ## | |
27 ## All `db_*` modules support the same form of parameter substitution. | |
28 ## That is, using the `?` (question mark) to signify the place where a | |
29 ## value should be placed. For example: | |
30 ## | |
31 ## ```Nim | |
32 ## sql"INSERT INTO my_table (colA, colB, colC) VALUES (?, ?, ?)" | |
33 ## ``` | |
34 ## | |
35 ## Opening a connection to a database | |
36 ## ---------------------------------- | |
37 ## | |
38 ## ```Nim | |
39 ## import db_connector/db_sqlite | |
40 ## | |
41 ## # user, password, database name can be empty. | |
42 ## # These params are not used on db_sqlite module. | |
43 ## let db = open("mytest.db", "", "", "") | |
44 ## db.close() | |
45 ## ``` | |
46 ## | |
47 ## Creating a table | |
48 ## ---------------- | |
49 ## | |
50 ## ```Nim | |
51 ## db.exec(sql"DROP TABLE IF EXISTS my_table") | |
52 ## db.exec(sql"""CREATE TABLE my_table ( | |
53 ## id INTEGER, | |
54 ## name VARCHAR(50) NOT NULL | |
55 ## )""") | |
56 ## ``` | |
57 ## | |
58 ## Inserting data | |
59 ## -------------- | |
60 ## | |
61 ## ```Nim | |
62 ## db.exec(sql"INSERT INTO my_table (id, name) VALUES (0, ?)", | |
63 ## "Jack") | |
64 ## ``` | |
65 ## | |
66 ## Larger example | |
67 ## -------------- | |
68 ## | |
69 ## ```Nim | |
70 ## import db_connector/db_sqlite | |
71 ## import std/math | |
72 ## | |
73 ## let db = open("mytest.db", "", "", "") | |
74 ## | |
75 ## db.exec(sql"DROP TABLE IF EXISTS my_table") | |
76 ## db.exec(sql"""CREATE TABLE my_table ( | |
77 ## id INTEGER PRIMARY KEY, | |
78 ## name VARCHAR(50) NOT NULL, | |
79 ## i INT(11), | |
80 ## f DECIMAL(18, 10) | |
81 ## )""") | |
82 ## | |
83 ## db.exec(sql"BEGIN") | |
84 ## for i in 1..1000: | |
85 ## db.exec(sql"INSERT INTO my_table (name, i, f) VALUES (?, ?, ?)", | |
86 ## "Item#" & $i, i, sqrt(i.float)) | |
87 ## db.exec(sql"COMMIT") | |
88 ## | |
89 ## for x in db.fastRows(sql"SELECT * FROM my_table"): | |
90 ## echo x | |
91 ## | |
92 ## let id = db.tryInsertId(sql"""INSERT INTO my_table (name, i, f) | |
93 ## VALUES (?, ?, ?)""", | |
94 ## "Item#1001", 1001, sqrt(1001.0)) | |
95 ## echo "Inserted item: ", db.getValue(sql"SELECT name FROM my_table WHERE id=?", id) | |
96 ## | |
97 ## db.close() | |
98 ## ``` | |
99 ## | |
100 ## Storing binary data example | |
101 ##---------------------------- | |
102 ## | |
103 ## ```nim | |
104 ## import std/random | |
105 ## | |
106 ## ## Generate random float datas | |
107 ## var orig = newSeq[float64](150) | |
108 ## randomize() | |
109 ## for x in orig.mitems: | |
110 ## x = rand(1.0)/10.0 | |
111 ## | |
112 ## let db = open("mysqlite.db", "", "", "") | |
113 ## block: ## Create database | |
114 ## ## Binary datas needs to be of type BLOB in SQLite | |
115 ## let createTableStr = sql"""CREATE TABLE test( | |
116 ## id INTEGER NOT NULL PRIMARY KEY, | |
117 ## data BLOB | |
118 ## ) | |
119 ## """ | |
120 ## db.exec(createTableStr) | |
121 ## | |
122 ## block: ## Insert data | |
123 ## var id = 1 | |
124 ## ## Data needs to be converted to seq[byte] to be interpreted as binary by bindParams | |
125 ## var dbuf = newSeq[byte](orig.len*sizeof(float64)) | |
126 ## copyMem(unsafeAddr(dbuf[0]), unsafeAddr(orig[0]), dbuf.len) | |
127 ## | |
128 ## ## Use prepared statement to insert binary data into database | |
129 ## var insertStmt = db.prepare("INSERT INTO test (id, data) VALUES (?, ?)") | |
130 ## insertStmt.bindParams(id, dbuf) | |
131 ## let bres = db.tryExec(insertStmt) | |
132 ## ## Check insert | |
133 ## doAssert(bres) | |
134 ## # Destroy statement | |
135 ## finalize(insertStmt) | |
136 ## | |
137 ## block: ## Use getValue to select data | |
138 ## var dataTest = db.getValue(sql"SELECT data FROM test WHERE id = ?", 1) | |
139 ## ## Calculate sequence size from buffer size | |
140 ## let seqSize = int(dataTest.len*sizeof(byte)/sizeof(float64)) | |
141 ## ## Copy binary string data in dataTest into a seq | |
142 ## var res: seq[float64] = newSeq[float64](seqSize) | |
143 ## copyMem(unsafeAddr(res[0]), addr(dataTest[0]), dataTest.len) | |
144 ## | |
145 ## ## Check datas obtained is identical | |
146 ## doAssert res == orig | |
147 ## | |
148 ## db.close() | |
149 ## ``` | |
150 ## | |
151 ## | |
152 ## Note | |
153 ## ==== | |
154 ## This module does not implement any ORM features such as mapping the types from the schema. | |
155 ## Instead, a `seq[string]` is returned for each row. | |
156 ## | |
157 ## The reasoning is as follows: | |
158 ## 1. it's close to what many DBs offer natively (`char**`:c:) | |
159 ## 2. it hides the number of types that the DB supports | |
160 ## (int? int64? decimal up to 10 places? geo coords?) | |
161 ## 3. it's convenient when all you do is to forward the data to somewhere else (echo, log, put the data into a new query) | |
162 ## | |
163 ## See also | |
164 ## ======== | |
165 ## | |
166 ## * `db_odbc module <db_odbc.html>`_ for ODBC database wrapper | |
167 ## * `db_mysql module <db_mysql.html>`_ for MySQL database wrapper | |
168 ## * `db_postgres module <db_postgres.html>`_ for PostgreSQL database wrapper | |
169 | |
170 | |
171 import ./sqlite3, macros | |
172 | |
173 import ./db_common | |
174 export db_common | |
175 | |
176 import private/dbutils | |
177 | |
178 import std/private/[since] | |
179 when defined(nimPreviewSlimSystem): | |
180 import std/assertions | |
181 | |
182 type | |
183 DbConn* = PSqlite3 ## Encapsulates a database connection. | |
184 Row* = seq[string] ## A row of a dataset. `NULL` database values will be | |
185 ## converted to an empty string. | |
186 InstantRow* = PStmt ## A handle that can be used to get a row's column | |
187 ## text on demand. | |
188 SqlPrepared* = distinct PStmt ## a identifier for the prepared queries | |
189 | |
190 proc bindParam*(ps: SqlPrepared, paramIdx: int, val: int32) {.since: (1, 3).} | |
191 proc bindParam*(ps: SqlPrepared, paramIdx: int, val: int64) {.since: (1, 3).} | |
192 proc bindParam*(ps: SqlPrepared, paramIdx: int, val: int) {.since: (1, 3).} | |
193 proc bindParam*(ps: SqlPrepared, paramIdx: int, val: float64) {.since: (1, 3).} | |
194 proc bindNull*(ps: SqlPrepared, paramIdx: int) {.since: (1, 3).} | |
195 proc bindParam*(ps: SqlPrepared, paramIdx: int, val: string, copy = true) {.since: (1, 3).} | |
196 proc bindParam*(ps: SqlPrepared, paramIdx: int,val: openArray[byte], copy = true) {.since: (1, 3).} | |
197 | |
198 proc dbError*(db: DbConn) {.noreturn.} = | |
199 ## Raises a `DbError` exception. | |
200 ## | |
201 ## **Examples:** | |
202 ## ```Nim | |
203 ## let db = open("mytest.db", "", "", "") | |
204 ## if not db.tryExec(sql"SELECT * FROM not_exist_table"): | |
205 ## dbError(db) | |
206 ## db.close() | |
207 ## ``` | |
208 var e: ref DbError | |
209 new(e) | |
210 e.msg = $sqlite3.errmsg(db) | |
211 raise e | |
212 | |
213 proc dbQuote*(s: string): string = | |
214 ## Escapes the `'` (single quote) char to `''`. | |
215 ## Because single quote is used for defining `VARCHAR` in SQL. | |
216 runnableExamples: | |
217 doAssert dbQuote("'") == "''''" | |
218 doAssert dbQuote("A Foobar's pen.") == "'A Foobar''s pen.'" | |
219 | |
220 result = "'" | |
221 for c in items(s): | |
222 if c == '\'': add(result, "''") | |
223 else: add(result, c) | |
224 add(result, '\'') | |
225 | |
226 proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string = | |
227 dbFormatImpl(formatstr, dbQuote, args) | |
228 | |
229 proc prepare*(db: DbConn; q: string): SqlPrepared {.since: (1, 3).} = | |
230 ## Creates a new `SqlPrepared` statement. | |
231 if prepare_v2(db, q, q.len.cint,result.PStmt, nil) != SQLITE_OK: | |
232 discard finalize(result.PStmt) | |
233 dbError(db) | |
234 | |
235 proc tryExec*(db: DbConn, query: SqlQuery, | |
236 args: varargs[string, `$`]): bool {. | |
237 tags: [ReadDbEffect, WriteDbEffect].} = | |
238 ## Tries to execute the query and returns `true` if successful, `false` otherwise. | |
239 ## | |
240 ## **Examples:** | |
241 ## ```Nim | |
242 ## let db = open("mytest.db", "", "", "") | |
243 ## if not db.tryExec(sql"SELECT * FROM my_table"): | |
244 ## dbError(db) | |
245 ## db.close() | |
246 ## ``` | |
247 assert(not db.isNil, "Database not connected.") | |
248 var q = dbFormat(query, args) | |
249 var stmt: sqlite3.PStmt | |
250 if prepare_v2(db, q.cstring, q.len.cint, stmt, nil) == SQLITE_OK: | |
251 let x = step(stmt) | |
252 if x in [SQLITE_DONE, SQLITE_ROW]: | |
253 result = finalize(stmt) == SQLITE_OK | |
254 else: | |
255 discard finalize(stmt) | |
256 result = false | |
257 | |
258 proc tryExec*(db: DbConn, stmtName: SqlPrepared): bool {. | |
259 tags: [ReadDbEffect, WriteDbEffect].} = | |
260 let x = step(stmtName.PStmt) | |
261 if x in [SQLITE_DONE, SQLITE_ROW]: | |
262 result = true | |
263 else: | |
264 discard finalize(stmtName.PStmt) | |
265 result = false | |
266 | |
267 proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) {. | |
268 tags: [ReadDbEffect, WriteDbEffect].} = | |
269 ## Executes the query and raises a `DbError` exception if not successful. | |
270 ## | |
271 ## **Examples:** | |
272 ## ```Nim | |
273 ## let db = open("mytest.db", "", "", "") | |
274 ## try: | |
275 ## db.exec(sql"INSERT INTO my_table (id, name) VALUES (?, ?)", | |
276 ## 1, "item#1") | |
277 ## except: | |
278 ## stderr.writeLine(getCurrentExceptionMsg()) | |
279 ## finally: | |
280 ## db.close() | |
281 ## ``` | |
282 if not tryExec(db, query, args): dbError(db) | |
283 | |
284 macro untypedLen(args: varargs[untyped]): int = | |
285 newLit(args.len) | |
286 | |
287 macro bindParams*(ps: SqlPrepared, params: varargs[untyped]): untyped {.since: (1, 3).} = | |
288 let bindParam = bindSym("bindParam", brOpen) | |
289 let bindNull = bindSym("bindNull") | |
290 let preparedStatement = genSym() | |
291 result = newStmtList() | |
292 # Store `ps` in a temporary variable. This prevents `ps` from being evaluated every call. | |
293 result.add newNimNode(nnkLetSection).add(newIdentDefs(preparedStatement, newEmptyNode(), ps)) | |
294 for idx, param in params: | |
295 if param.kind != nnkNilLit: | |
296 result.add newCall(bindParam, preparedStatement, newIntLitNode idx + 1, param) | |
297 else: | |
298 result.add newCall(bindNull, preparedStatement, newIntLitNode idx + 1) | |
299 | |
300 | |
301 template exec*(db: DbConn, stmtName: SqlPrepared, | |
302 args: varargs[typed]): untyped = | |
303 when untypedLen(args) > 0: | |
304 if reset(stmtName.PStmt) != SQLITE_OK: | |
305 dbError(db) | |
306 if clear_bindings(stmtName.PStmt) != SQLITE_OK: | |
307 dbError(db) | |
308 stmtName.bindParams(args) | |
309 if not tryExec(db, stmtName): dbError(db) | |
310 | |
311 proc newRow(L: int): Row = | |
312 newSeq(result, L) | |
313 for i in 0..L-1: result[i] = "" | |
314 | |
315 proc setupQuery(db: DbConn, query: SqlQuery, | |
316 args: varargs[string]): PStmt = | |
317 assert(not db.isNil, "Database not connected.") | |
318 var q = dbFormat(query, args) | |
319 if prepare_v2(db, q.cstring, q.len.cint, result, nil) != SQLITE_OK: dbError(db) | |
320 | |
321 proc setupQuery(db: DbConn, stmtName: SqlPrepared): SqlPrepared {.since: (1, 3).} = | |
322 assert(not db.isNil, "Database not connected.") | |
323 result = stmtName | |
324 | |
325 proc setRow(stmt: PStmt, r: var Row, cols: cint) = | |
326 for col in 0'i32..cols-1: | |
327 let cb = column_bytes(stmt, col) | |
328 setLen(r[col], cb) # set capacity | |
329 if column_type(stmt, col) == SQLITE_BLOB: | |
330 copyMem(addr(r[col][0]), column_blob(stmt, col), cb) | |
331 else: | |
332 setLen(r[col], 0) | |
333 let x = column_text(stmt, col) | |
334 if not isNil(x): add(r[col], x) | |
335 | |
336 iterator fastRows*(db: DbConn, query: SqlQuery, | |
337 args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} = | |
338 ## Executes the query and iterates over the result dataset. | |
339 ## | |
340 ## This is very fast, but potentially dangerous. Use this iterator only | |
341 ## if you require **ALL** the rows. | |
342 ## | |
343 ## **Note:** Breaking the `fastRows()` iterator during a loop will cause the | |
344 ## next database query to raise a `DbError` exception `unable to close due | |
345 ## to ...`. | |
346 ## | |
347 ## **Examples:** | |
348 ## | |
349 ## ```Nim | |
350 ## let db = open("mytest.db", "", "", "") | |
351 ## | |
352 ## # Records of my_table: | |
353 ## # | id | name | | |
354 ## # |----|----------| | |
355 ## # | 1 | item#1 | | |
356 ## # | 2 | item#2 | | |
357 ## | |
358 ## for row in db.fastRows(sql"SELECT id, name FROM my_table"): | |
359 ## echo row | |
360 ## | |
361 ## # Output: | |
362 ## # @["1", "item#1"] | |
363 ## # @["2", "item#2"] | |
364 ## | |
365 ## db.close() | |
366 ## ``` | |
367 var stmt = setupQuery(db, query, args) | |
368 var L = (column_count(stmt)) | |
369 var result = newRow(L) | |
370 try: | |
371 while step(stmt) == SQLITE_ROW: | |
372 setRow(stmt, result, L) | |
373 yield result | |
374 finally: | |
375 if finalize(stmt) != SQLITE_OK: dbError(db) | |
376 | |
377 iterator fastRows*(db: DbConn, stmtName: SqlPrepared): Row | |
378 {.tags: [ReadDbEffect,WriteDbEffect], since: (1, 3).} = | |
379 discard setupQuery(db, stmtName) | |
380 var L = (column_count(stmtName.PStmt)) | |
381 var result = newRow(L) | |
382 try: | |
383 while step(stmtName.PStmt) == SQLITE_ROW: | |
384 setRow(stmtName.PStmt, result, L) | |
385 yield result | |
386 except: | |
387 dbError(db) | |
388 | |
389 iterator instantRows*(db: DbConn, query: SqlQuery, | |
390 args: varargs[string, `$`]): InstantRow | |
391 {.tags: [ReadDbEffect].} = | |
392 ## Similar to `fastRows iterator <#fastRows.i,DbConn,SqlQuery,varargs[string,]>`_ | |
393 ## but returns a handle that can be used to get column text | |
394 ## on demand using `[]`. Returned handle is valid only within the iterator body. | |
395 ## | |
396 ## **Examples:** | |
397 ## | |
398 ## ```Nim | |
399 ## let db = open("mytest.db", "", "", "") | |
400 ## | |
401 ## # Records of my_table: | |
402 ## # | id | name | | |
403 ## # |----|----------| | |
404 ## # | 1 | item#1 | | |
405 ## # | 2 | item#2 | | |
406 ## | |
407 ## for row in db.instantRows(sql"SELECT * FROM my_table"): | |
408 ## echo "id:" & row[0] | |
409 ## echo "name:" & row[1] | |
410 ## echo "length:" & $len(row) | |
411 ## | |
412 ## # Output: | |
413 ## # id:1 | |
414 ## # name:item#1 | |
415 ## # length:2 | |
416 ## # id:2 | |
417 ## # name:item#2 | |
418 ## # length:2 | |
419 ## | |
420 ## db.close() | |
421 ## ``` | |
422 var stmt = setupQuery(db, query, args) | |
423 try: | |
424 while step(stmt) == SQLITE_ROW: | |
425 yield stmt | |
426 finally: | |
427 if finalize(stmt) != SQLITE_OK: dbError(db) | |
428 | |
429 iterator instantRows*(db: DbConn, stmtName: SqlPrepared): InstantRow | |
430 {.tags: [ReadDbEffect,WriteDbEffect], since: (1, 3).} = | |
431 var stmt = setupQuery(db, stmtName).PStmt | |
432 try: | |
433 while step(stmt) == SQLITE_ROW: | |
434 yield stmt | |
435 except: | |
436 dbError(db) | |
437 | |
438 proc toTypeKind(t: var DbType; x: int32) = | |
439 case x | |
440 of SQLITE_INTEGER: | |
441 t.kind = dbInt | |
442 t.size = 8 | |
443 of SQLITE_FLOAT: | |
444 t.kind = dbFloat | |
445 t.size = 8 | |
446 of SQLITE_BLOB: t.kind = dbBlob | |
447 of SQLITE_NULL: t.kind = dbNull | |
448 of SQLITE_TEXT: t.kind = dbVarchar | |
449 else: t.kind = dbUnknown | |
450 | |
451 proc setColumns(columns: var DbColumns; x: PStmt) = | |
452 let L = column_count(x) | |
453 setLen(columns, L) | |
454 for i in 0'i32 ..< L: | |
455 columns[i].name = $column_name(x, i) | |
456 columns[i].typ.name = $column_decltype(x, i) | |
457 toTypeKind(columns[i].typ, column_type(x, i)) | |
458 columns[i].tableName = $column_table_name(x, i) | |
459 | |
460 iterator instantRows*(db: DbConn; columns: var DbColumns; query: SqlQuery, | |
461 args: varargs[string, `$`]): InstantRow | |
462 {.tags: [ReadDbEffect].} = | |
463 ## Similar to `instantRows iterator <#instantRows.i,DbConn,SqlQuery,varargs[string,]>`_, | |
464 ## but sets information about columns to `columns`. | |
465 ## | |
466 ## **Examples:** | |
467 ## | |
468 ## ```Nim | |
469 ## let db = open("mytest.db", "", "", "") | |
470 ## | |
471 ## # Records of my_table: | |
472 ## # | id | name | | |
473 ## # |----|----------| | |
474 ## # | 1 | item#1 | | |
475 ## # | 2 | item#2 | | |
476 ## | |
477 ## var columns: DbColumns | |
478 ## for row in db.instantRows(columns, sql"SELECT * FROM my_table"): | |
479 ## discard | |
480 ## echo columns[0] | |
481 ## | |
482 ## # Output: | |
483 ## # (name: "id", tableName: "my_table", typ: (kind: dbNull, | |
484 ## # notNull: false, name: "INTEGER", size: 0, maxReprLen: 0, precision: 0, | |
485 ## # scale: 0, min: 0, max: 0, validValues: @[]), primaryKey: false, | |
486 ## # foreignKey: false) | |
487 ## | |
488 ## db.close() | |
489 ## ``` | |
490 var stmt = setupQuery(db, query, args) | |
491 setColumns(columns, stmt) | |
492 try: | |
493 while step(stmt) == SQLITE_ROW: | |
494 yield stmt | |
495 finally: | |
496 if finalize(stmt) != SQLITE_OK: dbError(db) | |
497 | |
498 proc `[]`*(row: InstantRow, col: int32): string {.inline.} = | |
499 ## Returns text for given column of the row. | |
500 ## | |
501 ## See also: | |
502 ## * `instantRows iterator <#instantRows.i,DbConn,SqlQuery,varargs[string,]>`_ | |
503 ## example code | |
504 $column_text(row, col) | |
505 | |
506 proc unsafeColumnAt*(row: InstantRow, index: int32): cstring {.inline.} = | |
507 ## Returns cstring for given column of the row. | |
508 ## | |
509 ## See also: | |
510 ## * `instantRows iterator <#instantRows.i,DbConn,SqlQuery,varargs[string,]>`_ | |
511 ## example code | |
512 column_text(row, index) | |
513 | |
514 proc len*(row: InstantRow): int32 {.inline.} = | |
515 ## Returns number of columns in a row. | |
516 ## | |
517 ## See also: | |
518 ## * `instantRows iterator <#instantRows.i,DbConn,SqlQuery,varargs[string,]>`_ | |
519 ## example code | |
520 column_count(row) | |
521 | |
522 proc getRow*(db: DbConn, query: SqlQuery, | |
523 args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} = | |
524 ## Retrieves a single row. If the query doesn't return any rows, this proc | |
525 ## will return a `Row` with empty strings for each column. | |
526 ## | |
527 ## **Examples:** | |
528 ## | |
529 ## ```Nim | |
530 ## let db = open("mytest.db", "", "", "") | |
531 ## | |
532 ## # Records of my_table: | |
533 ## # | id | name | | |
534 ## # |----|----------| | |
535 ## # | 1 | item#1 | | |
536 ## # | 2 | item#2 | | |
537 ## | |
538 ## doAssert db.getRow(sql"SELECT id, name FROM my_table" | |
539 ## ) == Row(@["1", "item#1"]) | |
540 ## doAssert db.getRow(sql"SELECT id, name FROM my_table WHERE id = ?", | |
541 ## 2) == Row(@["2", "item#2"]) | |
542 ## | |
543 ## # Returns empty. | |
544 ## doAssert db.getRow(sql"INSERT INTO my_table (id, name) VALUES (?, ?)", | |
545 ## 3, "item#3") == @[] | |
546 ## doAssert db.getRow(sql"DELETE FROM my_table WHERE id = ?", 3) == @[] | |
547 ## doAssert db.getRow(sql"UPDATE my_table SET name = 'ITEM#1' WHERE id = ?", | |
548 ## 1) == @[] | |
549 ## db.close() | |
550 ## ``` | |
551 var stmt = setupQuery(db, query, args) | |
552 var L = (column_count(stmt)) | |
553 result = newRow(L) | |
554 if step(stmt) == SQLITE_ROW: | |
555 setRow(stmt, result, L) | |
556 if finalize(stmt) != SQLITE_OK: dbError(db) | |
557 | |
558 proc getAllRows*(db: DbConn, query: SqlQuery, | |
559 args: varargs[string, `$`]): seq[Row] {.tags: [ReadDbEffect].} = | |
560 ## Executes the query and returns the whole result dataset. | |
561 ## | |
562 ## **Examples:** | |
563 ## | |
564 ## ```Nim | |
565 ## let db = open("mytest.db", "", "", "") | |
566 ## | |
567 ## # Records of my_table: | |
568 ## # | id | name | | |
569 ## # |----|----------| | |
570 ## # | 1 | item#1 | | |
571 ## # | 2 | item#2 | | |
572 ## | |
573 ## doAssert db.getAllRows(sql"SELECT id, name FROM my_table") == @[Row(@["1", "item#1"]), Row(@["2", "item#2"])] | |
574 ## db.close() | |
575 ## ``` | |
576 result = @[] | |
577 for r in fastRows(db, query, args): | |
578 result.add(r) | |
579 | |
580 proc getAllRows*(db: DbConn, stmtName: SqlPrepared): seq[Row] | |
581 {.tags: [ReadDbEffect,WriteDbEffect], since: (1, 3).} = | |
582 result = @[] | |
583 for r in fastRows(db, stmtName): | |
584 result.add(r) | |
585 | |
586 iterator rows*(db: DbConn, query: SqlQuery, | |
587 args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} = | |
588 ## Similar to `fastRows iterator <#fastRows.i,DbConn,SqlQuery,varargs[string,]>`_, | |
589 ## but slower and safe. | |
590 ## | |
591 ## **Examples:** | |
592 ## | |
593 ## ```Nim | |
594 ## let db = open("mytest.db", "", "", "") | |
595 ## | |
596 ## # Records of my_table: | |
597 ## # | id | name | | |
598 ## # |----|----------| | |
599 ## # | 1 | item#1 | | |
600 ## # | 2 | item#2 | | |
601 ## | |
602 ## for row in db.rows(sql"SELECT id, name FROM my_table"): | |
603 ## echo row | |
604 ## | |
605 ## ## Output: | |
606 ## ## @["1", "item#1"] | |
607 ## ## @["2", "item#2"] | |
608 ## | |
609 ## db.close() | |
610 ## ``` | |
611 for r in fastRows(db, query, args): yield r | |
612 | |
613 iterator rows*(db: DbConn, stmtName: SqlPrepared): Row | |
614 {.tags: [ReadDbEffect,WriteDbEffect], since: (1, 3).} = | |
615 for r in fastRows(db, stmtName): yield r | |
616 | |
617 proc getValue*(db: DbConn, query: SqlQuery, | |
618 args: varargs[string, `$`]): string {.tags: [ReadDbEffect].} = | |
619 ## Executes the query and returns the first column of the first row of the | |
620 ## result dataset. Returns `""` if the dataset contains no rows or the database | |
621 ## value is `NULL`. | |
622 ## | |
623 ## **Examples:** | |
624 ## | |
625 ## ```Nim | |
626 ## let db = open("mytest.db", "", "", "") | |
627 ## | |
628 ## # Records of my_table: | |
629 ## # | id | name | | |
630 ## # |----|----------| | |
631 ## # | 1 | item#1 | | |
632 ## # | 2 | item#2 | | |
633 ## | |
634 ## doAssert db.getValue(sql"SELECT name FROM my_table WHERE id = ?", | |
635 ## 2) == "item#2" | |
636 ## doAssert db.getValue(sql"SELECT id, name FROM my_table") == "1" | |
637 ## doAssert db.getValue(sql"SELECT name, id FROM my_table") == "item#1" | |
638 ## | |
639 ## db.close() | |
640 ## ``` | |
641 var stmt = setupQuery(db, query, args) | |
642 if step(stmt) == SQLITE_ROW: | |
643 let cb = column_bytes(stmt, 0) | |
644 if cb == 0: | |
645 result = "" | |
646 else: | |
647 if column_type(stmt, 0) == SQLITE_BLOB: | |
648 result.setLen(cb) | |
649 copyMem(addr(result[0]), column_blob(stmt, 0), cb) | |
650 else: | |
651 result = newStringOfCap(cb) | |
652 add(result, column_text(stmt, 0)) | |
653 else: | |
654 result = "" | |
655 if finalize(stmt) != SQLITE_OK: dbError(db) | |
656 | |
657 proc getValue*(db: DbConn, stmtName: SqlPrepared): string | |
658 {.tags: [ReadDbEffect,WriteDbEffect], since: (1, 3).} = | |
659 var stmt = setupQuery(db, stmtName).PStmt | |
660 if step(stmt) == SQLITE_ROW: | |
661 let cb = column_bytes(stmt, 0) | |
662 if cb == 0: | |
663 result = "" | |
664 else: | |
665 if column_type(stmt, 0) == SQLITE_BLOB: | |
666 result.setLen(cb) | |
667 copyMem(addr(result[0]), column_blob(stmt, 0), cb) | |
668 else: | |
669 result = newStringOfCap(cb) | |
670 add(result, column_text(stmt, 0)) | |
671 else: | |
672 result = "" | |
673 | |
674 proc tryInsertID*(db: DbConn, query: SqlQuery, | |
675 args: varargs[string, `$`]): int64 | |
676 {.tags: [WriteDbEffect], raises: [DbError].} = | |
677 ## Executes the query (typically "INSERT") and returns the | |
678 ## generated ID for the row or -1 in case of an error. | |
679 ## | |
680 ## **Examples:** | |
681 ## | |
682 ## ```Nim | |
683 ## let db = open("mytest.db", "", "", "") | |
684 ## db.exec(sql"CREATE TABLE my_table (id INTEGER, name VARCHAR(50) NOT NULL)") | |
685 ## | |
686 ## doAssert db.tryInsertID(sql"INSERT INTO not_exist_table (id, name) VALUES (?, ?)", | |
687 ## 1, "item#1") == -1 | |
688 ## db.close() | |
689 ## ``` | |
690 assert(not db.isNil, "Database not connected.") | |
691 var q = dbFormat(query, args) | |
692 var stmt: sqlite3.PStmt | |
693 result = -1 | |
694 if prepare_v2(db, q.cstring, q.len.cint, stmt, nil) == SQLITE_OK: | |
695 if step(stmt) == SQLITE_DONE: | |
696 result = last_insert_rowid(db) | |
697 if finalize(stmt) != SQLITE_OK: | |
698 result = -1 | |
699 else: | |
700 discard finalize(stmt) | |
701 | |
702 proc insertID*(db: DbConn, query: SqlQuery, | |
703 args: varargs[string, `$`]): int64 {.tags: [WriteDbEffect].} = | |
704 ## Executes the query (typically "INSERT") and returns the | |
705 ## generated ID for the row. | |
706 ## | |
707 ## Raises a `DbError` exception when failed to insert row. | |
708 ## For Postgre this adds `RETURNING id` to the query, so it only works | |
709 ## if your primary key is named `id`. | |
710 ## | |
711 ## **Examples:** | |
712 ## | |
713 ## ```Nim | |
714 ## let db = open("mytest.db", "", "", "") | |
715 ## db.exec(sql"CREATE TABLE my_table (id INTEGER, name VARCHAR(50) NOT NULL)") | |
716 ## | |
717 ## for i in 0..2: | |
718 ## let id = db.insertID(sql"INSERT INTO my_table (id, name) VALUES (?, ?)", i, "item#" & $i) | |
719 ## echo "LoopIndex = ", i, ", InsertID = ", id | |
720 ## | |
721 ## # Output: | |
722 ## # LoopIndex = 0, InsertID = 1 | |
723 ## # LoopIndex = 1, InsertID = 2 | |
724 ## # LoopIndex = 2, InsertID = 3 | |
725 ## | |
726 ## db.close() | |
727 ## ``` | |
728 result = tryInsertID(db, query, args) | |
729 if result < 0: dbError(db) | |
730 | |
731 proc tryInsert*(db: DbConn, query: SqlQuery, pkName: string, | |
732 args: varargs[string, `$`]): int64 | |
733 {.tags: [WriteDbEffect], raises: [DbError], since: (1, 3).} = | |
734 ## same as tryInsertID | |
735 tryInsertID(db, query, args) | |
736 | |
737 proc insert*(db: DbConn, query: SqlQuery, pkName: string, | |
738 args: varargs[string, `$`]): int64 | |
739 {.tags: [WriteDbEffect], since: (1, 3).} = | |
740 ## same as insertId | |
741 result = tryInsert(db, query, pkName, args) | |
742 if result < 0: dbError(db) | |
743 | |
744 proc execAffectedRows*(db: DbConn, query: SqlQuery, | |
745 args: varargs[string, `$`]): int64 {. | |
746 tags: [ReadDbEffect, WriteDbEffect].} = | |
747 ## Executes the query (typically "UPDATE") and returns the | |
748 ## number of affected rows. | |
749 ## | |
750 ## **Examples:** | |
751 ## | |
752 ## ```Nim | |
753 ## let db = open("mytest.db", "", "", "") | |
754 ## | |
755 ## # Records of my_table: | |
756 ## # | id | name | | |
757 ## # |----|----------| | |
758 ## # | 1 | item#1 | | |
759 ## # | 2 | item#2 | | |
760 ## | |
761 ## doAssert db.execAffectedRows(sql"UPDATE my_table SET name = 'TEST'") == 2 | |
762 ## | |
763 ## db.close() | |
764 ## ``` | |
765 exec(db, query, args) | |
766 result = changes(db) | |
767 | |
768 proc execAffectedRows*(db: DbConn, stmtName: SqlPrepared): int64 | |
769 {.tags: [ReadDbEffect, WriteDbEffect],since: (1, 3).} = | |
770 exec(db, stmtName) | |
771 result = changes(db) | |
772 | |
773 proc close*(db: DbConn) {.tags: [DbEffect].} = | |
774 ## Closes the database connection. | |
775 ## | |
776 ## **Examples:** | |
777 ## ```Nim | |
778 ## let db = open("mytest.db", "", "", "") | |
779 ## db.close() | |
780 ## ``` | |
781 if sqlite3.close(db) != SQLITE_OK: dbError(db) | |
782 | |
783 proc open*(connection, user, password, database: string): DbConn {. | |
784 tags: [DbEffect].} = | |
785 ## Opens a database connection. Raises a `DbError` exception if the connection | |
786 ## could not be established. | |
787 ## | |
788 ## **Note:** Only the `connection` parameter is used for `sqlite`. | |
789 ## | |
790 ## **Examples:** | |
791 ## ```Nim | |
792 ## try: | |
793 ## let db = open("mytest.db", "", "", "") | |
794 ## ## do something... | |
795 ## ## db.getAllRows(sql"SELECT * FROM my_table") | |
796 ## db.close() | |
797 ## except: | |
798 ## stderr.writeLine(getCurrentExceptionMsg()) | |
799 ## ``` | |
800 var db: DbConn | |
801 if sqlite3.open(connection, db) == SQLITE_OK: | |
802 result = db | |
803 else: | |
804 dbError(db) | |
805 | |
806 proc setEncoding*(connection: DbConn, encoding: string): bool {. | |
807 tags: [DbEffect].} = | |
808 ## Sets the encoding of a database connection, returns `true` for | |
809 ## success, `false` for failure. | |
810 ## | |
811 ## **Note:** The encoding cannot be changed once it's been set. | |
812 ## According to SQLite3 documentation, any attempt to change | |
813 ## the encoding after the database is created will be silently | |
814 ## ignored. | |
815 exec(connection, sql"PRAGMA encoding = ?", [encoding]) | |
816 result = connection.getValue(sql"PRAGMA encoding") == encoding | |
817 | |
818 proc finalize*(sqlPrepared:SqlPrepared) {.discardable, since: (1, 3).} = | |
819 discard finalize(sqlPrepared.PStmt) | |
820 | |
821 template dbBindParamError*(paramIdx: int, val: varargs[untyped]) = | |
822 ## Raises a `DbError` exception. | |
823 var e: ref DbError | |
824 new(e) | |
825 e.msg = "error binding param in position " & $paramIdx | |
826 raise e | |
827 | |
828 proc bindParam*(ps: SqlPrepared, paramIdx: int, val: int32) {.since: (1, 3).} = | |
829 ## Binds a int32 to the specified paramIndex. | |
830 if bind_int(ps.PStmt, paramIdx.int32, val) != SQLITE_OK: | |
831 dbBindParamError(paramIdx, val) | |
832 | |
833 proc bindParam*(ps: SqlPrepared, paramIdx: int, val: int64) {.since: (1, 3).} = | |
834 ## Binds a int64 to the specified paramIndex. | |
835 if bind_int64(ps.PStmt, paramIdx.int32, val) != SQLITE_OK: | |
836 dbBindParamError(paramIdx, val) | |
837 | |
838 proc bindParam*(ps: SqlPrepared, paramIdx: int, val: int) {.since: (1, 3).} = | |
839 ## Binds a int to the specified paramIndex. | |
840 when sizeof(int) == 8: | |
841 bindParam(ps, paramIdx, val.int64) | |
842 else: | |
843 bindParam(ps, paramIdx, val.int32) | |
844 | |
845 proc bindParam*(ps: SqlPrepared, paramIdx: int, val: float64) {.since: (1, 3).} = | |
846 ## Binds a 64bit float to the specified paramIndex. | |
847 if bind_double(ps.PStmt, paramIdx.int32, val) != SQLITE_OK: | |
848 dbBindParamError(paramIdx, val) | |
849 | |
850 proc bindNull*(ps: SqlPrepared, paramIdx: int) {.since: (1, 3).} = | |
851 ## Sets the bindparam at the specified paramIndex to null | |
852 ## (default behaviour by sqlite). | |
853 if bind_null(ps.PStmt, paramIdx.int32) != SQLITE_OK: | |
854 dbBindParamError(paramIdx) | |
855 | |
856 proc bindParam*(ps: SqlPrepared, paramIdx: int, val: string, copy = true) {.since: (1, 3).} = | |
857 ## Binds a string to the specified paramIndex. | |
858 ## if copy is true then SQLite makes its own private copy of the data immediately | |
859 if bind_text(ps.PStmt, paramIdx.int32, val.cstring, val.len.int32, if copy: SQLITE_TRANSIENT else: SQLITE_STATIC) != SQLITE_OK: | |
860 dbBindParamError(paramIdx, val) | |
861 | |
862 proc bindParam*(ps: SqlPrepared, paramIdx: int,val: openArray[byte], copy = true) {.since: (1, 3).} = | |
863 ## binds a blob to the specified paramIndex. | |
864 ## if copy is true then SQLite makes its own private copy of the data immediately | |
865 let len = val.len | |
866 if bind_blob(ps.PStmt, paramIdx.int32, val[0].unsafeAddr, len.int32, if copy: SQLITE_TRANSIENT else: SQLITE_STATIC) != SQLITE_OK: | |
867 dbBindParamError(paramIdx, val) | |
868 | |
869 when not defined(testing) and isMainModule: | |
870 var db = open(":memory:", "", "", "") | |
871 exec(db, sql"create table tbl1(one varchar(10), two smallint)", []) | |
872 exec(db, sql"insert into tbl1 values('hello!',10)", []) | |
873 exec(db, sql"insert into tbl1 values('goodbye', 20)", []) | |
874 var p1 = db.prepare "create table tbl2(one varchar(10), two smallint)" | |
875 exec(db, p1) | |
876 finalize(p1) | |
877 var p2 = db.prepare "insert into tbl2 values('hello!',10)" | |
878 exec(db, p2) | |
879 finalize(p2) | |
880 var p3 = db.prepare "insert into tbl2 values('goodbye', 20)" | |
881 exec(db, p3) | |
882 finalize(p3) | |
883 #db.query("create table tbl1(one varchar(10), two smallint)") | |
884 #db.query("insert into tbl1 values('hello!',10)") | |
885 #db.query("insert into tbl1 values('goodbye', 20)") | |
886 for r in db.rows(sql"select * from tbl1", []): | |
887 echo(r[0], r[1]) | |
888 for r in db.instantRows(sql"select * from tbl1", []): | |
889 echo(r[0], r[1]) | |
890 var p4 = db.prepare "select * from tbl2" | |
891 for r in db.rows(p4): | |
892 echo(r[0], r[1]) | |
893 finalize(p4) | |
894 var i5 = 0 | |
895 var p5 = db.prepare "select * from tbl2" | |
896 for r in db.instantRows(p5): | |
897 inc i5 | |
898 echo(r[0], r[1]) | |
899 assert i5 == 2 | |
900 finalize(p5) | |
901 | |
902 for r in db.rows(sql"select * from tbl2", []): | |
903 echo(r[0], r[1]) | |
904 for r in db.instantRows(sql"select * from tbl2", []): | |
905 echo(r[0], r[1]) | |
906 var p6 = db.prepare "select * from tbl2 where one = ? " | |
907 p6.bindParams("goodbye") | |
908 var rowsP3 = 0 | |
909 for r in db.rows(p6): | |
910 rowsP3 = 1 | |
911 echo(r[0], r[1]) | |
912 assert rowsP3 == 1 | |
913 finalize(p6) | |
914 | |
915 var p7 = db.prepare "select * from tbl2 where two=?" | |
916 p7.bindParams(20'i32) | |
917 when sizeof(int) == 4: | |
918 p7.bindParams(20) | |
919 var rowsP = 0 | |
920 for r in db.rows(p7): | |
921 rowsP = 1 | |
922 echo(r[0], r[1]) | |
923 assert rowsP == 1 | |
924 finalize(p7) | |
925 | |
926 exec(db, sql"CREATE TABLE photos(ID INTEGER PRIMARY KEY AUTOINCREMENT, photo BLOB)") | |
927 var p8 = db.prepare "INSERT INTO photos (ID,PHOTO) VALUES (?,?)" | |
928 var d = "abcdefghijklmnopqrstuvwxyz" | |
929 p8.bindParams(1'i32, "abcdefghijklmnopqrstuvwxyz") | |
930 exec(db, p8) | |
931 finalize(p8) | |
932 var p10 = db.prepare "INSERT INTO photos (ID,PHOTO) VALUES (?,?)" | |
933 p10.bindParams(2'i32,nil) | |
934 exec(db, p10) | |
935 exec( db, p10, 3, nil) | |
936 finalize(p10) | |
937 for r in db.rows(sql"select * from photos where ID = 1", []): | |
938 assert r[1].len == d.len | |
939 assert r[1] == d | |
940 var i6 = 0 | |
941 for r in db.rows(sql"select * from photos where ID = 3", []): | |
942 i6 = 1 | |
943 assert i6 == 1 | |
944 var p9 = db.prepare("select * from photos where PHOTO is ?") | |
945 p9.bindParams(nil) | |
946 var rowsP2 = 0 | |
947 for r in db.rows(p9): | |
948 rowsP2 = 1 | |
949 echo(r[0], repr r[1]) | |
950 assert rowsP2 == 1 | |
951 finalize(p9) | |
952 | |
953 db_sqlite.close(db) |