odbc.bmx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053
  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 <copyright holder> 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 - ODBC
  29. about: An ODBC database driver for #Database.Core
  30. End Rem
  31. Module Database.ODBC
  32. ModuleInfo "Version: 1.09"
  33. ModuleInfo "Author: Bruce A Henderson"
  34. ModuleInfo "License: BSD"
  35. ModuleInfo "Copyright: Wrapper - 2007-2022 Bruce A Henderson"
  36. ModuleInfo "Copyright: iODBC - 2021 OpenLink Software"
  37. ModuleInfo "Modserver: BRL"
  38. ModuleInfo "History: 1.09"
  39. ModuleInfo "History: Update to iODBC 3.52.15.0b90ca1"
  40. ModuleInfo "History: Refactored glue."
  41. ModuleInfo "History: 1.08"
  42. ModuleInfo "History: Update to iODBC 3.52.12."
  43. ModuleInfo "History: Fixed for NG mem changes."
  44. ModuleInfo "History: 1.07"
  45. ModuleInfo "History: Fixed for NG and 64-bit."
  46. ModuleInfo "History: 1.06"
  47. ModuleInfo "History: Update to iODBC 3.52.8."
  48. ModuleInfo "History: Ensure string buffer is large enough."
  49. ModuleInfo "History: Core API updates."
  50. ModuleInfo "History: 1.05"
  51. ModuleInfo "History: Implemented Date, DateTime and Time types."
  52. ModuleInfo "History: 1.03"
  53. ModuleInfo "History: Fixed issue with mis-count of bound parameters."
  54. ModuleInfo "History: 1.02"
  55. ModuleInfo "History: Added hasPrepareSupport() and hasTransactionSupport() methods."
  56. ModuleInfo "History: 1.01"
  57. ModuleInfo "History: Fixed MacOS SQLSMALLINT/int cast issues."
  58. ModuleInfo "History: 1.00 Initial Release"
  59. ModuleInfo "History: Includes iODBC 3.52.5 source for Linux/MacOS module."
  60. ?linux
  61. ModuleInfo "CC_OPTS: -DHAVE_CONFIG_H"
  62. ?
  63. Import Database.Core
  64. Import BRL.StringBuilder
  65. Import Pub.STDC
  66. Import "common.bmx"
  67. Type TDBODBC Extends TDBConnection
  68. Field envHandle:Byte Ptr
  69. Function Create:TDBConnection(dbname:String = Null, host:String = Null, ..
  70. port:Int = Null, user:String = Null, password:String = Null, ..
  71. server:String = Null, options:String = Null)
  72. Local this:TDBODBC = New TDBODBC
  73. this.init(dbname, host, port, user, password, server, options)
  74. If this._dbname Then
  75. this.open(user, password)
  76. End If
  77. Return this
  78. End Function
  79. Method close()
  80. _isOpen = False
  81. If handle Then
  82. bmx_odbc_disconnectAndFree(handle)
  83. handle = Null
  84. End If
  85. If envHandle Then
  86. bmx_odbc_freeEnvHandle(envHandle)
  87. envHandle = Null
  88. End If
  89. End Method
  90. Method commit:Int()
  91. If Not _isOpen Then
  92. Return False
  93. End If
  94. Local result:Int = bmx_odbc_commitTransaction(handle)
  95. If isSQLError(result) Then
  96. processError(SQL_HANDLE_DBC, "Error committing transaction", TDatabaseError.ERROR_TRANSACTION)
  97. Return False
  98. End If
  99. result = bmx_odbc_toggleTransaction(handle, True) ' enable autocommit - ends transaction
  100. If isSQLError(result) Then
  101. processError(SQL_HANDLE_DBC, "Error ending transaction", TDatabaseError.ERROR_TRANSACTION)
  102. Return False
  103. End If
  104. Return True
  105. End Method
  106. Method getTables:String[]()
  107. Local list:String[]
  108. If Not _isOpen Then
  109. Return list
  110. End If
  111. Local stmtHandle:Byte Ptr
  112. ' allocate a new statement handle
  113. Local result:Int = bmx_odbc_SQLAllocHandle(SQL_HANDLE_STMT, handle, Varptr stmtHandle)
  114. If isSQLError(result) Then
  115. processError(SQL_HANDLE_DBC, "Error allocating statement handle", TDatabaseError.ERROR_STATEMENT)
  116. If stmtHandle Then
  117. result = bmx_odbc_freeStmtHandle(stmtHandle)
  118. End If
  119. Return list
  120. End If
  121. ' set the cursor to forward only
  122. result = bmx_odbc_setForwardCursor(stmtHandle)
  123. If isSQLError(result) Then
  124. processError(SQL_HANDLE_STMT, "Error setting cursor type", TDatabaseError.ERROR_STATEMENT, stmtHandle)
  125. result = bmx_odbc_freeStmtHandle(stmtHandle)
  126. Return list
  127. End If
  128. result = bmx_odbc_SQLTables(stmtHandle, "TABLE", 5)
  129. If isSQLError(result) Then
  130. processError(SQL_HANDLE_STMT, "Error setting cursor type", TDatabaseError.ERROR_STATEMENT, stmtHandle)
  131. result = bmx_odbc_freeStmtHandle(stmtHandle)
  132. Return list
  133. End If
  134. result = bmx_odbc_SQLFetchScroll(stmtHandle)
  135. Local tables:TList = New TList
  136. While result = SQL_SUCCESS
  137. ' This is a copy of the string stuff from executeQuery...
  138. ' It might be better to refactor things so that we can reuse the same code.
  139. Local lenIndicator:Int
  140. Local StaticArray buffer:Byte[256]
  141. Local sb:TStringBuilder = New TStringBuilder
  142. While True
  143. result = bmx_odbc_SQLGetData_string(stmtHandle, 3, buffer, 256, Varptr lenIndicator)
  144. If isSQLError(result) Or result = SQL_NO_DATA Then
  145. Exit
  146. End If
  147. ' nothing here...
  148. If lenIndicator = SQL_NULL_DATA Or lenIndicator = SQL_NO_TOTAL Then
  149. Exit
  150. End If
  151. Local actualSize:Int
  152. If result = SQL_SUCCESS_WITH_INFO Then
  153. actualSize = 256
  154. Else
  155. actualSize = lenIndicator
  156. End If
  157. sb.AppendUTF8Bytes(buffer, actualSize)
  158. If lenIndicator < 256 Then
  159. Exit
  160. End If
  161. Wend
  162. tables.addLast(sb.ToString())
  163. result = bmx_odbc_SQLFetchScroll(stmtHandle)
  164. Wend
  165. If tables.count() > 0 Then
  166. list = New String[tables.count()]
  167. Local i:Int = 0
  168. For Local s:String = EachIn tables
  169. list[i] = s
  170. i:+ 1
  171. Next
  172. End If
  173. result = bmx_odbc_freeStmtHandle(stmtHandle)
  174. Return list
  175. End Method
  176. Method getTableInfo:TDBTable(tableName:String, withDDL:Int = False)
  177. End Method
  178. Method open:Int(user:String = Null, pass:String = Null)
  179. If _isOpen Then
  180. close()
  181. End If
  182. Assert _server, "server identifies the Datasource name. It cannot be empty"
  183. If user Then
  184. _user = user
  185. End If
  186. If pass Then
  187. _password = pass
  188. End If
  189. ' allocate environment
  190. Local result:Int = bmx_odbc_SQLAllocHandle(SQL_HANDLE_ENV, Null, Varptr envHandle)
  191. If isSQLError(result) Then
  192. processError(SQL_HANDLE_ENV, "Error allocating environment", TDatabaseError.ERROR_CONNECTION)
  193. Return False
  194. End If
  195. ' set env to use odbc3
  196. bmx_odbc_setattr_odbc3(envHandle)
  197. ' allocate connection
  198. result = bmx_odbc_SQLAllocHandle(SQL_HANDLE_DBC, envHandle, Varptr handle)
  199. If isSQLError(result) Then
  200. processError(SQL_HANDLE_DBC, "Error allocating connection", TDatabaseError.ERROR_CONNECTION)
  201. Return False
  202. End If
  203. ' TODO : connection options
  204. ' connect to the driver/database
  205. Local connect:String
  206. If _server.contains("DRIVER") Or _server.contains("SERVER") Then
  207. connect = _server
  208. Else If _server.contains(".dsn") Then
  209. connect = "FILEDSN=" + _server
  210. Else
  211. connect = "DSN=" + _server
  212. End If
  213. If _dbname And _dbname.length > 0 Then
  214. connect:+ ";DATABASE=" + _dbname
  215. End If
  216. If _user Then
  217. connect:+ ";USER=" + _user
  218. End If
  219. If _password Then
  220. connect:+ ";PWD=" + _password
  221. End If
  222. If _host Then
  223. connect:+ ";HOST=" + _host
  224. End If
  225. If _port Then
  226. connect:+ ";PORT=" + _port
  227. End If
  228. Local conv:Byte Ptr = connect.ToUTF8String()
  229. result = bmx_odbc_SQLDriverConnect(handle, conv, int(strlen_(conv)))
  230. MemFree(conv)
  231. If isSQLError(result) Then
  232. processError(SQL_HANDLE_DBC, "Error opening connection", TDatabaseError.ERROR_CONNECTION)
  233. Return False
  234. End If
  235. ' success!
  236. _isOpen = True
  237. Return True
  238. End Method
  239. Method rollback:Int()
  240. If Not _isOpen Then
  241. Return False
  242. End If
  243. Local result:Int = bmx_odbc_rollbackTransaction(handle)
  244. If isSQLError(result) Then
  245. processError(SQL_HANDLE_DBC, "Error rolling back transaction", TDatabaseError.ERROR_TRANSACTION)
  246. Return False
  247. End If
  248. result = bmx_odbc_toggleTransaction(handle, True) ' enable autocommit - ends transaction
  249. If isSQLError(result) Then
  250. processError(SQL_HANDLE_DBC, "Error ending transaction", TDatabaseError.ERROR_TRANSACTION)
  251. Return False
  252. End If
  253. Return True
  254. End Method
  255. Method startTransaction:Int()
  256. If Not _isOpen Then
  257. Return False
  258. End If
  259. ' ODBC doesn't have an actual "begin work" type of option.
  260. ' Instead, we need to turn off "autocommit"...
  261. Local result:Int = bmx_odbc_toggleTransaction(handle, False) ' disable autocommit - begins transaction
  262. If isSQLError(result) Then
  263. processError(SQL_HANDLE_DBC, "Error starting transaction", TDatabaseError.ERROR_TRANSACTION)
  264. Return False
  265. End If
  266. Return True
  267. End Method
  268. Method databaseHandle:Byte Ptr()
  269. End Method
  270. Method createResultSet:TQueryResultSet()
  271. Return TODBCResultSet.Create(Self)
  272. End Method
  273. Method nativeErrorMessage:String(err:Int)
  274. End Method
  275. Method processError(kind:Int, msg:String, errType:Int, h:Byte Ptr = Null)
  276. Local code:Int
  277. Local err:String
  278. Select kind
  279. Case SQL_HANDLE_ENV
  280. err = bmx_odbc_envError(envHandle, Varptr code)
  281. Case SQL_HANDLE_DBC
  282. err = bmx_odbc_connError(handle, Varptr code)
  283. Case SQL_HANDLE_STMT
  284. err = bmx_odbc_stmtError(h, Varptr code)
  285. End Select
  286. setError(msg, err, errType, code)
  287. End Method
  288. Method hasPrepareSupport:Int()
  289. Return True
  290. End Method
  291. Method hasTransactionSupport:Int()
  292. Return True
  293. End Method
  294. End Type
  295. Function isSQLError:Int(result:Int)
  296. If result = SQL_SUCCESS Or result = SQL_SUCCESS_WITH_INFO Then
  297. Return False
  298. End If
  299. Return True
  300. End Function
  301. Type TODBCResultSet Extends TQueryResultSet
  302. Function Create:TQueryResultSet(db:TDBConnection, sql:String = Null)
  303. Local this:TODBCResultSet = New TODBCResultSet
  304. this.init(db, sql)
  305. this.rec = TQueryRecord.Create()
  306. Return this
  307. End Function
  308. Method executeQuery:Int(statement:String)
  309. _isActive = False
  310. index = SQL_BeforeFirstRow
  311. rec.clear()
  312. Local result:Int
  313. If stmtHandle Then
  314. result = bmx_odbc_freeStmtHandle(stmtHandle)
  315. stmtHandle = Null
  316. If isSQLError(result) Then
  317. TDBODBC(conn).processError(SQL_HANDLE_STMT, "Error freeing statement handle", TDatabaseError.ERROR_STATEMENT, stmtHandle)
  318. Return False
  319. End If
  320. End If
  321. ' allocate a new statement handle
  322. result = bmx_odbc_SQLAllocHandle(SQL_HANDLE_STMT, conn.handle, Varptr stmtHandle)
  323. If isSQLError(result) Then
  324. TDBODBC(conn).processError(SQL_HANDLE_DBC, "Error allocating statement handle", TDatabaseError.ERROR_STATEMENT)
  325. Return False
  326. End If
  327. ' set the cursor to forward only
  328. result = bmx_odbc_setForwardCursor(stmtHandle)
  329. If isSQLError(result) Then
  330. TDBODBC(conn).processError(SQL_HANDLE_STMT, "Error setting cursor type", TDatabaseError.ERROR_STATEMENT, stmtHandle)
  331. Return False
  332. End If
  333. Local q:Byte Ptr = statement.ToUTF8String()
  334. ' execute the query
  335. result = bmx_odbc_execute(stmtHandle, q, int(strlen_(q)))
  336. MemFree(q)
  337. If isSQLError(result) Then
  338. TDBODBC(conn).processError(SQL_HANDLE_STMT, "Error executing statement", TDatabaseError.ERROR_STATEMENT, stmtHandle)
  339. Return False
  340. End If
  341. Local fieldCount:Int
  342. bmx_odbc_SQLNumResultCols(stmtHandle, Varptr fieldCount)
  343. initRecord(fieldCount)
  344. ' this was a select... we can populate the fields with information (column name, size, etc)
  345. If fieldCount <> 0 Then
  346. Local bufferLength:Int = 256
  347. Local StaticArray columnName:Byte[256]
  348. Local nameLength:Int
  349. Local dataType:Int
  350. Local columnSize:Int
  351. Local decimalDigits:Int
  352. Local nullable:Int
  353. For Local i:Int = 0 Until fieldCount
  354. ' get the column/field description
  355. result = bmx_odbc_SQLDescribeCol(stmtHandle, i + 1, columnName, bufferLength, ..
  356. Varptr nameLength, Varptr dataType, Varptr columnSize, ..
  357. Varptr decimalDigits, Varptr nullable)
  358. If isSQLError(result) Then
  359. TDBODBC(conn).processError(SQL_HANDLE_STMT, "Error getting column description", TDatabaseError.ERROR_STATEMENT, stmtHandle)
  360. Return False
  361. End If
  362. Local qf:TQueryField = TQueryField.Create(String.FromUTF8Bytes(columnName, nameLength), dbTypeFromNative(Null, dataType))
  363. If columnSize = 0 Then
  364. qf.length = -1 ' not specified
  365. Else
  366. qf.length = columnSize
  367. End If
  368. If decimalDigits = 0 Then
  369. qf.precision = -1 ' not specified
  370. Else
  371. qf.precision = decimalDigits
  372. End If
  373. If nullable = SQL_NULLABLE Then
  374. qf.nullable = True
  375. Else If nullable = SQL_NO_NULLS Then
  376. qf.nullable = False
  377. End If
  378. rec.setField(i, qf)
  379. Next
  380. End If
  381. _isActive = True
  382. Return True
  383. End Method
  384. Method prepare:Int(statement:String)
  385. _isActive = False
  386. index = SQL_BeforeFirstRow
  387. rec.clear()
  388. Local result:Int
  389. If stmtHandle Then
  390. result = bmx_odbc_freeStmtHandle(stmtHandle)
  391. stmtHandle = Null
  392. If isSQLError(result) Then
  393. TDBODBC(conn).processError(SQL_HANDLE_STMT, "Error freeing statement handle", TDatabaseError.ERROR_STATEMENT, stmtHandle)
  394. Return False
  395. End If
  396. End If
  397. ' allocate a new statement handle
  398. result = bmx_odbc_SQLAllocHandle(SQL_HANDLE_STMT, conn.handle, Varptr stmtHandle)
  399. If isSQLError(result) Then
  400. TDBODBC(conn).processError(SQL_HANDLE_DBC, "Error allocating statement handle", TDatabaseError.ERROR_STATEMENT)
  401. Return False
  402. End If
  403. ' set the cursor to forward only
  404. result = bmx_odbc_setForwardCursor(stmtHandle)
  405. If isSQLError(result) Then
  406. TDBODBC(conn).processError(SQL_HANDLE_STMT, "Error setting cursor type", TDatabaseError.ERROR_STATEMENT, stmtHandle)
  407. Return False
  408. End If
  409. Local q:Byte Ptr = statement.ToUTF8String()
  410. ' prepare the query
  411. result = bmx_odbc_prepare(stmtHandle, q, int(strlen_(q)))
  412. MemFree(q)
  413. If isSQLError(result) Then
  414. TDBODBC(conn).processError(SQL_HANDLE_STMT, "Error preparing statement", TDatabaseError.ERROR_STATEMENT, stmtHandle)
  415. Return False
  416. End If
  417. Return True
  418. End Method
  419. Method execute:Int()
  420. _isActive = False
  421. index = SQL_BeforeFirstRow
  422. If Not stmtHandle Then
  423. Return False
  424. End If
  425. Local result:Int = 0
  426. ' BIND stuff
  427. Local values:TDBType[] = boundValues
  428. Local strings:Byte Ptr[]
  429. Local paramCount:Int
  430. If values Then
  431. paramCount = bindCount
  432. Local isNull:Int[] = New Int[paramCount]
  433. strings = New Byte Ptr[paramCount]
  434. For Local i:Int = 0 Until paramCount
  435. isNull[i] = False
  436. If Not values[i] Or values[i].isNull() Then
  437. isNull[i] = SQL_NULL_DATA
  438. End If
  439. Select values[i].kind()
  440. Case DBTYPE_INT
  441. If Not values[i] Then
  442. values[i] = New TDBInt
  443. End If
  444. result = bmx_odbc_SQLBindParameter_int(stmtHandle, i + 1, Varptr TDBInt(values[i]).value, Varptr isNull[i])
  445. Case DBTYPE_FLOAT
  446. If Not values[i] Then
  447. values[i] = New TDBDouble
  448. End If
  449. ' since ODBC doesn't do Floats, we convert to a Double, just to be safe
  450. If TDBFloat(values[i]) Then
  451. Local d:TDBDouble = New TDBDouble
  452. d.setDouble(Double(TDBFloat(values[i]).value))
  453. values[i].clear()
  454. values[i] = d
  455. End If
  456. result = bmx_odbc_SQLBindParameter_double(stmtHandle, i + 1, Varptr TDBDouble(values[i]).value, Varptr isNull[i])
  457. Case DBTYPE_DOUBLE
  458. If Not values[i] Then
  459. values[i] = New TDBDouble
  460. End If
  461. result = bmx_odbc_SQLBindParameter_double(stmtHandle, i + 1, Varptr TDBDouble(values[i]).value, Varptr isNull[i])
  462. Case DBTYPE_LONG
  463. If Not values[i] Then
  464. values[i] = New TDBLong
  465. End If
  466. result = bmx_odbc_SQLBindParameter_long(stmtHandle, i + 1, Varptr TDBLong(values[i]).value, Varptr isNull[i])
  467. Case DBTYPE_STRING
  468. If Not values[i] Then
  469. values[i] = New TDBString
  470. End If
  471. Local s:Byte Ptr = values[i].getString().ToUTF8String()
  472. strings[i] = s
  473. Local length:Int = strlen_(s)
  474. If Not isNull[i] Then
  475. isNull[i] = length
  476. End If
  477. result = bmx_odbc_SQLBindParameter_string(stmtHandle, i + 1, s, length, Varptr isNull[i])
  478. Case DBTYPE_BLOB
  479. 'result = sqlite3_bind_blob(stmtHandle, i + 1, values[i].getBlob(), values[i].size(), 0)
  480. Case DBTYPE_DATE
  481. If Not values[i] Then
  482. values[i] = New TDBDate
  483. End If
  484. Local s:Byte Ptr = values[i].getString().ToUTF8String()
  485. strings[i] = s
  486. Local length:Int = strlen_(s)
  487. If Not isNull[i] Then
  488. isNull[i] = length
  489. End If
  490. result = bmx_odbc_SQLBindParameter_string(stmtHandle, i + 1, s, length, Varptr isNull[i])
  491. Case DBTYPE_DATETIME
  492. If Not values[i] Then
  493. values[i] = New TDBDateTime
  494. End If
  495. Local s:Byte Ptr = values[i].getString().ToUTF8String()
  496. strings[i] = s
  497. Local length:Int = strlen_(s)
  498. If Not isNull[i] Then
  499. isNull[i] = length
  500. End If
  501. result = bmx_odbc_SQLBindParameter_string(stmtHandle, i + 1, s, length, Varptr isNull[i])
  502. Case DBTYPE_TIME
  503. If Not values[i] Then
  504. values[i] = New TDBTime
  505. End If
  506. Local s:Byte Ptr = values[i].getString().ToUTF8String()
  507. strings[i] = s
  508. Local length:Int = strlen_(s)
  509. If Not isNull[i] Then
  510. isNull[i] = length
  511. End If
  512. result = bmx_odbc_SQLBindParameter_string(stmtHandle, i + 1, s, length, Varptr isNull[i])
  513. End Select
  514. If isSQLError(result) Then
  515. TDBODBC(conn).processError(SQL_HANDLE_STMT, "Error binding parameters", TDatabaseError.ERROR_STATEMENT, stmtHandle)
  516. ' free up the strings
  517. For Local i:Int = 0 Until paramCount
  518. If strings[i] Then
  519. MemFree(strings[i])
  520. End If
  521. Next
  522. Return False
  523. End If
  524. Next
  525. End If
  526. ' execute the query
  527. result = bmx_odbc_executePrepared(stmtHandle)
  528. If strings Then
  529. ' free up the strings
  530. For Local i:Int = 0 Until paramCount
  531. If strings[i] Then
  532. MemFree(strings[i])
  533. End If
  534. Next
  535. End If
  536. If isSQLError(result) Then
  537. TDBODBC(conn).processError(SQL_HANDLE_STMT, "Error executing statement", TDatabaseError.ERROR_STATEMENT, stmtHandle)
  538. Return False
  539. End If
  540. Local fieldCount:Int
  541. bmx_odbc_SQLNumResultCols(stmtHandle, Varptr fieldCount)
  542. initRecord(fieldCount)
  543. ' this was a select... we can populate the fields with information (column name, size, etc)
  544. If fieldCount <> 0 Then
  545. Local bufferLength:Int = 256
  546. Local StaticArray columnName:Byte[256]
  547. Local nameLength:Int
  548. Local dataType:Int
  549. Local columnSize:Int
  550. Local decimalDigits:Int
  551. Local nullable:Int
  552. For Local i:Int = 0 Until fieldCount
  553. ' get the column/field description
  554. result = bmx_odbc_SQLDescribeCol(stmtHandle, i + 1, columnName, bufferLength, ..
  555. Varptr nameLength, Varptr dataType, Varptr columnSize, ..
  556. Varptr decimalDigits, Varptr nullable)
  557. If isSQLError(result) Then
  558. TDBODBC(conn).processError(SQL_HANDLE_STMT, "Error getting column description", TDatabaseError.ERROR_STATEMENT, stmtHandle)
  559. Return False
  560. End If
  561. Local qf:TQueryField = TQueryField.Create(String.FromUTF8Bytes(columnName, nameLength), dbTypeFromNative(Null, dataType))
  562. If columnSize = 0 Then
  563. qf.length = -1 ' not specified
  564. Else
  565. qf.length = columnSize
  566. End If
  567. If decimalDigits = 0 Then
  568. qf.precision = -1 ' not specified
  569. Else
  570. qf.precision = decimalDigits
  571. End If
  572. If nullable = SQL_NULLABLE Then
  573. qf.nullable = True
  574. Else If nullable = SQL_NO_NULLS Then
  575. qf.nullable = False
  576. End If
  577. rec.setField(i, qf)
  578. Next
  579. End If
  580. _isActive = True
  581. Return True
  582. End Method
  583. Method firstRow:Int()
  584. If index = SQL_BeforeFirstRow Then
  585. Return nextRow()
  586. End If
  587. Return False
  588. End Method
  589. Method nextRow:Int()
  590. If Not stmtHandle Then
  591. Return False
  592. End If
  593. Local result:Int = bmx_odbc_SQLFetchScroll(stmtHandle)
  594. If result <> SQL_SUCCESS And result <> SQL_SUCCESS_WITH_INFO Then
  595. If result <> SQL_NO_DATA Then
  596. TDBODBC(conn).processError(SQL_HANDLE_STMT, "Error fetching row", TDatabaseError.ERROR_STATEMENT, stmtHandle)
  597. End If
  598. Return False
  599. End If
  600. ' now populate the values[] array with the fetched data !
  601. For Local i:Int = 0 Until rec.count()
  602. If values[i] Then
  603. values[i].clear()
  604. End If
  605. Local lenIndicator:Int
  606. Select rec.fields[i].fType
  607. Case DBTYPE_INT
  608. Local intValue:Int
  609. result = bmx_odbc_SQLGetData_int(stmtHandle, i + 1, Varptr intValue, Varptr lenIndicator)
  610. If isSQLError(result) Or lenIndicator = SQL_NULL_DATA Then
  611. Continue
  612. End If
  613. values[i] = New TDBInt
  614. values[i].setInt(intValue)
  615. Case DBTYPE_LONG
  616. Local longValue:Long
  617. result = bmx_odbc_SQLGetData_long(stmtHandle, i + 1, Varptr longValue, Varptr lenIndicator)
  618. If isSQLError(result) Or lenIndicator = SQL_NULL_DATA Then
  619. Continue
  620. End If
  621. values[i] = New TDBLong
  622. values[i].setLong(longValue)
  623. Case DBTYPE_DOUBLE
  624. Local doubleValue:Double
  625. result = bmx_odbc_SQLGetData_double(stmtHandle, i + 1, Varptr doubleValue, Varptr lenIndicator)
  626. If isSQLError(result) Or lenIndicator = SQL_NULL_DATA Then
  627. Continue
  628. End If
  629. values[i] = New TDBDouble
  630. values[i].setDouble(doubleValue)
  631. Case DBTYPE_DATE
  632. Local y:Int, m:Int, d:Int
  633. result = bmx_odbc_SQLGetData_date(stmtHandle, i + 1, Varptr y, Varptr m, ..
  634. Varptr d, Varptr lenIndicator)
  635. If isSQLError(result) Or lenIndicator = SQL_NULL_DATA Then
  636. Continue
  637. End If
  638. Local date:TDBDate = New TDBDate
  639. values[i] = date
  640. date.setFromParts(y, m, d)
  641. Case DBTYPE_DATETIME
  642. Local y:Int, m:Int, d:Int, hh:Int, mm:Int, ss:Int
  643. result = bmx_odbc_SQLGetData_datetime(stmtHandle, i + 1, Varptr y, Varptr m, ..
  644. Varptr d, Varptr hh, Varptr mm, Varptr ss, Varptr lenIndicator)
  645. If isSQLError(result) Or lenIndicator = SQL_NULL_DATA Then
  646. Continue
  647. End If
  648. Local date:TDBDateTime = New TDBDateTime
  649. values[i] = date
  650. date.setFromParts(y, m, d, hh, mm, ss)
  651. Case DBTYPE_TIME
  652. Local hh:Int, mm:Int, ss:Int
  653. result = bmx_odbc_SQLGetData_time(stmtHandle, i + 1, Varptr hh, Varptr mm, Varptr ss, Varptr lenIndicator)
  654. If isSQLError(result) Or lenIndicator = SQL_NULL_DATA Then
  655. Continue
  656. End If
  657. Local date:TDBTime = New TDBTime
  658. values[i] = date
  659. date.setFromParts(hh, mm, ss)
  660. Case DBTYPE_BLOB
  661. ' TODO
  662. Default
  663. Local s:String = getStringData(rec.fields[i], i, result)
  664. If isSQLError(result) Or result = SQL_NO_DATA Then
  665. Continue
  666. End If
  667. If s Then
  668. values[i] = New TDBString
  669. values[i].setString(s)
  670. End If
  671. End Select
  672. Next
  673. index:+ 1
  674. Return True
  675. End Method
  676. Method getStringData:String(record:TQueryField, i:Int, result:Int Var)
  677. ' Strings are returned in blocks... so we need to loop thru
  678. ' to get all the possible data.
  679. Local sb:TStringBuilder = New TStringBuilder
  680. Local bufferSize:Int = record.length
  681. If record.length <= 0 Then
  682. bufferSize = 256
  683. Else If record.length > 65536 Then
  684. bufferSize = 65536
  685. Else
  686. bufferSize :+ 1 ' Add an extra char for null termination
  687. End If
  688. Local lenIndicator:Int
  689. Local buffer:Byte Ptr = MemAlloc(Size_T(bufferSize))
  690. Local s:String
  691. While True
  692. result = bmx_odbc_SQLGetData_string(stmtHandle, i + 1, buffer, bufferSize, Varptr lenIndicator)
  693. If isSQLError(result) Or result = SQL_NO_DATA Then
  694. Exit
  695. End If
  696. ' nothing here...
  697. If lenIndicator = SQL_NULL_DATA Or lenIndicator = SQL_NO_TOTAL Then
  698. Exit
  699. End If
  700. Local actualSize:Int
  701. If result = SQL_SUCCESS_WITH_INFO Then
  702. actualSize = bufferSize
  703. Else
  704. actualSize = lenIndicator
  705. End If
  706. sb.AppendUTF8Bytes(buffer, actualSize)
  707. If lenIndicator < bufferSize Then
  708. Exit
  709. End If
  710. Wend
  711. ' free up the buffer memory
  712. MemFree(buffer)
  713. Return sb.ToString()
  714. End Method
  715. Method lastInsertedId:Long()
  716. ' TODO : with... "SELECT LAST_INSERT_ID()"
  717. End Method
  718. Method rowsAffected:Int()
  719. If stmtHandle Then
  720. Local num:Int
  721. Local result:Int = bmx_odbc_SQLRowCount(stmtHandle, Varptr num)
  722. If Not isSQLError(result) Then
  723. Return num
  724. End If
  725. End If
  726. Return -1
  727. End Method
  728. Function dbTypeFromNative:Int(name:String, _type:Int = 0, _flags:Int = 0)
  729. Local dbType:Int
  730. Select _type
  731. Case SQL_SMALLINT, SQL_INTEGER, SQL_BIT, SQL_TINYINT
  732. dbType = DBTYPE_INT
  733. Case SQL_BIGINT, 65531
  734. ' 65531 is what it returned from a bigint field against a mysql DB on linux....
  735. dbType = DBTYPE_LONG
  736. Case SQL_DECIMAL, SQL_NUMERIC, SQL_REAL, SQL_FLOAT, SQL_DOUBLE
  737. dbType = DBTYPE_DOUBLE
  738. Case SQL_DATE, SQL_TYPE_DATE
  739. dbType = DBTYPE_DATE
  740. Case SQL_TIME, SQL_TYPE_TIME
  741. dbType = DBTYPE_TIME
  742. Case SQL_TIMESTAMP, SQL_TYPE_TIMESTAMP
  743. dbType = DBTYPE_DATETIME
  744. Case SQL_BINARY, SQL_VARBINARY, SQL_LONGVARBINARY
  745. dbType = DBTYPE_BLOB
  746. Default
  747. dbType = DBTYPE_STRING
  748. End Select
  749. Return dbType
  750. End Function
  751. Method initRecord(size:Int)
  752. rec.clear()
  753. If size > 0 Then
  754. rec.init(size)
  755. End If
  756. resetValues(size)
  757. End Method
  758. End Type
  759. Type TODBCDatabaseLoader Extends TDatabaseLoader
  760. Method New()
  761. _type = "ODBC"
  762. End Method
  763. Method LoadDatabase:TDBConnection( dbname:String = Null, host:String = Null, ..
  764. port:Int = Null, user:String = Null, password:String = Null, ..
  765. server:String = Null, options:String = Null )
  766. Return TDBODBC.Create(dbName, host, port, user, password, server, options)
  767. End Method
  768. End Type
  769. AddDatabaseLoader New TODBCDatabaseLoader