core.bmx 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455
  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 Framework
  29. End Rem
  30. Module Database.Core
  31. ModuleInfo "Version: 1.09"
  32. ModuleInfo "Author: Bruce A Henderson"
  33. ModuleInfo "License: BSD"
  34. ModuleInfo "Copyright: Bruce A Henderson"
  35. ModuleInfo "Modserver: BRL"
  36. ModuleInfo "History: 1.09"
  37. ModuleInfo "History: Refactored use of 'enum'."
  38. ModuleInfo "History: 1.08"
  39. ModuleInfo "History: Fixed prepared statement reuse issue with some drivers."
  40. ModuleInfo "History: Added some integrity checks to TQueryRecord methods."
  41. ModuleInfo "History: Added getTableInfo(), TDBTable and TDBColum."
  42. ModuleInfo "History: Improvements to TDBBlob."
  43. ModuleInfo "History: 1.07"
  44. ModuleInfo "History: Resets error status before execution of new query."
  45. ModuleInfo "History: 1.06"
  46. ModuleInfo "History: Implementation of Date, DateTime and Time types."
  47. ModuleInfo "History: 1.05"
  48. ModuleInfo "History: Improved object cleanup."
  49. ModuleInfo "History: 1.04"
  50. ModuleInfo "History: Improved getFieldByName efficiency."
  51. ModuleInfo "History: Added TQueryRecord helper methods for type/name retrieval - getXXXByName()."
  52. ModuleInfo "History: 1.03"
  53. ModuleInfo "History: Fixed clearing of lasterror after successful query prepare/execute."
  54. ModuleInfo "History: 1.02"
  55. ModuleInfo "History: Added TDatabaseQuery helper binding functions for set/add values."
  56. ModuleInfo "History: Docs update."
  57. ModuleInfo "History: 1.01"
  58. ModuleInfo "History: Fixed Null exception on re-prepare."
  59. ModuleInfo "History: Added TDatabaseQuery clearBindValues() method."
  60. ModuleInfo "History: Added getter methods to TQueryRecord for String, Int, Long, Float and Double."
  61. ModuleInfo "History: Added hasPrepareSupport() and hasTransactionSupport() methods."
  62. ModuleInfo "History: 1.00"
  63. ModuleInfo "History: Initial Release."
  64. Import BRL.LinkedList
  65. Import BRL.Map
  66. Import "dbtypes.bmx"
  67. Const SQL_BeforeFirstRow:Int = -1
  68. Const SQL_AfterLastRow:Int = -2
  69. Rem
  70. bbdoc: Represents a connection to a database.
  71. about: Usually, creating a #TDBConnection object is done through a call to #LoadDatabase with an
  72. appropriate dbtype parameter.<br>
  73. End Rem
  74. Type TDBConnection Abstract
  75. ' the native handle
  76. Field handle:Byte Ptr
  77. Field _dbname:String
  78. Field _host:String
  79. Field _port:Int
  80. Field _user:String
  81. Field _password:String
  82. Field _options:String
  83. Field _server:String
  84. Field _isOpen:Int = False
  85. Field _lastError:TDatabaseError
  86. ' actual implementation in the driver
  87. Function Create:TDBConnection(dbname:String = Null, host:String = Null, ..
  88. port:Int = Null, user:String = Null, password:String = Null, ..
  89. server:String = Null, options:String = Null) Abstract
  90. Method Init(dbname:String, host:String, port:Int, user:String, password:String, server:String, options:String)
  91. _dbname = dbname
  92. _host = host
  93. _port = port
  94. _user = user
  95. _password = password
  96. _options = options
  97. _server = server
  98. End Method
  99. Rem
  100. bbdoc: Closes the database connection.
  101. about: Check #hasError and #error for details of any problems.
  102. End Rem
  103. Method close() Abstract
  104. Rem
  105. bbdoc: Commits a database transaction.
  106. returns: True if successful.
  107. about: Calling this method is only valid for a previous call to #startTransaction.
  108. <p>Check #hasError and #error for details of any problems.</p>
  109. End Rem
  110. Method commit:Int() Abstract
  111. Rem
  112. bbdoc: Returns a list of table names for the current database.
  113. End Rem
  114. Method getTables:String[]() Abstract
  115. Rem
  116. bbdoc:
  117. End Rem
  118. Method getTableInfo:TDBTable(tableName:String, withDDL:Int = False) Abstract
  119. Rem
  120. bbdoc: Attempts to open a new database connection.
  121. returns: True if successful.
  122. about: Check #hasError and #error for details of any problems.
  123. End Rem
  124. Method open:Int(user:String = Null, pass:String = Null) Abstract
  125. Rem
  126. bbdoc: Rolls back a database transaction.
  127. returns: True if successful.
  128. about: Calling this method is only valid for a previous call to #startTransaction.
  129. <p>Check #hasError and #error for details of any problems.</p>
  130. End Rem
  131. Method rollback:Int() Abstract
  132. Rem
  133. bbdoc: Starts a database transaction.
  134. returns: True if successful.
  135. about: Once a transaction has started, it should be eventually closed with a call to either
  136. #rollback (if the transaction should be abandoned) or #commit (to save all database changes).
  137. <p>Check #hasError and #error for details of any problems.</p>
  138. End Rem
  139. Method startTransaction:Int() Abstract
  140. Rem
  141. bbdoc: Executes an sql statement.
  142. returns: A new #TDatabaseQuery object.
  143. about: Check #hasError and #error for details of any problems.
  144. End Rem
  145. Method executeQuery:TDatabaseQuery(sql:String)
  146. resetError()
  147. Local query:TDatabaseQuery = TDatabaseQuery.Create(Self)
  148. If sql And sql.length > 0 Then
  149. If query.execute(sql) Then
  150. ' reset error...
  151. resetError()
  152. End If
  153. End If
  154. Return query
  155. End Method
  156. Rem
  157. bbdoc: Determines if the database connection is open.
  158. returns: True if the connection is open.
  159. End Rem
  160. Method isOpen:Int()
  161. Return _isOpen
  162. End Method
  163. Rem
  164. bbdoc: Returns the database name.
  165. returns: The database name.
  166. End Rem
  167. Method getDatabaseName:String()
  168. Return _dbName
  169. End Method
  170. Rem
  171. bbdoc: Returns the connection host.
  172. returns: The host, or Null.
  173. about: Not all drivers require a Host.
  174. End Rem
  175. Method getHost:String()
  176. Return _dbName
  177. End Method
  178. Rem
  179. bbdoc: Returns the connection port number.
  180. returns: The port number, or 0.
  181. about: Not all drivers require a Port number.
  182. End Rem
  183. Method getPortNumber:Int()
  184. Return _port
  185. End Method
  186. Rem
  187. bbdoc: Returns the last database error.
  188. returns: A #TDatabaseError object.
  189. about: Will always return a valid #TDatabaseError object.
  190. End Rem
  191. Method error:TDatabaseError()
  192. If Not _lastError Then
  193. _lastError = New TDatabaseError
  194. End If
  195. Return _lastError
  196. End Method
  197. Rem
  198. bbdoc: Resets error.
  199. End Rem
  200. Method resetError()
  201. If _lasterror Then
  202. _lasterror.reset()
  203. End If
  204. End Method
  205. Rem
  206. bbdoc: Determines if there is an outstanding error.
  207. returns: True if there is an error.<br>
  208. Use #error to retrieve the #TDatabaseError object.
  209. End Rem
  210. Method hasError:Int()
  211. If _lastError Then
  212. Return _lastError.isSet()
  213. End If
  214. Return False
  215. End Method
  216. Method setError(error:String, append:String = Null, eType:Int, errorValue:Int = 0)
  217. If Not _lastError Then
  218. _lastError = TDatabaseError.Create(Self, error, append, eType, errorValue)
  219. Else
  220. _lastError.error = error
  221. If append Then
  222. _lastError.error:+ " : " + append
  223. End If
  224. _lastError.errorValue = errorValue
  225. _lastError.errorType = eType
  226. End If
  227. End Method
  228. Method databaseHandle:Byte Ptr() Abstract
  229. Method createResultSet:TQueryResultSet() Abstract
  230. Method nativeErrorMessage:String(err:Int) Abstract
  231. Rem
  232. bbdoc: Determines if the database has support for Prepare/Execute statements.
  233. returns: True if the driver supports Prepare/Execute statements.
  234. End Rem
  235. Method hasPrepareSupport:Int() Abstract
  236. Rem
  237. bbdoc: Determines if the database has transactioning support.
  238. returns: True if the driver supports transactions.
  239. End Rem
  240. Method hasTransactionSupport:Int() Abstract
  241. Method free()
  242. If _lastError Then
  243. _lastError = Null
  244. End If
  245. End Method
  246. Method Delete()
  247. free()
  248. End Method
  249. End Type
  250. Rem
  251. bbdoc: A Query object for executing queries and navigating the result sets.
  252. End Rem
  253. Type TDatabaseQuery
  254. Field conn:TDBConnection
  255. Field resultSet:TQueryResultSet
  256. Rem
  257. bbdoc: Creates a new #TDatabaseQuery using the supplied @connection.
  258. End Rem
  259. Function Create:TDatabaseQuery(connection:TDBConnection)
  260. Local this:TDatabaseQuery = New TDatabaseQuery
  261. this.conn = connection
  262. Return this
  263. End Function
  264. Rem
  265. bbdoc: Prepares an SQL statement for execution.
  266. returns: True if the prepare succeeded.
  267. about: Check connection #hasError and #error for details of any problems.
  268. End Rem
  269. Method prepare:Int(statement:String)
  270. If Not resultSet Then
  271. resultSet = conn.createResultSet()
  272. Else
  273. resultSet.clear()
  274. resultSet._isActive = False
  275. End If
  276. If statement = Null Or statement.length = 0 Then
  277. conn.setError("Cannot prepare empty statement", Null, TDatabaseError.ERROR_STATEMENT)
  278. Return False
  279. End If
  280. resultSet.query = statement.Trim()
  281. If Not conn.isOpen() Then
  282. conn.setError("The connection is not open", Null, TDatabaseError.ERROR_CONNECTION)
  283. Return False
  284. End If
  285. If resultSet.prepare(statement) Then
  286. ' on success, reset the last error.
  287. If conn._lasterror Then
  288. conn._lasterror.reset()
  289. End If
  290. Return True
  291. Else
  292. Return False
  293. End If
  294. End Method
  295. Rem
  296. bbdoc: Executes an SQL statement.
  297. returns: True if the execute succeeded.
  298. about: For a previously prepared statement, pass Null into this method.
  299. <p>Check connection #hasError and #error for details of any problems.</p>
  300. End Rem
  301. Method execute:Int(statement:String = Null)
  302. If statement Then
  303. If Not resultSet Then
  304. resultSet = conn.createResultSet()
  305. Else
  306. resultSet.clear()
  307. resultSet._isActive = False
  308. End If
  309. If statement.Trim().length = 0 Then
  310. conn.setError("Cannot execute empty statement", Null, TDatabaseError.ERROR_STATEMENT)
  311. Return False
  312. End If
  313. resultSet.query = statement.Trim()
  314. If Not conn.isOpen() Then
  315. conn.setError("The connection is not open", Null, TDatabaseError.ERROR_CONNECTION)
  316. Return False
  317. End If
  318. If resultSet.executeQuery(statement) Then
  319. ' on success, reset the last error.
  320. If conn._lasterror Then
  321. conn._lasterror.reset()
  322. End If
  323. Return True
  324. Else
  325. Return False
  326. End If
  327. Else
  328. If Not conn.isOpen() Then
  329. conn.setError("The connection is not open", Null, TDatabaseError.ERROR_CONNECTION)
  330. Return False
  331. End If
  332. If resultSet.execute() Then
  333. ' on success, reset the last error.
  334. If conn._lasterror Then
  335. conn._lasterror.reset()
  336. End If
  337. Return True
  338. Else
  339. Return False
  340. End If
  341. End If
  342. End Method
  343. Rem
  344. bbdoc: Returns the value of the field at @index.
  345. returns: A #TDBType object or Null.
  346. End Rem
  347. Method value:TDBType(index:Int)
  348. If isActive() And index > SQL_BeforeFirstRow Then
  349. Return resultSet.dataValue(index)
  350. End If
  351. Return Null
  352. End Method
  353. Rem
  354. bbdoc: Retrieves the next row in the result set.
  355. returns: True if a row was retrieved.
  356. about: Each call to this method populates a #TQueryRecord which can be
  357. retrieved via the #record method.
  358. <p>Check connection #hasError and #error for details of any problems.</p>
  359. End Rem
  360. Method nextRow:Int()
  361. If Not isActive() Then
  362. Return False
  363. End If
  364. Local result:Int
  365. Select rowIndex()
  366. Case SQL_BeforeFirstRow
  367. result = resultSet.firstRow()
  368. Return result
  369. Case SQL_AfterLastRow
  370. Return False
  371. Default
  372. If Not resultSet.nextRow() Then
  373. resultSet.setRowIndex(SQL_AfterLastRow)
  374. ' done with the resultset...
  375. resultSet.reset()
  376. Return False
  377. End If
  378. End Select
  379. Return True
  380. End Method
  381. Method isActive:Int()
  382. Return resultSet And resultSet.isActive()
  383. End Method
  384. Method rowIndex:Int()
  385. Return resultSet.rowIndex()
  386. End Method
  387. Rem
  388. bbdoc: Returns the record for the query.
  389. End Rem
  390. Method rowRecord:TQueryRecord()
  391. Local r:TQueryRecord = resultSet.rowRecord()
  392. ' if the resultSet is valid we can fill in the values.
  393. If resultSet.isValid() Then
  394. Local c:Int = r.count()
  395. For Local i:Int = 0 Until c
  396. r.setValue(i, value(i))
  397. Next
  398. End If
  399. Return r
  400. End Method
  401. Rem
  402. bbdoc: Binds a #TDBType value at the specified position.
  403. about: If a bind value already exists at the specified @position, it is replaced with the new one.
  404. End Rem
  405. Method bindValue(position:Int, value:TDBType)
  406. If resultSet Then
  407. resultSet.bindValue(position, value)
  408. End If
  409. End Method
  410. Rem
  411. bbdoc: Adds a new #TDBType bind value.
  412. about: The value is added to the end of the current list of bind values.
  413. End Rem
  414. Method addBindValue(value:TDBType)
  415. If resultSet Then
  416. resultSet.addBindValue(value)
  417. End If
  418. End Method
  419. Rem
  420. bbdoc: Binds the String @value at the specified @position.
  421. about: If a bind value already exists at the specified @position, it is replaced with the new one.
  422. End Rem
  423. Method setString(position:Int, value:String)
  424. bindValue(position, TDBString.Set(value))
  425. End Method
  426. Rem
  427. bbdoc: Binds the Int @value at the specified @position.
  428. about: If a bind value already exists at the specified @position, it is replaced with the new one.
  429. End Rem
  430. Method setInt(position:Int, value:Int)
  431. bindValue(position, TDBInt.Set(value))
  432. End Method
  433. Rem
  434. bbdoc: Binds the Long @value at the specified @position.
  435. about: If a bind value already exists at the specified @position, it is replaced with the new one.
  436. End Rem
  437. Method setLong(position:Int, value:Long)
  438. bindValue(position, TDBLong.Set(value))
  439. End Method
  440. Rem
  441. bbdoc: Binds the Float @value at the specified @position.
  442. about: If a bind value already exists at the specified @position, it is replaced with the new one.
  443. End Rem
  444. Method setFloat(position:Int, value:Float)
  445. bindValue(position, TDBFloat.Set(value))
  446. End Method
  447. Rem
  448. bbdoc: Binds the Double @value at the specified @position.
  449. about: If a bind value already exists at the specified @position, it is replaced with the new one.
  450. End Rem
  451. Method setDouble(position:Int, value:Double)
  452. bindValue(position, TDBDouble.Set(value))
  453. End Method
  454. Rem
  455. bbdoc: Adds a new String bind value.
  456. about: The value is added to the end of the current list of bind values.
  457. End Rem
  458. Method addString(value:String)
  459. addBindValue(TDBString.Set(value))
  460. End Method
  461. Rem
  462. bbdoc: Adds a new Int bind value.
  463. about: The value is added to the end of the current list of bind values.
  464. End Rem
  465. Method addInt(value:Int)
  466. addBindValue(TDBInt.Set(value))
  467. End Method
  468. Rem
  469. bbdoc: Adds a new Long bind value.
  470. about: The value is added to the end of the current list of bind values.
  471. End Rem
  472. Method addLong(value:Long)
  473. addBindValue(TDBLong.Set(value))
  474. End Method
  475. Rem
  476. bbdoc: Adds a new Float bind value.
  477. about: The value is added to the end of the current list of bind values.
  478. End Rem
  479. Method addFloat(value:Float)
  480. addBindValue(TDBFloat.Set(value))
  481. End Method
  482. Rem
  483. bbdoc: Adds a new Double bind value.
  484. about: The value is added to the end of the current list of bind values.
  485. End Rem
  486. Method addDouble(value:Double)
  487. addBindValue(TDBDouble.Set(value))
  488. End Method
  489. Rem
  490. bbdoc: Clears the query bind values.
  491. End Rem
  492. Method clearBindValues()
  493. If resultSet Then
  494. resultSet.clearBindValues()
  495. End If
  496. End Method
  497. Rem
  498. bbdoc: Returns the id of the last inserted row.
  499. about: Results returned from this method on anything other than an insert on a table with
  500. an auto-incrementing field, are undetermined.
  501. End Rem
  502. Method lastInsertedId:Long()
  503. If resultSet Then
  504. Return resultSet.lastInsertedId()
  505. End If
  506. End Method
  507. Rem
  508. bbdoc: Returns the number of rows affected by the previously executed statement.
  509. about: Only really useful for inserts, updates and deletes. That is, results on selects are
  510. undetermined.
  511. End Rem
  512. Method rowsAffected:Int()
  513. If resultSet Then
  514. Return resultSet.rowsAffected()
  515. End If
  516. Return -1
  517. End Method
  518. ' "eachin" support
  519. Method ObjectEnumerator:TRowEnumerator()
  520. Local enumerator:TRowEnumerator = New TRowEnumerator
  521. enumerator.query = Self
  522. Return enumerator
  523. End Method
  524. Method free()
  525. If resultSet Then
  526. resultSet.free()
  527. resultSet = Null
  528. End If
  529. If conn Then
  530. conn = Null
  531. End If
  532. End Method
  533. Method Delete()
  534. free()
  535. End Method
  536. End Type
  537. ' "eachin" support
  538. Type TRowEnumerator
  539. Method HasNext:Int()
  540. Local result:Int = query.nextRow()
  541. If result Then
  542. record = query.rowRecord()
  543. If record._isEmptySet Then
  544. Return False
  545. End If
  546. End If
  547. Return result
  548. End Method
  549. Method NextObject:Object()
  550. Return record
  551. End Method
  552. Method Delete()
  553. query = Null
  554. record = Null
  555. End Method
  556. '***** PRIVATE *****
  557. Field query:TDatabaseQuery
  558. Field record:TQueryRecord
  559. End Type
  560. ' Implementation specific result set.
  561. ' You probably shouldn't be using any of this type's methods directly...
  562. Type TQueryResultSet
  563. Field conn:TDBConnection
  564. Field stmtHandle:Byte Ptr
  565. Field query:String
  566. Field _isActive:Int = False
  567. Field index:Int = SQL_BeforeFirstRow
  568. Field values:TDBType[]
  569. Field bindCount:Int
  570. Field boundValues:TDBType[]
  571. Field rec:TQueryRecord
  572. ' actual implementation in the driver
  573. Function Create:TQueryResultSet(db:TDBConnection, sql:String = Null) Abstract
  574. Method Init(db:TDBConnection, sql:String)
  575. conn = db
  576. query = sql
  577. End Method
  578. Method clearBindValues()
  579. If boundValues Then
  580. For Local i:Int = 0 Until boundValues.length
  581. If boundValues[i] Then
  582. boundValues[i].clear()
  583. 'boundValues[i] = Null
  584. End If
  585. Next
  586. 'boundValues = Null
  587. bindCount = 0
  588. End If
  589. If values Then
  590. For Local i:Int = 0 Until values.length
  591. If values[i] Then
  592. values[i].clear()
  593. 'values[i] = Null
  594. End If
  595. Next
  596. 'values = Null
  597. End If
  598. End Method
  599. Method clear()
  600. clearBindValues()
  601. End Method
  602. Method executeQuery:Int(statement:String) Abstract
  603. Method prepare:Int(statement:String) Abstract
  604. Method execute:Int() Abstract
  605. Method firstRow:Int() Abstract
  606. Method nextRow:Int() Abstract
  607. Method lastInsertedId:Long() Abstract
  608. Method rowsAffected:Int() Abstract
  609. Function dbTypeFromNative:Int(name:String, _type:Int = 0, _flags:Int = 0) Abstract
  610. Method rowRecord:TQueryRecord()
  611. If Not isActive() Then
  612. Return TQueryRecord.Create()
  613. End If
  614. Return rec
  615. End Method
  616. Method dataValue:TDBType(index:Int)
  617. If isActive() And rec And Not rec.isEmpty() Then
  618. If index >= 0 And index < rec.count() Then
  619. Return values[index]
  620. End If
  621. End If
  622. Return Null
  623. End Method
  624. Method rowIndex:Int()
  625. Return index
  626. End Method
  627. Method setRowIndex(i:Int)
  628. index = i
  629. End Method
  630. Method isActive:Int()
  631. Return _isActive
  632. End Method
  633. Method isValid:Int()
  634. Return index <> SQL_BeforeFirstRow And index <> SQL_AfterLastRow
  635. End Method
  636. Method resetValues(size:Int)
  637. If values Then
  638. For Local i:Int = 0 Until values.length
  639. If values[i] Then
  640. values[i].clear()
  641. End If
  642. Next
  643. values = Null
  644. End If
  645. values = New TDBType[size]
  646. End Method
  647. Method resetBindCount()
  648. bindCount = 0
  649. End Method
  650. Method addBindValue(value:TDBType)
  651. bindValue(bindCount, value)
  652. End Method
  653. Method bindValue(index:Int, value:TDBType)
  654. If Not boundValues Then
  655. boundValues = New TDBType[index + 1]
  656. End If
  657. ' extend the array if required
  658. If boundValues.length <= index Then
  659. boundValues = boundValues[..index + 1]
  660. End If
  661. ' amend bindCount if required
  662. If index > bindCount Then
  663. bindCount = index
  664. End If
  665. ' bindCount represents the length, so if last index matches, need to increment it.
  666. If index = bindCount Then
  667. bindCount :+ 1
  668. End If
  669. ' a value already exists here... remove it first.
  670. If boundValues[index] Then
  671. boundValues[index].clear()
  672. boundValues[index] = Null
  673. End If
  674. boundValues[index] = value
  675. End Method
  676. Method reset()
  677. End Method
  678. Method free()
  679. clear()
  680. values = Null
  681. boundValues = Null
  682. rec = Null
  683. End Method
  684. Method Delete()
  685. free()
  686. End Method
  687. End Type
  688. Rem
  689. bbdoc: A specific record (or row) for a result set.
  690. End Rem
  691. Type TQueryRecord
  692. Field _empty:Int = True
  693. Field _isEmptySet:Int = False
  694. Field fields:TQueryField[]
  695. Field fieldsMap:TMap
  696. Function Create:TQueryRecord()
  697. Local this:TQueryRecord = New TQueryRecord
  698. Return this
  699. End Function
  700. Rem
  701. bbdoc: Returns the #TQueryField object at @index.
  702. End Rem
  703. Method getField:TQueryField(index:Int)
  704. Return fields[index]
  705. End Method
  706. Rem
  707. bbdoc: Returns the named #TQueryField object.
  708. End Rem
  709. Method getFieldByName:TQueryField(name:String)
  710. Return TQueryField(fieldsMap.valueForKey(name))
  711. End Method
  712. Rem
  713. bbdoc: The index (position) of the field @name in the record.
  714. returns: The field index, or -1 if not found.
  715. End Rem
  716. Method indexOf:Int(name:String)
  717. For Local i:Int = 0 Until fields.length
  718. If name = fields[i].name Then
  719. Return i
  720. End If
  721. Next
  722. Return -1
  723. End Method
  724. Rem
  725. bbdoc: A count of the number of fields in the record.
  726. returns: The field count.
  727. End Rem
  728. Method count:Int()
  729. Return fields.length
  730. End Method
  731. Method isEmpty:Int()
  732. Return _empty
  733. End Method
  734. Method setValue(index:Int, value:Object)
  735. If index >= 0 And index < fields.length Then
  736. fields[index].setValue(value)
  737. End If
  738. End Method
  739. Rem
  740. bbdoc: Returns the value of the field at @index
  741. returns: a #TDBType value object or Null.
  742. End Rem
  743. Method value:TDBType(index:Int)
  744. If fields Then
  745. If index >= 0 And index < fields.length Then
  746. Return fields[index].value
  747. End If
  748. End If
  749. Return Null
  750. End Method
  751. Rem
  752. bbdoc: Returns the string value at @index
  753. about: The result is undetermined if the value at @index is not a string field.
  754. End Rem
  755. Method getString:String(index:Int)
  756. Local v:TDBType = value(index)
  757. If v Then
  758. Return v.getString()
  759. End If
  760. End Method
  761. Rem
  762. bbdoc: Returns the string value for the field @name
  763. about: The result is undetermined if the value at @name is not a string field.
  764. End Rem
  765. Method getStringByName:String(name:String)
  766. Local f:TQueryField = getFieldByName(name)
  767. If f And f.value Then
  768. Return f.value.getString()
  769. End If
  770. End Method
  771. Rem
  772. bbdoc: Returns the int value at @index
  773. about: The result is undetermined if the value at @index is not an int field.
  774. End Rem
  775. Method getInt:Int(index:Int)
  776. Local v:TDBType = value(index)
  777. If v Then
  778. Return v.getInt()
  779. End If
  780. End Method
  781. Rem
  782. bbdoc: Returns the int value for the field @name
  783. about: The result is undetermined if the value at @name is not an int field.
  784. End Rem
  785. Method getIntByName:Int(name:String)
  786. Local f:TQueryField = getFieldByName(name)
  787. If f And f.value Then
  788. Return f.value.getInt()
  789. End If
  790. End Method
  791. Rem
  792. bbdoc: Returns the long value at @index
  793. about: The result is undetermined if the value at @index is not a long field.
  794. End Rem
  795. Method getLong:Long(index:Int)
  796. Local v:TDBType = value(index)
  797. If v Then
  798. Return v.getLong()
  799. End If
  800. End Method
  801. Rem
  802. bbdoc: Returns the long value for the field @name
  803. about: The result is undetermined if the value at @name is not a long field.
  804. End Rem
  805. Method getLongByName:Long(name:String)
  806. Local f:TQueryField = getFieldByName(name)
  807. If f And f.value Then
  808. Return f.value.getLong()
  809. End If
  810. End Method
  811. Rem
  812. bbdoc: Returns the float value at @index
  813. about: The result is undetermined if the value at @index is not a float field.
  814. End Rem
  815. Method getFloat:Float(index:Int)
  816. Local v:TDBType = value(index)
  817. If v Then
  818. Return v.getFloat()
  819. End If
  820. End Method
  821. Rem
  822. bbdoc: Returns the float value for the field @name
  823. about: The result is undetermined if the value at @name is not a float field.
  824. End Rem
  825. Method getFloatByName:Float(name:String)
  826. Local f:TQueryField = getFieldByName(name)
  827. If f And f.value Then
  828. Return f.value.getFloat()
  829. End If
  830. End Method
  831. Rem
  832. bbdoc: Returns the double value at @index
  833. about: The result is undetermined if the value at @index is not a double field.
  834. End Rem
  835. Method getDouble:Double(index:Int)
  836. Local v:TDBType = value(index)
  837. If v Then
  838. Return v.getDouble()
  839. End If
  840. End Method
  841. Rem
  842. bbdoc: Returns the double value for the field @name
  843. about: The result is undetermined if the value at @name is not a double field.
  844. End Rem
  845. Method getDoubleByName:String(name:String)
  846. Local f:TQueryField = getFieldByName(name)
  847. If f And f.value Then
  848. Return f.value.getDouble()
  849. End If
  850. End Method
  851. Method clear()
  852. If fields Then
  853. For Local i:Int = 0 Until fields.length
  854. If fields[i] Then
  855. fields[i].clear()
  856. End If
  857. Next
  858. fields = Null
  859. fieldsMap.clear()
  860. fieldsMap = Null
  861. End If
  862. _empty = True
  863. _isEmptySet = True
  864. End Method
  865. Method Init(size:Int)
  866. If fields Then
  867. clear()
  868. End If
  869. fields = New TQueryField[size]
  870. fieldsMap = New TMap
  871. _empty = False
  872. _isEmptySet = False
  873. End Method
  874. Method setField(index:Int, theField:TQueryField)
  875. If fields Then
  876. fields[index] = theField
  877. fieldsMap.insert(theField.name, theField)
  878. End If
  879. End Method
  880. Method setIsEmptySet()
  881. _isEmptySet = True
  882. End Method
  883. Method Delete()
  884. Clear()
  885. End Method
  886. End Type
  887. Rem
  888. bbdoc: A field definition, including a value if part of a result set record.
  889. End Rem
  890. Type TQueryField
  891. Rem
  892. bbdoc: The field name
  893. End Rem
  894. Field name:String
  895. Rem
  896. bbdoc: Field type
  897. End Rem
  898. Field fType:Int
  899. Rem
  900. bbdoc: Field size.
  901. about: Dependent on the type of field. For a DBString field it would indicate number of characters.<br>
  902. A value of -1 means that this is undetermined by the database driver.
  903. End Rem
  904. Field length:Int = -1
  905. Rem
  906. bbdoc: Decimal precision.
  907. about: Only applicable for DBFloat and DBDouble field types.<br>
  908. A value of -1 means that this is undetermined by the database driver.
  909. End Rem
  910. Field precision:Int = -1
  911. Field value:TDBType
  912. Rem
  913. bbdoc: Whether this field is required or not.
  914. about: True if field is optional (can be NULL), False if field is required (NOT NULL).<br>
  915. A value of -1 means that this is undetermined by the database driver.
  916. End Rem
  917. Field nullable:Int = -1
  918. ' driver field type
  919. Field dtype:Int
  920. Field dflags:Int
  921. Function Create:TQueryField(name:String, fType:Int)
  922. Local this:TQueryField = New TQueryField
  923. this.name = name
  924. this.fType = fType
  925. Return this
  926. End Function
  927. Method clear()
  928. value = Null
  929. End Method
  930. Method setValue(v:Object)
  931. If TDBType(v) Then
  932. value = TDBType(v)
  933. End If
  934. End Method
  935. Method Delete()
  936. clear()
  937. End Method
  938. End Type
  939. Rem
  940. bbdoc: Contains details of the last error from the driver.
  941. End Rem
  942. Type TDatabaseError
  943. Const ERROR_NONE:Int = 0
  944. Const ERROR_TRANSACTION:Int = 1
  945. Const ERROR_CONNECTION:Int = 2
  946. Const ERROR_STATEMENT:Int = 3
  947. Const ERROR_UNKOWN:Int = 4
  948. Field db:TDBConnection
  949. Rem
  950. bbdoc: The error text.
  951. End Rem
  952. Field error:String
  953. Rem
  954. bbdoc: The type of error.
  955. about: Can be one of ERROR_NONE, ERROR_TRANSACTION, ERROR_CONNECTION, ERROR_STATEMENT or ERROR_UNKOWN.
  956. End Rem
  957. Field errorType:Int
  958. Rem
  959. bbdoc: The "native" error value.
  960. about: Refer to the specific database documentation for details.
  961. End Rem
  962. Field errorValue:Int
  963. Function Create:TDatabaseError(db:TDBConnection, error:String, append:String = Null, errorType:Int, errorValue:Int)
  964. Local this:TDatabaseError = New TDatabaseError
  965. this.db = db
  966. this.error = error
  967. If append Then
  968. this.error:+ " : " + append
  969. End If
  970. If errorValue Then
  971. this.error:+ " (" + errorValue + ") "
  972. End If
  973. this.errorValue = errorValue
  974. this.errorType = errorType
  975. Return this
  976. End Function
  977. Method reset()
  978. error = Null
  979. errorValue = 0
  980. errorType = 0
  981. End Method
  982. Rem
  983. bbdoc: Determines if there is an outstanding error.
  984. returns: True if this represents an error.
  985. End Rem
  986. Method isSet:Int()
  987. Return error <> Null And error.length > 0
  988. End Method
  989. Rem
  990. bbdoc: Returns the full error details.
  991. End Rem
  992. Method toString:String()
  993. Return "(" + errorType + ") " + error + " : " + nativeError()
  994. End Method
  995. Method nativeError:String()
  996. If db Then
  997. Return db.nativeErrorMessage(errorValue)
  998. End If
  999. End Method
  1000. Method Delete()
  1001. db = Null
  1002. End Method
  1003. End Type
  1004. Rem
  1005. bbdoc:
  1006. End Rem
  1007. Type TDBTable
  1008. Rem
  1009. bbdoc:
  1010. End Rem
  1011. Field name:String
  1012. Rem
  1013. bbdoc:
  1014. End Rem
  1015. Field columns:TDBColumn[]
  1016. Rem
  1017. bbdoc:
  1018. End Rem
  1019. Field ddl:String
  1020. Method SetCountColumns(count:Int)
  1021. columns = New TDBColumn[count]
  1022. End Method
  1023. Method SetColumn(index:Int, col:TDBColumn)
  1024. columns[index] = col
  1025. End Method
  1026. End Type
  1027. Rem
  1028. bbdoc:
  1029. End Rem
  1030. Type TDBColumn
  1031. Rem
  1032. bbdoc:
  1033. End Rem
  1034. Field name:String
  1035. Rem
  1036. bbdoc:
  1037. End Rem
  1038. Field dbType:Int
  1039. Rem
  1040. bbdoc:
  1041. End Rem
  1042. Field nullable:Int
  1043. Rem
  1044. bbdoc:
  1045. End Rem
  1046. Field defaultValue:TDBType
  1047. Function Create:TDBColumn(name:String, dbType:Int, nullable:Int, defaultValue:TDBType)
  1048. Local this:TDBColumn = New TDBColumn
  1049. this.name = name
  1050. this.dbType = dbType
  1051. this.nullable = nullable
  1052. this.defaultValue = defaultValue
  1053. Return this
  1054. End Function
  1055. End Type
  1056. Extern
  1057. Function _strlen:Int(s:Byte Ptr) = "strlen"
  1058. End Extern
  1059. ' Convert from Max to UTF8
  1060. Function convertISO8859toUTF8:String(text:String)
  1061. If Not text Then
  1062. Return ""
  1063. End If
  1064. Local l:Int = text.length
  1065. If l = 0 Then
  1066. Return ""
  1067. End If
  1068. Local count:Int = 0
  1069. Local s:Byte[] = New Byte[l * 3]
  1070. For Local i:Int = 0 Until l
  1071. Local char:Int = text[i]
  1072. If char < 128 Then
  1073. s[count] = char
  1074. count:+ 1
  1075. Continue
  1076. Else If char<2048
  1077. s[count] = char/64 | 192
  1078. count:+ 1
  1079. s[count] = char Mod 64 | 128
  1080. count:+ 1
  1081. Continue
  1082. Else
  1083. s[count] = char/4096 | 224
  1084. count:+ 1
  1085. s[count] = char/64 Mod 64 | 128
  1086. count:+ 1
  1087. s[count] = char Mod 64 | 128
  1088. count:+ 1
  1089. Continue
  1090. EndIf
  1091. Next
  1092. Return String.fromBytes(s, count)
  1093. End Function
  1094. ' Convert from UTF8 to Max
  1095. Function convertUTF8toISO8859:String(s:Byte Ptr)
  1096. Local l:Int = _strlen(s)
  1097. Local b:Short[] = New Short[l]
  1098. Local bc:Int = -1
  1099. Local c:Int
  1100. Local d:Int
  1101. Local e:Int
  1102. For Local i:Int = 0 Until l
  1103. bc:+1
  1104. c = s[i]
  1105. If c<128
  1106. b[bc] = c
  1107. Continue
  1108. End If
  1109. i:+1
  1110. d=s[i]
  1111. If c<224
  1112. b[bc] = (c-192)*64+(d-128)
  1113. Continue
  1114. End If
  1115. i:+1
  1116. e = s[i]
  1117. If c < 240
  1118. b[bc] = (c-224)*4096+(d-128)*64+(e-128)
  1119. If b[bc] = 8233 Then
  1120. b[bc] = 10
  1121. End If
  1122. Continue
  1123. End If
  1124. Next
  1125. Return String.fromshorts(b, bc + 1)
  1126. End Function
  1127. Function sizedUTF8toISO8859:String(s:Byte Ptr, size:Int)
  1128. Local l:Int = size
  1129. Local b:Short[] = New Short[l]
  1130. Local bc:Int = -1
  1131. Local c:Int
  1132. Local d:Int
  1133. Local e:Int
  1134. For Local i:Int = 0 Until l
  1135. c = s[i]
  1136. If c = 0 Continue
  1137. bc:+1
  1138. If c<128
  1139. b[bc] = c
  1140. Continue
  1141. End If
  1142. i:+1
  1143. d=s[i]
  1144. If c<224
  1145. b[bc] = (c-192)*64+(d-128)
  1146. Continue
  1147. End If
  1148. i:+1
  1149. e = s[i]
  1150. If c < 240
  1151. b[bc] = (c-224)*4096+(d-128)*64+(e-128)
  1152. If b[bc] = 8233 Then
  1153. b[bc] = 10
  1154. End If
  1155. Continue
  1156. End If
  1157. Next
  1158. Return String.fromshorts(b, bc + 1)
  1159. End Function
  1160. Type TDatabaseLoader
  1161. Field _type:String
  1162. Field _succ:TDatabaseLoader
  1163. Method LoadDatabase:TDBConnection( dbname:String = Null, host:String = Null, ..
  1164. port:Int = Null, user:String = Null, password:String = Null, ..
  1165. server:String = Null, options:String = Null ) Abstract
  1166. End Type
  1167. Private
  1168. Global _loaders:TDatabaseloader
  1169. Public
  1170. Function AddDatabaseLoader( loader:TDatabaseLoader )
  1171. If loader._succ Return
  1172. loader._succ = _loaders
  1173. _loaders = loader
  1174. End Function
  1175. Rem
  1176. bbdoc: Loads a database engine of the specific @dbType.
  1177. about: Optionally, the function takes a set of parameters that can be used to connect to the
  1178. database at load time.<br>
  1179. See the specific database module documentation for correct @dbType name.
  1180. End Rem
  1181. Function LoadDatabase:TDBConnection( dbType:String, dbname:String = Null, host:String = Null, ..
  1182. port:Int = Null, user:String = Null, password:String = Null, server:String = Null, ..
  1183. options:String = Null )
  1184. Local loader:TDatabaseLoader = _loaders
  1185. While loader
  1186. If loader._type = dbType Then
  1187. Local db:TDBConnection = loader.LoadDatabase(dbname, host, port, user, password, server, options)
  1188. If db Return db
  1189. End If
  1190. loader = loader._succ
  1191. Wend
  1192. End Function