sqlite.bmx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870
  1. ' Copyright (c) 2007-2022 Bruce A Henderson
  2. ' All rights reserved.
  3. '
  4. ' Redistribution and use in source and binary forms, with or without
  5. ' modification, are permitted provided that the following conditions are met:
  6. ' * Redistributions of source code must retain the above copyright
  7. ' notice, this list of conditions and the following disclaimer.
  8. ' * Redistributions in binary form must reproduce the above copyright
  9. ' notice, this list of conditions and the following disclaimer in the
  10. ' documentation and/or other materials provided with the distribution.
  11. ' * Neither the auther nor the names of its contributors may be used to
  12. ' endorse or promote products derived from this software without specific
  13. ' prior written permission.
  14. '
  15. ' THIS SOFTWARE IS PROVIDED BY Bruce A Henderson ``AS IS'' AND ANY
  16. ' EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  17. ' WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. ' DISCLAIMED. IN NO EVENT SHALL Bruce A Henderson BE LIABLE FOR ANY
  19. ' DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  20. ' (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  21. ' LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  22. ' ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  23. ' (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  24. ' SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. '
  26. SuperStrict
  27. Rem
  28. bbdoc: Database Driver - SQLite
  29. about: An SQLite database driver for #Database.Core
  30. End Rem
  31. Module Database.SQLite
  32. ModuleInfo "Version: 1.20"
  33. ModuleInfo "Author: Bruce A Henderson"
  34. ModuleInfo "License: BSD"
  35. ModuleInfo "Copyright: Wrapper - 2007-2022 Bruce A Henderson"
  36. ModuleInfo "Copyright: SQLite - The original author of SQLite has dedicated the code to the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute the original SQLite code, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means."
  37. ModuleInfo "Modserver: BRL"
  38. ModuleInfo "History: 1.20"
  39. ModuleInfo "History: Update to SQLite 3.38.5."
  40. ModuleInfo "History: 1.19"
  41. ModuleInfo "History: Update to SQLite 3.37.2."
  42. ModuleInfo "History: 1.18"
  43. ModuleInfo "History: Update to SQLite 3.29.0."
  44. ModuleInfo "History: Disable double-quoted string literals by default."
  45. ModuleInfo "History: 1.17"
  46. ModuleInfo "History: Update to SQLite 3.28.0."
  47. ModuleInfo "History: 1.16"
  48. ModuleInfo "History: Update to SQLite 3.27.2."
  49. ModuleInfo "History: Fixed query free issue."
  50. ModuleInfo "History: 1.15"
  51. ModuleInfo "History: Update to SQLite 3.22.0."
  52. ModuleInfo "History: Fixed for 64-bit targets."
  53. ModuleInfo "History: 1.14"
  54. ModuleInfo "History: Update to SQLite 3.8.11.1."
  55. ModuleInfo "History: Added user authentication support."
  56. ModuleInfo "History: 1.13"
  57. ModuleInfo "History: Update to SQLite 3.8.2."
  58. ModuleInfo "History: 1.12"
  59. ModuleInfo "History: Update to SQLite 3.7.15."
  60. ModuleInfo "History: Updated documentation."
  61. ModuleInfo "History: Added loadOrSaveDB() function."
  62. ModuleInfo "History: 1.11"
  63. ModuleInfo "History: Update to SQLite 3.6.15."
  64. ModuleInfo "History: Fixed prepared statement reuse issue."
  65. ModuleInfo "History: Fixed problem where open/live queries could cause problem when committing."
  66. ModuleInfo "History: Added getTableInfo() support."
  67. ModuleInfo "History: Added blob support."
  68. ModuleInfo "History: 1.10"
  69. ModuleInfo "History: Update to SQLite 3.5.6."
  70. ModuleInfo "History: Fixed lack of error reporting during query execution."
  71. ModuleInfo "History: Transaction queries are finalized more quickly."
  72. ModuleInfo "History: Statement should generally be reset before acquiring error message."
  73. ModuleInfo "History: 1.09"
  74. ModuleInfo "History: Update to SQLite 3.5.2. Now using the Amalgamated version."
  75. ModuleInfo "History: Implementation of Date, DateTime and Time types."
  76. ModuleInfo "History: 1.08"
  77. ModuleInfo "History: Fixed null column types not being handled."
  78. ModuleInfo "History: 1.07"
  79. ModuleInfo "History: Fixed problem with lastInsertedId() not returning.. the last inserted id."
  80. ModuleInfo "History: 1.06"
  81. ModuleInfo "History: Update to SQLite 3.4.2."
  82. ModuleInfo "History: 1.05"
  83. ModuleInfo "History: Fixed database Close to cleanup non-finalized queries."
  84. ModuleInfo "History: 1.04"
  85. ModuleInfo "History: Improved error message details."
  86. ModuleInfo "History: 1.03"
  87. ModuleInfo "History: Fixed NextRow returning True on empty queries."
  88. ModuleInfo "History: 1.02"
  89. ModuleInfo "History: Fixed issue with mis-count of bound parameters."
  90. ModuleInfo "History: 1.01"
  91. ModuleInfo "History: Added hasPrepareSupport() and hasTransactionSupport() methods."
  92. ModuleInfo "History: 1.00 Initial Release"
  93. ModuleInfo "History: Includes SQLite 3.3.13 source."
  94. ModuleInfo "CC_OPTS: -DSQLITE_USER_AUTHENTICATION"
  95. ModuleInfo "CC_OPTS: -DSQLITE_DQS=0" ' deactivates the double-quoted string literal "misfeature". see https://www.sqlite.org/quirks.html#dblquote
  96. Import Database.Core
  97. Import "common.bmx"
  98. ' Notes
  99. '
  100. ' Appended userauth.c to end of sqlite3.c
  101. '
  102. ' The implementation
  103. Type TDBSQLite Extends TDBConnection
  104. Field queries:TSQLiteResultSet[2]
  105. Function Create:TDBConnection(dbname:String = Null, host:String = Null, ..
  106. port:Int = Null, user:String = Null, password:String = Null, ..
  107. server:String = Null, options:String = Null)
  108. Local this:TDBSQLite = New TDBSQLite
  109. this.init(dbname, host, port, user, password, server, options)
  110. If this._dbname Then
  111. this.open(user, password)
  112. End If
  113. Return this
  114. End Function
  115. Method close()
  116. clearQueries()
  117. If _isOpen Then
  118. If sqlite3_close(handle) <> SQLITE_OK Then
  119. setError("Error closing database", Null, TDatabaseError.ERROR_CONNECTION)
  120. End If
  121. handle = Null
  122. _isOpen = False
  123. End If
  124. End Method
  125. Method commit:Int()
  126. If Not _isOpen Then
  127. Return False
  128. End If
  129. ' we need to ensure our queries are in a state that can be committed.
  130. resetQueries()
  131. Local query:TDatabaseQuery = executeQuery("COMMIT TRANSACTION")
  132. If hasError() Then
  133. setError("Error committing transaction", error().error, TDatabaseError.ERROR_TRANSACTION)
  134. Return False
  135. End If
  136. query.Free()
  137. Return True
  138. End Method
  139. Method getTables:String[]()
  140. Local list:String[]
  141. If Not _isOpen Then
  142. Return list
  143. End If
  144. Local tables:TList = New TList
  145. Local query:TDatabaseQuery = TDatabaseQuery.Create(Self)
  146. Local sql:String = "SELECT name FROM sqlite_master WHERE type = 'table' " + ..
  147. "UNION ALL SELECT name FROM sqlite_temp_master WHERE type = 'table'"
  148. If query.execute(sql) Then
  149. While query.nextRow()
  150. tables.addLast(query.value(0).getString())
  151. Wend
  152. End If
  153. If tables.count() > 0 Then
  154. list = New String[tables.count()]
  155. Local i:Int = 0
  156. For Local s:String = EachIn tables
  157. list[i] = s
  158. i:+ 1
  159. Next
  160. End If
  161. Return list
  162. End Method
  163. Method getTableInfo:TDBTable(tableName:String, withDDL:Int = False)
  164. If Not _isOpen Then
  165. Return Null
  166. End If
  167. Local query:TDatabaseQuery = TDatabaseQuery.Create(Self)
  168. Local table:TDBTable
  169. Local sql:String = "PRAGMA table_info(" + tableName + ")"
  170. If query.execute(sql) Then
  171. table = New TDBTable
  172. table.name = tableName
  173. Local cols:TList = New TList
  174. For Local rec:TQueryRecord = EachIn query
  175. Local name:String = rec.GetString(1)
  176. Local dbType:Int = TSQLiteResultSet.dbTypeFromNative(rec.GetString(2))
  177. Local nullable:Int = rec.GetInt(3)
  178. Local defaultValue:TDBType = rec.value(4)
  179. cols.AddLast(TDBColumn.Create(name, dbType, nullable, defaultValue))
  180. Next
  181. table.SetCountColumns(cols.count())
  182. Local i:Int
  183. For Local col:TDBColumn = EachIn cols
  184. table.SetColumn(i, col)
  185. i:+ 1
  186. Next
  187. cols.Clear()
  188. If withDDL Then
  189. sql = "SELECT sql FROM sqlite_master WHERE Type = 'table' and name = '" + tableName + "'"
  190. If query.execute(sql) Then
  191. For Local rec:TQueryRecord = EachIn query
  192. table.ddl:+ rec.GetString(0) + ";~n~n"
  193. Next
  194. End If
  195. End If
  196. Else
  197. ' no table?
  198. End If
  199. Return table
  200. End Method
  201. Method open:Int(user:String = Null, pass:String = Null)
  202. ' close if the connection is already open
  203. If _isOpen Then
  204. close()
  205. End If
  206. Local s:Byte Ptr = _dbname.ToUTF8String()
  207. Local flags:Int = _options.ToInt()
  208. If Not flags Then
  209. flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
  210. End If
  211. Local ret:Int = sqlite3_open_v2(s, Varptr handle, flags, Null)
  212. MemFree(s)
  213. If ret = SQLITE_OK Then
  214. _isOpen = True
  215. ' authenticate
  216. If _user Then
  217. Local u:Byte Ptr = _user.ToUTF8String()
  218. Local p:Byte Ptr = _password.ToUTF8String()
  219. ret = sqlite3_user_authenticate(handle, u, p, _strlen(p))
  220. MemFree(p)
  221. MemFree(u)
  222. If ret <> SQLITE_OK Then
  223. setError("Error authenticating user", Null, TDatabaseError.ERROR_CONNECTION, ret)
  224. End If
  225. End If
  226. Return True
  227. Else
  228. setError("Error opening database", Null, TDatabaseError.ERROR_CONNECTION, ret)
  229. End If
  230. Return False
  231. End Method
  232. Method rollback:Int()
  233. If Not _isOpen Then
  234. Return False
  235. End If
  236. ' we need to ensure our queries are in a state that can be rolledback.
  237. resetQueries()
  238. Local query:TDatabaseQuery = executeQuery("ROLLBACK TRANSACTION")
  239. If hasError() Then
  240. setError("Error rolling back transaction", error().error, TDatabaseError.ERROR_TRANSACTION)
  241. Return False
  242. End If
  243. query.Free()
  244. Return True
  245. End Method
  246. Method startTransaction:Int()
  247. If Not _isOpen Then
  248. Return False
  249. End If
  250. Local query:TDatabaseQuery = executeQuery("BEGIN TRANSACTION")
  251. If hasError() Then
  252. setError("Error starting transaction", error().error, TDatabaseError.ERROR_TRANSACTION)
  253. Return False
  254. End If
  255. query.Free()
  256. Return True
  257. End Method
  258. Method databaseHandle:Byte Ptr()
  259. Return handle
  260. End Method
  261. Method createResultSet:TQueryResultSet()
  262. Return TSQLiteResultSet.Create(Self)
  263. End Method
  264. Method nativeErrorMessage:String(err:Int)
  265. Select err
  266. Case 0 Return "Successful result"
  267. Case 1 Return "SQL error Or missing database"
  268. Case 2 Return "An internal logic error in SQLite"
  269. Case 3 Return "Access permission denied"
  270. Case 4 Return "Callback routine requested an abort"
  271. Case 5 Return "The database file is locked"
  272. Case 6 Return "A table in the database is locked"
  273. Case 7 Return "A malloc() failed"
  274. Case 8 Return "Attempt To write a readonly database"
  275. Case 9 Return "Operation terminated by sqlite_interrupt()"
  276. Case 10 Return "Some kind of disk I/O error occurred"
  277. Case 11 Return "The database disk image is malformed"
  278. Case 12 Return "(Internal Only) Table Or record Not found"
  279. Case 13 Return "Insertion failed because database is full"
  280. Case 14 Return "Unable To open the database file"
  281. Case 15 Return "Database lock protocol error"
  282. Case 16 Return "(Internal Only) Database table is empty"
  283. Case 17 Return "The database schema changed"
  284. Case 18 Return "Too much data For one row of a table"
  285. Case 19 Return "Abort due To constraint violation"
  286. Case 20 Return "Data Type mismatch"
  287. Case 21 Return "Library used incorrectly"
  288. Case 22 Return "Uses OS features Not supported on host"
  289. Case 23 Return "Authorization denied"
  290. Case 25 Return "2nd parameter to sqlite3_bind out of range"
  291. Case 26 Return "File opened that is not a database file"
  292. Case 27 Return "Notifications from sqlite3_log()"
  293. Case 28 Return "Warnings from sqlite3_log()"
  294. Case 100 Return "sqlite_step() has another row ready"
  295. Case 101 Return "sqlite_step() has finished executing"
  296. Default Return "Error " + err
  297. End Select
  298. End Method
  299. Method hasPrepareSupport:Int()
  300. Return True
  301. End Method
  302. Method hasTransactionSupport:Int()
  303. Return True
  304. End Method
  305. Method clearQueries()
  306. For Local i:Int = 0 Until queries.length
  307. Local q:TSQLiteResultSet = queries[i]
  308. If q Then
  309. q.free()
  310. End If
  311. Next
  312. End Method
  313. Method resetQueries()
  314. For Local q:TSQLiteResultSet = EachIn queries
  315. If q Then
  316. q.reset()
  317. End If
  318. Next
  319. End Method
  320. Method addQuery(query:TSQLiteResultSet)
  321. Local firstFree:Int = -1
  322. For Local i:Int = 0 Until queries.length
  323. Local q:TSQLiteResultSet = queries[i]
  324. If Not q And firstFree < 0 Then
  325. firstFree = i
  326. Else If queries[i] = query Then
  327. Return
  328. End If
  329. Next
  330. If firstFree >= 0 Then
  331. queries[firstFree] = query
  332. Else
  333. queries :+ [query]
  334. End If
  335. End Method
  336. Method removeQuery(query:TSQLiteResultSet)
  337. For Local i:Int = 0 Until queries.length
  338. If queries[i] = query Then
  339. queries[i] = Null
  340. Exit
  341. End If
  342. Next
  343. End Method
  344. Method addUser(username:String, password:String, isAdmin:Int = False)
  345. If username Then
  346. Local n:Byte Ptr = username.ToUTF8String()
  347. Local p:Byte Ptr
  348. Local plen:Int
  349. If password Then
  350. p = password.ToUTF8String()
  351. plen = _strlen(p)
  352. End If
  353. Local res:Int = sqlite3_user_add(handle, n, p, plen, isAdmin)
  354. If p Then
  355. MemFree(p)
  356. End If
  357. MemFree(n)
  358. If res <> SQLITE_OK Then
  359. setError("Error adding user", Null, TDatabaseError.ERROR_CONNECTION, res)
  360. End If
  361. End If
  362. End Method
  363. Method modifyUser(username:String, password:String, isAdmin:Int = False)
  364. If username Then
  365. Local n:Byte Ptr = username.ToUTF8String()
  366. Local p:Byte Ptr
  367. Local plen:Int
  368. If password Then
  369. p = password.ToUTF8String()
  370. plen = _strlen(p)
  371. End If
  372. Local res:Int = sqlite3_user_change(handle, n, p, plen, isAdmin)
  373. If p Then
  374. MemFree(p)
  375. End If
  376. MemFree(n)
  377. If res <> SQLITE_OK Then
  378. setError("Error changing user", Null, TDatabaseError.ERROR_CONNECTION, res)
  379. End If
  380. End If
  381. End Method
  382. Method deleteUser(username:String)
  383. If username Then
  384. Local n:Byte Ptr = username.ToUTF8String()
  385. Local res:Int = sqlite3_user_delete(handle, n)
  386. MemFree(n)
  387. If res <> SQLITE_OK Then
  388. setError("Error deleting user", Null, TDatabaseError.ERROR_CONNECTION, res)
  389. End If
  390. EndIf
  391. End Method
  392. End Type
  393. Type TSQLiteResultSet Extends TQueryResultSet
  394. Field initialFetch:Int = True
  395. Field fakeFirstRowFetch:Int
  396. Method free()
  397. TDBSQLite(conn).removeQuery(Self)
  398. If stmtHandle Then
  399. sqlite3_finalize(stmtHandle)
  400. stmtHandle = Null
  401. End If
  402. End Method
  403. Method Delete()
  404. free()
  405. End Method
  406. Method reset()
  407. initialFetch = True
  408. index = SQL_BeforeFirstRow
  409. If stmtHandle Then
  410. sqlite3_reset(stmtHandle)
  411. End If
  412. End Method
  413. Function Create:TQueryResultSet(db:TDBConnection, sql:String = Null)
  414. Local this:TSQLiteResultSet = New TSQLiteResultSet
  415. this.init(db, sql)
  416. this.rec = TQueryRecord.Create()
  417. TDBSQLite(this.conn).addQuery(this)
  418. Return this
  419. End Function
  420. Method executeQuery:Int(statement:String)
  421. If Not prepare(statement) Then
  422. Return False
  423. End If
  424. Return execute()
  425. End Method
  426. Method cleanup()
  427. index = SQL_beforeFirstRow
  428. initialFetch = True
  429. clear()
  430. free()
  431. End Method
  432. Method prepare:Int(stmt:String)
  433. If Not conn Or Not conn.isOpen() Then
  434. Return False
  435. End If
  436. cleanup()
  437. TDBSQLite(conn).addQuery(Self)
  438. ' set the query if not set already
  439. If Not query Then
  440. query = stmt
  441. End If
  442. Local q:Byte Ptr = query.ToUTF8String()
  443. Local result:Int = sqlite3_prepare_v2(TDBSQLite(conn).handle, q, _strlen(q) , Varptr stmtHandle, 0)
  444. MemFree(q)
  445. If result <> SQLITE_OK Then
  446. conn.setError("Error preparing statement", String.FromUTF8String(sqlite3_errmsg(TDBSQLite(conn).handle)), TDatabaseError.ERROR_STATEMENT, result)
  447. free()
  448. Return False
  449. End If
  450. Return True
  451. End Method
  452. Method execute:Int()
  453. fakeFirstRowFetch = False
  454. Local result:Int = sqlite3_reset(stmtHandle)
  455. If result <> SQLITE_OK Then
  456. conn.setError("Error resetting statement", String.FromUTF8String(sqlite3_errmsg(TDBSQLite(conn).handle)), TDatabaseError.ERROR_STATEMENT, result)
  457. free()
  458. Return False
  459. End If
  460. ' BIND stuff
  461. Local values:TDBType[] = boundValues
  462. Local paramCount:Int = sqlite3_bind_parameter_count(stmtHandle)
  463. If paramCount = bindCount Then
  464. For Local i:Int = 0 Until paramCount
  465. ' reset error-state flag
  466. result = SQLITE_OK
  467. If Not values[i] Or values[i].isNull() Then
  468. result = sqlite3_bind_null(stmtHandle, i + 1)
  469. Else
  470. Select values[i].kind()
  471. Case DBTYPE_INT
  472. result = sqlite3_bind_int(stmtHandle, i + 1, values[i].getInt())
  473. Case DBTYPE_FLOAT
  474. result = sqlite3_bind_double(stmtHandle, i + 1, values[i].getFloat())
  475. Case DBTYPE_DOUBLE
  476. result = sqlite3_bind_double(stmtHandle, i + 1, values[i].getDouble())
  477. Case DBTYPE_LONG
  478. result = sqlite3_bind_int64(stmtHandle, i + 1, values[i].getLong())
  479. Case DBTYPE_STRING
  480. Local s:Byte Ptr = values[i].getString().ToUTF8String()
  481. result = bmx_sqlite3_bind_text64(stmtHandle, i + 1, s, _strlen(s), -1)
  482. MemFree(s)
  483. Case DBTYPE_BLOB
  484. result = bmx_sqlite3_bind_blob64(stmtHandle, i + 1, values[i].getBlob(), values[i].size(), 0)
  485. Case DBTYPE_DATE, DBTYPE_DATETIME, DBTYPE_TIME
  486. Local s:Byte Ptr = values[i].getString().ToUTF8String()
  487. result = bmx_sqlite3_bind_text64(stmtHandle, i + 1, s, _strlen(s), -1)
  488. MemFree(s)
  489. End Select
  490. End If
  491. If result <> SQLITE_OK Then
  492. sqlite3_reset(stmtHandle)
  493. conn.setError("Failed to bind parameter (" + i + ")", String.FromUTF8String(sqlite3_errmsg(TDBSQLite(conn).handle)), TDatabaseError.ERROR_STATEMENT, result)
  494. free()
  495. Return False
  496. End If
  497. Next
  498. Else
  499. conn.setError("Parameter count mismatch", Null, TDatabaseError.ERROR_STATEMENT, 0)
  500. free()
  501. Return False
  502. End If
  503. ' we need to pre-fetch the first row to get the field information
  504. nextRow()
  505. If conn.error().isSet() Then
  506. _isActive = False
  507. Return False
  508. End If
  509. _isActive = True
  510. Return True
  511. End Method
  512. Method firstRow:Int()
  513. If index = SQL_BeforeFirstRow Then
  514. Return nextRow()
  515. End If
  516. Return False
  517. End Method
  518. Method nextRow:Int()
  519. If fakeFirstRowFetch Then
  520. fakeFirstRowFetch = False
  521. Return True
  522. End If
  523. If initialFetch Then
  524. fakeFirstRowFetch = True
  525. initialFetch = False
  526. End If
  527. Local result:Int = sqlite3_step(stmtHandle)
  528. Select result
  529. Case SQLITE_ROW
  530. If rec.isEmpty() Then
  531. initRecord()
  532. resetValues(rec.count())
  533. End If
  534. For Local i:Int = 0 Until rec.count()
  535. If values[i] Then
  536. values[i].clear()
  537. End If
  538. Select sqlite3_column_type(stmtHandle, i)
  539. Case SQLITE_INTEGER
  540. values[i] = New TDBLong
  541. Local lvalue:Long
  542. bmx_sqlite3_column_int64(stmtHandle, i, Varptr lvalue)
  543. values[i].setLong(lvalue)
  544. Case SQLITE_FLOAT
  545. values[i] = New TDBDouble
  546. values[i].setDouble(sqlite3_column_double(stmtHandle, i))
  547. Case SQLITE_NULL
  548. Case SQLITE_BLOB
  549. values[i] = New TDBBlob
  550. values[i].setBlob(sqlite3_column_blob(stmtHandle, i), sqlite3_column_bytes(stmtHandle, i))
  551. Default
  552. values[i] = New TDBString
  553. values[i].setString(sizedUTF8toISO8859(sqlite3_column_text(stmtHandle, i), sqlite3_column_bytes(stmtHandle, i)))
  554. End Select
  555. Next
  556. index:+ 1
  557. Return True
  558. Case SQLITE_DONE
  559. If rec.isEmpty() Then
  560. initRecord()
  561. resetValues(rec.count())
  562. rec.SetIsEmptySet()
  563. End If
  564. ' prevent NextRow() returning True on the first fetch - since there are no rows.
  565. fakeFirstRowFetch = False
  566. sqlite3_reset(stmtHandle)
  567. Return False
  568. Default
  569. sqlite3_reset(stmtHandle)
  570. ' raise an error!
  571. conn.setError(String.FromUTF8String(sqlite3_errmsg(TDBSQLite(conn).handle)), Null, TDatabaseError.ERROR_STATEMENT, result)
  572. Return False
  573. End Select
  574. Return False
  575. End Method
  576. Method initRecord()
  577. rec.clear()
  578. Local colCount:Int = sqlite3_column_count(stmtHandle)
  579. If colCount <= 0 Then
  580. Return
  581. End If
  582. rec.init(colCount)
  583. For Local i:Int = 0 Until colCount
  584. Local columnName:String = String.FromUTF8String(sqlite3_column_name(stmtHandle, i))
  585. Local tn:Byte Ptr = sqlite3_column_decltype(stmtHandle, i)
  586. Local typeName:String
  587. If tn Then
  588. typeName = String.FromUTF8String(tn)
  589. End If
  590. Local dotPosition:Int = columnName.findLast(".") + 1
  591. rec.setField(i, TQueryField.Create(columnName[dotPosition..], dbTypeFromNative(typeName)))
  592. Next
  593. End Method
  594. Function dbTypeFromNative:Int(name:String, _type:Int = 0, _flags:Int = 0)
  595. name = name.ToLower()
  596. If name.startsWith("numeric") Then
  597. Return DBTYPE_DOUBLE
  598. End If
  599. Select name
  600. Case "integer"
  601. Return DBTYPE_LONG
  602. Case "int"
  603. Return DBTYPE_LONG
  604. Case "double"
  605. Return DBTYPE_DOUBLE
  606. Case "float"
  607. Return DBTYPE_DOUBLE
  608. Case "blob"
  609. Return DBTYPE_BLOB
  610. Default
  611. Return DBTYPE_STRING
  612. End Select
  613. End Function
  614. Method lastInsertedId:Long()
  615. If isActive() Then
  616. Local id:Long
  617. bmx_sqlite3_last_insert_rowid(conn.handle, Varptr id)
  618. Return id
  619. End If
  620. End Method
  621. Method rowsAffected:Int()
  622. If isActive() Then
  623. Return sqlite3_changes(conn.handle)
  624. End If
  625. Return -1
  626. End Method
  627. End Type
  628. Rem
  629. bbdoc: Loads an SQLite database from file into an already open in-memory database, or saves an in-memory database to a file.
  630. End Rem
  631. Function loadOrSaveDB:Int(inMemory:TDBSQLite, filename:String, isSave:Int, database:String = "main")
  632. Local db:TDBSQLite = TDBSQLite(TDBSQLite.Create(filename))
  633. Local rc:Int
  634. If db.isOpen() Then
  635. Local toHandle:Byte Ptr
  636. Local fromHandle:Byte Ptr
  637. If isSave Then
  638. fromHandle = inMemory.handle
  639. toHandle = db.handle
  640. Else
  641. fromHandle = db.handle
  642. toHandle = inMemory.handle
  643. End If
  644. Local d:Byte Ptr = database.ToUTF8String()
  645. Local backup:Byte Ptr = sqlite3_backup_init(toHandle, d, fromHandle, d)
  646. If backup Then
  647. sqlite3_backup_step(backup, -1)
  648. sqlite3_backup_finish(backup)
  649. End If
  650. rc = sqlite3_errcode(toHandle)
  651. db.close()
  652. Else
  653. If db.hasError() Then
  654. rc = db.error().errorValue
  655. End If
  656. End If
  657. Return rc
  658. End Function
  659. Type TSQLiteDatabaseLoader Extends TDatabaseLoader
  660. Method New()
  661. _type = "SQLITE"
  662. End Method
  663. Method LoadDatabase:TDBConnection( dbname:String = Null, host:String = Null, ..
  664. port:Int = Null, user:String = Null, password:String = Null, ..
  665. server:String = Null, options:String = Null )
  666. Return TDBSQLite.Create(dbName, host, port, user, password, server, options)
  667. End Method
  668. End Type
  669. AddDatabaseLoader New TSQLiteDatabaseLoader