├── .gitignore ├── LICENSE ├── README.md ├── config.nims ├── demo ├── demo.nim ├── democredentials.nim ├── demoscript.sql ├── demosql.nim └── sqlplusdemo.nim ├── examples ├── hr_pipelined_tablefunc.nim ├── hr_select.nim └── ora_credentials.nim ├── nimodpi.nimble └── src ├── db_oracle.nim ├── nimodpi.nim ├── nimtype_to_odpi.nim ├── odpi_obj2string.nim ├── odpi_to_nimtype.nim └── setfetchtypes.nim /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | build/ 3 | *.exe 4 | *.dot 5 | *.deps 6 | democredentials.nim -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Michael Krauter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nimodpi 2 | Oracle ODPI-C wrapper for Nim 3 | 4 | ## [Oracle ODPI-C Documentation](https://oracle.github.io/odpi/) 5 | 6 | ### dependencies 7 | - Oracle Instant Client (see ODPI-C Documentation for details) 8 | - [nimterop](https://github.com/nimterop/nimterop) 9 | 10 | ### announcement 11 | unfortunately nimterop does not compile with the latest nim release (2.x). 12 | Nimterop seems stale; due to that we decided to switch to [futhark](https://github.com/PMunch/futhark). 13 | The actual quickfix present is stay at nim version 1.6. 14 | 15 | ### how to install 16 | clone this project directly or use "nimble install nimodpi". 17 | then import the module "db_oracle" within your project. 18 | the dependent ODPI-C source is directly included into your project (via nimterop) 19 | 20 | See the "isMainModule" section at the end of the module for some examples. 21 | 22 | Besides the abstraction layer you can also consume the raw ODPI-C API if something is 23 | missing ( or PR me your solution ). 24 | 25 | See /demo/demo.nim for some direct ODPI-C examples. 26 | The subdir /demo also contains an example how sqlplus could be utilised for script execution. 27 | 28 | ### Remarks 29 | upgraded to Nim Compiler Version devel (should also work with latest release), ODPI-C 4.3.0 and nimterop 0.6.11. The ODPI-C version is hardcoded within the static section of nimodpi.nim (nimterop control file) - don't forget to flush nimcache when changing the version between compile runs. 30 | The NIM-API could be subject of change. 31 | 32 | ### Oracle XE installation 33 | For testing just install a local copy of the oracle xe: 34 | https://www.oracle.com/database/technologies/appdev/xe.html 35 | 36 | The oracle instant client is already included. 37 | 38 | Tests only against windows 10 OS - if you face problems while installing xe onto 39 | your machine: the installer logs everything under the program files directory within the subdirectory 40 | /Oracle/Inventory/logs. Before installing make sure a third party virus scanner is disabled. 41 | (I faced no problems with the windows defender). 42 | 43 | My database was created with the nls_character set "AL32UTF8" 44 | 45 | To check your database characterset you could query for: 46 | 47 | ```PLSQL 48 | select DECODE(parameter, 'NLS_CHARACTERSET', 'CHARACTER SET', 49 | 'NLS_LANGUAGE', 'LANGUAGE', 50 | 'NLS_TERRITORY', 'TERRITORY') name, 51 | value from v$nls_parameters 52 | WHERE parameter IN ( 'NLS_CHARACTERSET', 'NLS_LANGUAGE', 'NLS_TERRITORY') 53 | / 54 | ``` 55 | 56 | If you like to browse the schemas use Oracle SQL Developer for instance. 57 | Future examples will be based on the HR schema. 58 | This user is locked by default so the examples will only work if you use 59 | sysdba for the connecting user. 60 | 61 | ### demo 62 | before running the demo, adjust "/demo/democredentials.nim" (login and connection_string) 63 | for the database you like to connect to. No ddl is executed. 64 | 65 | run the raw ODPI-C demo with "nimble demo". 66 | 67 | run the nim demo with "nimble db_oracle". 68 | this demo executes some DDL and performs a cleanup. 69 | 70 | ### Todo 71 | direct bindings almost completed except SODA/BLOB/VARRAY; 72 | the nimish abstraction layer (db_oracle) is functional but more examples needed. 73 | next steps would be provide documented examples. 74 | 75 | Comments, bug reports and PR´s appreciated. 76 | -------------------------------------------------------------------------------- /config.nims: -------------------------------------------------------------------------------- 1 | import ospaths, strutils 2 | 3 | task odpic_demo, "run odpi-c demo": 4 | withDir thisDir(): 5 | switch("run") 6 | switch("app","console") 7 | setCommand "c", "demo/demo.nim" 8 | 9 | task oracle_demo, "run db_oracle API examples (with ddl)": 10 | withDir thisDir(): 11 | switch("run") 12 | switch("app","console") 13 | setCommand "c", "src/db_oracle.nim" -------------------------------------------------------------------------------- /demo/demo.nim: -------------------------------------------------------------------------------- 1 | import ../src/nimodpi 2 | import strutils 3 | import os 4 | import times 5 | import result 6 | 7 | # testsetup: win10,instantclient_19_3_x64 8 | # target: 12.2.0.1 PDB and 12.1.0.2(exadata,1n) 9 | # compiler: gcc version 5.1.0 (tdm64-1) 10 | # TODO: testing against timesten 11 | 12 | #[ 13 | this demo (it consumes ODPI-C directly) contains three examples 14 | (just select stmt - no ddl executed - statements can be found in demosql.nim ) 15 | - setting clients encoding 16 | - simple fetch 17 | - fetch with fetchRows (additional types double,raw,timestamp,big_number as string) 18 | - ref_cursor example 19 | ]# 20 | 21 | const 22 | nlsLang: string = "WE8ISO8859P15" 23 | 24 | include democredentials 25 | include demosql 26 | 27 | 28 | proc `$`* (p: var dpiTimestamp): string = 29 | ## string representation of a timestamp column 30 | "dpiTimestamp: year:" & $p.year & " month:" & $p.month & " day:" & $p.day & 31 | " hour: " & $p.hour & " minute:" & $p.minute & " second:" & $p.second & 32 | " fsecond:" & $p.fsecond & 33 | " tzHOffset:" & $p.tzHourOffset & " tzMinOffset:" & $p.tzMinuteOffset 34 | 35 | proc toDateTime(p : ptr dpiTimestamp ) : DateTime = 36 | ## dpiTimestamp to DateTime 37 | let utcoffset : int = p.tzHourOffset*3600.int + p.tzMinuteOffset*60 38 | proc utcTzInfo(time: Time): ZonedTime = 39 | ZonedTime(utcOffset: utcoffset, isDst: false, time: time) 40 | initDateTime(p.day,Month(p.month),p.year,p.hour,p.minute,p.second,p.fsecond, 41 | newTimezone("Etc/UTC", utcTzInfo, utcTzInfo)) 42 | 43 | proc `$`*(p: var dpiStmtInfo): string = 44 | ## string repr of a statementInfo obj 45 | "dpiStatementInfo: isQuery " & $p.isQuery & " isPlSql: " & $p.isPLSQL & 46 | " isDDL: " & $p.isDDL & " isDML: " & $p.isDML & " isReturning: " & 47 | $p.isReturning & 48 | " " & $DpiStmtType(p.statementType) 49 | 50 | 51 | template `[]`(data: ptr dpiData, idx: int): ptr dpiData = 52 | ## accesses the cell(row) of the column by index 53 | cast[ptr dpiData]((cast[int](data)) + (sizeof(dpiData)*idx)) 54 | 55 | template toNimString (data: ptr dpiData): untyped = 56 | var result: string = newString(data.value.asBytes.length) 57 | copyMem(addr(result[0]), data.value.asBytes.ptr, result.len) 58 | result 59 | 60 | template onSuccessExecute(context: ptr dpiContext, toprobe: untyped, 61 | body: untyped) = 62 | ## template to wrap the boilerplate errorinfo code 63 | var err: dpiErrorInfo 64 | if toprobe < DpiResult.SUCCESS.ord: 65 | dpiContext_getError(context, err.addr) 66 | echo $err 67 | else: 68 | body 69 | 70 | type 71 | ColumnBuffer = object 72 | colVar: ptr dpiVar 73 | buffer: ptr dpiData 74 | dpiresult: DpiResult # contains errorcode and msg 75 | 76 | type 77 | PreparedStatement = object 78 | nativeStatement: ptr dpiStmt 79 | statementInfo: dpiStmtInfo 80 | # obj from int dpiStmt_getInfo(dpiStmt *stmt, dpiStmtInfo *info 81 | columnDataTypes: seq[dpiQueryInfo] 82 | columnBuffers: seq[ColumnBuffer] 83 | fetchArraySize: uint32 84 | columnCount: uint32 85 | 86 | template `[]`(pstmt: var PreparedStatement, idx: int): ptr dpiData = 87 | ## access the columnBuffer by index 88 | pstmt.columnBuffers[idx].buffer 89 | 90 | proc `$`*(p: var PreparedStatement): string = 91 | "preparedStmt: colcount: " & $p.columnCount & " " & $p.columnDataTypes 92 | 93 | template newVar(conn: ptr dpiConn, otype: DpiOracleType, 94 | ntype: DpiNativeCType, arrSize: int, colLength: uint32, 95 | column: ptr ColumnBuffer) = 96 | ## initialises a new dpiVar and populates the ColumnBuffer-type 97 | column.dpiresult = DpiResult(dpiConn_newVar(conn, cast[dpiOracleTypeNum]( 98 | otype.ord), # db coltype 99 | cast[dpiNativeTypeNum](ntype.ord), # client coltype 100 | arrSize.uint32, # max array size 101 | colLength, # size of buffer 102 | 0, # sizeIsBytes 103 | 0, # isArray 104 | nil, # objType 105 | column.colVar.addr, # ptr to the variable 106 | column.buffer.addr # ptr to buffer array 107 | )) 108 | 109 | template releaseBuffer(prepStmt: var PreparedStatement) = 110 | ## releases the internal buffers. to reinit initMetadataAndAllocBuffersAfterExecute must 111 | ## be used 112 | for i in countup(0, prepStmt.columnBuffers.len-1): 113 | discard dpiVar_release(prepStmt.columnBuffers[i].colVar) 114 | 115 | 116 | template initMetadataAndAllocBuffersAfterExecute(ctx: ptr dpiContext, 117 | conn: ptr dpiConn, prepStmt: var PreparedStatement) = 118 | ## fetches the statements metadata after execute. 119 | ## if the statement returns any rows the internal buffer 120 | ## is initialised according to the internal fetchArraySize. 121 | ## TODO: handle refcursor differently 122 | ## (we can not introspect the refcursor till it's fetched) 123 | ## TODO: if numeric exceeds 64bit handle as string 124 | discard dpiStmt_getFetchArraySize(prepStmt.nativeStatement, 125 | prepStmt.fetchArraySize.addr) 126 | echo "farrsize : " & $prepStmt.fetchArraySize 127 | discard dpiStmt_getNumQueryColumns(prepStmt.nativeStatement, 128 | prepStmt.columnCount.addr) 129 | if dpiStmt_getInfo(prepstatement.nativeStatement, 130 | prepStmt.statementInfo.addr) == DpiResult.SUCCESS.ord: 131 | echo $prepStmt.statementInfo 132 | if prepStmt.statementInfo.isQuery == 1.cint: 133 | prepStmt.columnDataTypes = newSeq[dpiQueryInfo](prepStmt.columnCount) 134 | prepStmt.columnBuffers = newSeq[ColumnBuffer](prepStmt.columnCount) 135 | 136 | var colinfo: dpiQueryInfo 137 | 138 | #introspect resultset 139 | for i in countup(1, prepStmt.columnCount.int): 140 | if dpiStmt_getQueryInfo(prepStmt.nativeStatement, i.uint32, 141 | colinfo.addr) < DpiResult.SUCCESS.ord: 142 | echo "error_introspect_column " & $i 143 | else: 144 | prepStmt.columnDataTypes[i-1] = colinfo 145 | echo $DpiOracleType(colinfo.typeinfo.oracleTypeNum) & 146 | " clientsize: " & $colinfo.typeinfo.clientSizeInBytes.uint32 147 | 148 | for i in countup(0, prepStmt.columnDataTypes.len-1): 149 | var cbuf: ColumnBuffer 150 | let otype = DpiOracleType(prepStmt.columnDataTypes[ 151 | i].typeInfo.oracleTypeNum) 152 | let ntype = DpiNativeCType(prepStmt.columnDataTypes[ 153 | i].typeInfo.defaultNativeTypeNum) 154 | echo $otype & " " & $ntype 155 | echo "oci_type_code " & $prepStmt.columnDataTypes[i].typeInfo.ociTypeCode 156 | # TODO: construct the dpiVars out of dpiQueryInfo 157 | var size: uint32 158 | case ntype 159 | of INT64, UINT64, FLOAT, Double, 160 | TIMESTAMP, INTERVAL_DS, 161 | INTERVAL_YM, BOOLEAN, ROWID: 162 | size = 1 163 | echo "scale: " & $prepStmt.columnDataTypes[i].typeInfo.scale 164 | echo " precision : " & $prepStmt.columnDataTypes[ 165 | i].typeInfo.precision 166 | of BYTES: 167 | size = prepStmt.columnDataTypes[i].typeInfo.sizeInChars 168 | if size == 0: 169 | # non character data 170 | size = prepStmt.columnDataTypes[i].typeInfo.clientSizeInBytes 171 | else: 172 | echo "unsupportedType at idx: " & $i 173 | # TODO: proper error handling 174 | break 175 | if i == 4: # bignum column 176 | echo "size_col_4 chars " & $prepStmt.columnDataTypes[i].typeInfo.sizeInChars 177 | echo "size_col_4 clientsize " & $prepStmt.columnDataTypes[i].typeInfo.clientSizeInBytes 178 | echo "size_col_4 dbsize " & $prepStmt.columnDataTypes[i].typeInfo.dbSizeInBytes 179 | # these values are only populated if varchar2 type. introspection is limited here 180 | # and it´s not possible to detect big numbers 181 | newVar(conn, otype, DpiNativeCType.BYTES, 182 | (prepStmt.fetchArraySize).int,1, cbuf.addr) 183 | else: 184 | newVar(conn, otype, ntype, (prepStmt.fetchArraySize).int, size, 185 | cbuf.addr) 186 | # TODO remove template newVar 187 | if hasError(cbuf): 188 | echo "error_newvar at column: " & $(i+1) 189 | break 190 | else: 191 | prepStmt.columnBuffers[i] = cbuf 192 | 193 | for i in countup(0, prepStmt.columnBuffers.len-1): 194 | # bind buffer to statement 195 | if dpiStmt_define(prepStmt.nativeStatement, (i+1).uint32, 196 | prepStmt.columnBuffers[i].colVar) == DpiResult.FAILURE.ord: 197 | var err: dpiErrorInfo 198 | dpiContext_getError(ctx, err.addr) 199 | echo $err 200 | break 201 | 202 | template isNull(dat: ptr dpiData): bool = 203 | if dat.isNull == 1.cint: 204 | true 205 | else: 206 | false 207 | 208 | template hasError(colvar: var ColumnBuffer): bool = 209 | if colvar.dpiresult < DpiResult.SUCCESS: 210 | true 211 | else: 212 | false 213 | 214 | template hasError(context: ptr dpiContext, toprobe: untyped): bool = 215 | if toprobe < DpiResult.SUCCESS.ord: 216 | true 217 | else: 218 | false 219 | 220 | var context: ptr dpiContext 221 | var errorinfo: dpiErrorInfo 222 | var versionInfo: dpiVersionInfo 223 | var commonParams: dpiCommonCreateParams 224 | var connectionParams : dpiConnCreateParams 225 | # globals 226 | 227 | let errno = dpiContext_createWithParams(DPI_MAJOR_VERSION, DPI_MINOR_VERSION, nil, addr( 228 | context), addr(errorinfo)) 229 | if errno == DpiResult.SUCCESS.ord: 230 | echo "context_created" 231 | discard dpiContext_initCommonCreateParams(context, commonParams.addr) 232 | discard dpiContext_initConnCreateParams(context,connectionParams.addr) 233 | connectionParams.authMode = DpiAuthMode.SYSDBA.ord 234 | commonParams.encoding = nlsLang 235 | else: 236 | echo "unable to create context: " & $errorinfo 237 | 238 | discard dpiContext_getClientVersion(context, addr(versionInfo)) 239 | echo "client_version: " & $versionInfo.versionNum 240 | 241 | var conn: ptr dpiConn 242 | 243 | # connectionstrings could be from tns definitions (tnsnames.ora) or provided as param 244 | onSuccessExecute(context, dpiConn_create(context,oracleuser,oracleuser.len, 245 | pw,pw.len, connString, connString.len, commonParams.addr,connectionParams.addr, addr(conn))): 246 | echo "connection created" 247 | 248 | var prepStatement: ptr dpiStmt 249 | var numCols: uint32 250 | var numRows: uint64 251 | 252 | let query = "select 'hello äöü ' from dual ".cstring 253 | let nlsQuery = "ALTER SESSION SET NLS_NUMERIC_CHARACTERS = '. ' ".cstring 254 | let scrollAble = 0.cint 255 | let tagLength = 0.uint32 256 | 257 | onSuccessExecute(context, dpiConn_prepareStmt(conn, scrollAble,nlsQuery, 258 | nlsQuery.len.uint32, nil, tagLength, 259 | prepStatement.addr)): 260 | 261 | onSuccessExecute(context, dpiStmt_execute(prepStatement, 262 | DpiModeExec.DEFAULTMODE.ord, numCols.addr)): 263 | discard 264 | discard dpiStmt_release(prepStatement) 265 | 266 | # simple select example 267 | onSuccessExecute(context, dpiConn_prepareStmt(conn, scrollAble, query, 268 | query.len.uint32, nil, tagLength, 269 | prepStatement.addr)): 270 | 271 | onSuccessExecute(context, dpiStmt_execute(prepStatement, 272 | DpiModeExec.DEFAULTMODE.ord, numCols.addr)): 273 | echo "num_cols_returned: " & $numCols 274 | # fetch the row to client 275 | var bufferRowIndex: uint32 276 | var found: cint 277 | 278 | onSuccessExecute(context, dpiStmt_fetch(prepStatement, found.addr, 279 | bufferRowIndex.addr)): 280 | # fetch one row 281 | var stringColVal: ptr dpiData 282 | var nativeTypeNum: dpiNativeTypeNum 283 | 284 | onSuccessExecute(context, dpiStmt_getQueryValue(prepStatement, 1, 285 | nativeTypeNum.addr, stringColVal.addr)): 286 | if stringColVal.isNull == 0: 287 | echo "encoding: " & $stringColVal.value.asBytes.encoding 288 | echo "colval: " & toNimString(stringColVal) 289 | 290 | var queryInfo: dpiQueryInfo 291 | 292 | onSuccessExecute(context, dpiStmt_getQueryInfo(prepStatement, 1, 293 | queryInfo.addr)): 294 | echo "dbtype: " & $DpiOracleType( 295 | queryInfo.typeInfo.oracleTypeNum) & 296 | " size_in_chars " & $queryInfo.typeInfo.sizeInChars 297 | 298 | else: 299 | echo "val_is_null" 300 | 301 | discard dpiStmt_getRowCount(prepStatement, numRows.addr) 302 | echo "prepStatement: num_rows_fetched: " & $numRows 303 | 304 | 305 | 306 | var prepStatement2: ptr dpiStmt 307 | 308 | onSuccessExecute(context, 309 | dpiConn_prepareStmt(conn, scrollAble, testTypesSql, 310 | testTypesSql.len.uint32, 311 | nil, tagLength, prepStatement2.addr)): 312 | #eval fetch_array_size 313 | var arrSize: uint32 314 | discard dpiStmt_getFetchArraySize(prepStatement2, arrSize.addr) 315 | echo "fetch_array_size: " & $arrSize 316 | # int dpiStmt_setFetchArraySize(dpiStmt *stmt, uint32_t arraySize) 317 | # example fetching multiple rows 318 | # metadata can be retrieved with dpiStmt_getQueryInfo() after execute. 319 | onSuccessExecute(context, dpiStmt_execute(prepStatement2, 320 | DpiModeExec.DEFAULTMODE.ord, numCols.addr)): 321 | 322 | var prepStatement: PreparedStatement 323 | prepStatement.nativeStatement = prepStatement2 324 | 325 | initMetadataAndAllocBuffersAfterExecute(context, conn, prepstatement) 326 | echo "num_query_cols " & $prepStatement.columnCount 327 | 328 | var moreRows: cint 329 | var bufferRowIndex: uint32 330 | var rowsFetched: uint32 331 | 332 | onSuccessExecute(context, dpiStmt_fetchRows(prepStatement2, 333 | 50.uint32, 334 | bufferRowIndex.addr, 335 | rowsFetched.addr, 336 | moreRows.addr)): 337 | # fetchRows gives you a spreadsheet-like access to the resultset 338 | echo "prepStatement2: num_rows_fetched: " & $rowsFetched 339 | 340 | echo "col1: " & toNimString(prepStatement[0][0]) # column/row 341 | echo "col2: " & $dpiData_getDouble(prepStatement[1][0]) 342 | echo "col1: " & toNimString(prepStatement[0][1]) 343 | echo "col2: " & $dpiData_getDouble(prepStatement[1][1]) 344 | # raw column 345 | echo "col3: " & toHex(toNimString(prepStatement[2][0])) 346 | echo "col3: " & toHex(toNimString(prepStatement[2][1])) 347 | 348 | # tstamp column 349 | let tstamp: ptr dpiTimestamp = cast[ptr dpiTimestamp](prepStatement[3][ 350 | 0].value.asTimestamp.addr) 351 | echo " dateTime : " & $toDateTime(tstamp) 352 | 353 | if isNull(prepStatement[3][1]): 354 | echo "col4: col missing value " 355 | else: 356 | var tstamp2 : ptr dpiTimestamp = cast[ptr dpiTimestamp](prepStatement[3][1].value.asTimestamp.addr) 357 | echo "col4: " & $toDateTime(tstamp2) 358 | echo "col5: " & toNimString(prepStatement[4][0]) 359 | echo "col5: " & toNimString(prepStatement[4][1]) 360 | # output big_numbers as string 361 | # TODO: probe if it's a big_number 362 | 363 | releaseBuffer(prepStatement) 364 | 365 | discard dpiStmt_release(prepStatement2) 366 | discard dpiStmt_release(prepStatement) 367 | 368 | # refCursors example taken out of DemoRefCursors.c 369 | echo "executing ref_cursor example " 370 | onSuccessExecute(context, 371 | dpiConn_prepareStmt(conn, scrollAble, testRefCursor, 372 | testRefCursor.len.uint32, 373 | nil, tagLength, prepStatement.addr)): 374 | 375 | var refCursorCol: ColumnBuffer 376 | 377 | var statementInfo: dpiStmtInfo 378 | if dpiStmt_getInfo(prepStatement, statementInfo.addr) == 379 | DpiResult.SUCCESS.ord: 380 | echo $statementInfo 381 | var colcount: uint32 382 | discard dpiStmt_getNumQueryColumns(prepStatement, colcount.addr) 383 | var colinfo: dpiQueryInfo 384 | 385 | #introspect resultset 386 | for i in countup(1, colcount.int): 387 | if dpiStmt_getQueryInfo(prepStatement, i.uint32, 388 | colinfo.addr) < DpiResult.SUCCESS.ord: 389 | echo "error_introspect_column " & $i 390 | else: 391 | echo $DpiOracleType(colinfo.typeinfo.oracleTypeNum) & 392 | " clientsize: " & $colinfo.typeinfo.clientSizeInBytes.uint32 393 | 394 | newVar(conn, DpiOracleType.OTSTMT, DpiNativeCType.STMT, 1, 0, 395 | refCursorCol.addr) 396 | if hasError(refCursorCol): 397 | echo "error_init_refcursor_column" 398 | 399 | if dpiStmt_bindByPos(prepStatement, 1, 400 | refCursorCol.colvar) < DpiResult.SUCCESS.ord: 401 | echo "error_binding_refcursor_param" 402 | 403 | onSuccessExecute(context, dpiStmt_execute(prepStatement, 404 | DpiModeExec.DEFAULTMODE.ord, numCols.addr)): 405 | # introspect refCursor 406 | var colcount: uint32 407 | discard dpiStmt_getNumQueryColumns(prepStatement, colcount.addr) 408 | echo "refcursor: num_query_cols " & $colcount 409 | var colinfo: dpiQueryInfo 410 | var typeinfo: dpiDataTypeInfo 411 | var statementInfo: dpiStmtInfo 412 | if dpiStmt_getInfo(prepStatement, statementInfo.addr) == 413 | DpiResult.SUCCESS.ord: 414 | echo $statementInfo 415 | 416 | #introspect resultset 417 | for i in countup(1, colcount.int): 418 | if dpiStmt_getQueryInfo(prepStatement, i.uint32, 419 | colinfo.addr) < DpiResult.SUCCESS.ord: 420 | echo "error_introspect_column " & $i 421 | else: 422 | echo $DpiOracleType(colinfo.typeinfo.oracleTypeNum) & 423 | " clientsize: " & $colinfo.typeinfo.clientSizeInBytes.uint32 424 | # a refcursor could not be introspected by the statementInfo 425 | # -> we need the metadata (refcursor) from the caller 426 | # TODO: in this case the refcursor is opened by the client 427 | # - but a procedure could also return a refCursor (is ref_cursor) 428 | # example needed 429 | discard dpiStmt_release(prepStatement) 430 | 431 | prepStatement = refCursorCol.buffer.value.asStmt # fetch ref_cursor 432 | 433 | if dpiStmt_getInfo(prepStatement, statementInfo.addr) == 434 | DpiResult.SUCCESS.ord: 435 | echo $statementInfo 436 | 437 | discard dpiStmt_getNumQueryColumns(prepStatement, colcount.addr) 438 | echo "refcursor_columns: " & $colcount 439 | #introspect refcursor 440 | for i in countup(1, colcount.int): 441 | if dpiStmt_getQueryInfo(prepStatement, i.uint32, 442 | colinfo.addr) < DpiResult.SUCCESS.ord: 443 | echo "error_introspect_column " & $i 444 | else: 445 | echo $DpiOracleType(colinfo.typeinfo.oracleTypeNum) & 446 | " clientsize: " & $colinfo.typeinfo.clientSizeInBytes.uint32 447 | 448 | 449 | # fetch cursor content 450 | var bufferRowIndex: uint32 451 | var found: cint 452 | var stringColVal: ptr dpiData 453 | var nativeTypeNum: dpiNativeTypeNum 454 | 455 | for i in countup(1, 2): 456 | onSuccessExecute(context, 457 | dpiStmt_fetch(prepStatement, found.addr, 458 | bufferRowIndex.addr)): 459 | if found != 1: 460 | break 461 | onSuccessExecute(context, 462 | dpiStmt_getQueryValue(prepStatement, 1, 463 | nativeTypeNum.addr, stringColVal.addr)): 464 | echo $nativeTypeNum 465 | if stringColVal.isNull == 0: 466 | echo "encoding: " & $stringColVal.value.asBytes.encoding 467 | echo "ref_cursor_val: " & toNimString(stringColVal) 468 | else: 469 | echo "ref_cursor value is null" 470 | 471 | discard dpiVar_release(refCursorCol.colVar) 472 | 473 | discard dpiConn_release(conn) 474 | 475 | # TODO: blob,execute pl/sql, execute script , utilize poolableConnection 476 | 477 | discard dpiContext_destroy(context) 478 | 479 | 480 | -------------------------------------------------------------------------------- /demo/democredentials.nim: -------------------------------------------------------------------------------- 1 | const 2 | oracleuser : cstring = "sys" 3 | pw : cstring = "" 4 | # credentials provided only for demo purpose (working only for local oracl xe installation) 5 | connString : cstring = """(DESCRIPTION = (ADDRESS = 6 | (PROTOCOL = TCP) 7 | (HOST = localhost ) 8 | (PORT = 1521 )) 9 | (CONNECT_DATA =(SERVER = DEDICATED) 10 | (SERVICE_NAME = XEPDB1 ) 11 | ))""" 12 | # the connectionstring - tns_entry out of tnsnames.ora not tested 13 | -------------------------------------------------------------------------------- /demo/demoscript.sql: -------------------------------------------------------------------------------- 1 | select 1 from dual 2 | / -------------------------------------------------------------------------------- /demo/demosql.nim: -------------------------------------------------------------------------------- 1 | const 2 | testTypesSql : cstring = """ 3 | select 'test ' as teststr, 4 | to_number('-134.123456') as testnum, 5 | hextoraw('C0FFEE') as rawcol, 6 | to_timestamp( '29.02.2012 13:01:0987654321', 'DD.MM.YYYY HH24:MI:SSFF8' ) as tstamp, 7 | to_number('-134.123456789012345678901234567') as testnum2 8 | from dual 9 | union all 10 | select 'test2' as teststr, 11 | to_number('1.0123456789') as testnum, 12 | hextoraw('ABCDEF') as rawcol, 13 | null as tstamp, 14 | to_number('1234567890123456789012345678901234567') as testnum2 15 | from dual 16 | """ 17 | 18 | testRefCursor : cstring = """ 19 | begin 20 | open :1 for select 'X' StrVal from dual union all select 'Y' from dual; 21 | end; 22 | """ 23 | -------------------------------------------------------------------------------- /demo/sqlplusdemo.nim: -------------------------------------------------------------------------------- 1 | import ../src/nimodpi 2 | import os 3 | import osproc 4 | import streams 5 | 6 | #[ 7 | very basic example to get a script within the current working 8 | directory executed. only 10lines are consumed. 9 | sqlplus must be accessible from the path-env. 10 | 11 | the script performs a "select 1 from dual " 12 | ]# 13 | 14 | var sqlplusProc = startProcess("sqlplus user/pw@host:port/servicename ","",[],nil,{poEvalCommand,poUsePath}) 15 | # insert your credentials here 16 | sqlplusProc.inputStream.writeLine("@demoscript.sql") 17 | sqlplusProc.inputStream.flush() 18 | 19 | var line = "" 20 | 21 | for i in countup(0,10): 22 | discard sqlplusProc.outputStream.readLine(line.TaintedString) 23 | echo line 24 | 25 | sqlplusProc.inputStream.writeLine(" exit ") 26 | sqlplusProc.inputStream.writeLine("/") 27 | sqlplusProc.inputStream.flush() 28 | 29 | for i in countup(0,10): 30 | discard sqlplusProc.outputStream.readLine(line.TaintedString) 31 | echo line 32 | 33 | sqlplusProc.close() 34 | -------------------------------------------------------------------------------- /examples/hr_pipelined_tablefunc.nim: -------------------------------------------------------------------------------- 1 | # pipelined table function example 2 | # app access via package logic (decoupled tables) 3 | import db_oracle 4 | import options,times 5 | 6 | # table_function example 7 | # intended to run with a local oracle instance (XE) 8 | # if you like to access a remote instance adjust user,pw 9 | # and connection string 10 | include ora_credentials 11 | 12 | var 13 | hr_pa_employee_spec : SqlQuery = osql""" 14 | CREATE OR REPLACE PACKAGE hr.test_pa_employee AS 15 | TYPE ty_employee_rec IS RECORD ( 16 | employee_id NUMBER(6, 0), 17 | first_name VARCHAR2(20 BYTE), 18 | last_name VARCHAR2(25 BYTE), 19 | email VARCHAR2(25 BYTE), 20 | phone_number VARCHAR2(20 BYTE), 21 | hire_date DATE, 22 | job_id VARCHAR2(10 BYTE), 23 | job_title VARCHAR2(35 BYTE), 24 | manager_id NUMBER(6, 0), 25 | manager_first_name VARCHAR2(20 BYTE), 26 | manager_last_name VARCHAR2(25 BYTE) 27 | ); 28 | TYPE ty_employees IS 29 | TABLE OF ty_employee_rec; 30 | FUNCTION getemployeesby_department_id ( 31 | department_id_in NUMBER 32 | ) RETURN ty_employees 33 | PIPELINED; 34 | END test_pa_employee; 35 | """ 36 | hr_pa_employee_body : SqlQuery = osql""" 37 | CREATE OR REPLACE PACKAGE BODY hr.test_pa_employee AS 38 | FUNCTION getemployeesby_department_id ( 39 | department_id_in NUMBER 40 | ) RETURN ty_employees 41 | PIPELINED 42 | AS 43 | va_ret ty_employees; 44 | 45 | CURSOR employee_cur IS 46 | SELECT /* hr.pa_employee.getemployeesby_department_id */ 47 | emp.employee_id, 48 | emp.first_name, 49 | emp.last_name, 50 | emp.email, 51 | emp.phone_number, 52 | emp.hire_date, 53 | j.job_id, 54 | j.job_title, 55 | man.employee_id AS manager_id, 56 | man.first_name AS manager_first_name, 57 | man.last_name AS manager_last_name 58 | FROM 59 | hr.employees emp 60 | INNER JOIN hr.jobs j ON ( j.job_id = emp.job_id ) 61 | LEFT JOIN hr.employees man ON ( man.employee_id = emp.manager_id ) 62 | WHERE 63 | emp.department_id = department_id_in; 64 | 65 | BEGIN 66 | OPEN employee_cur; 67 | LOOP 68 | FETCH employee_cur BULK COLLECT INTO va_ret LIMIT 100; 69 | EXIT WHEN va_ret.count = 0; 70 | FOR indx IN 1..va_ret.count LOOP PIPE ROW ( va_ret(indx) ); 71 | END LOOP; 72 | END LOOP; 73 | 74 | CLOSE employee_cur; 75 | END getemployeesby_department_id; 76 | 77 | END test_pa_employee; 78 | """ 79 | octx: OracleContext 80 | 81 | type 82 | EmployeeDbRec = object 83 | employee_id : Option[int64] 84 | first_name : Option[string] 85 | last_name : Option[string] 86 | email : Option[string] 87 | phone_number : Option[string] 88 | hire_date : Option[DateTime] 89 | job_id : Option[string] 90 | job_title : Option[string] 91 | manager_id : Option[int64] 92 | manager_first_name : Option[string] 93 | manager_last_name : Option[string] 94 | 95 | template toEmployee( row : DpiRow ) : EmployeeDbRec = 96 | EmployeeDbRec(employee_id : row["EMPLOYEE_ID"].fetchInt64, 97 | first_name : row["FIRST_NAME"].fetchString, 98 | last_name : row["LAST_NAME"].fetchString, 99 | email : row["EMAIL"].fetchString, 100 | phone_number : row["PHONE_NUMBER"].fetchString, 101 | hire_date : row["HIRE_DATE"].fetchDateTime , 102 | job_id : row["JOB_ID"].fetchString , 103 | job_title : row["JOB_TITLE"].fetchString, 104 | manager_id : row["MANAGER_ID"].fetchInt64, 105 | manager_first_name : row["MANAGER_FIRST_NAME"].fetchString, 106 | manager_last_name : row["MANAGER_LAST_NAME"].fetchString) 107 | # FIXME: autogen this part with macro 108 | 109 | proc `$`*(p: EmployeeDbRec): string = 110 | $p.first_name & " " & $p.last_name & " " & $p.email & " " & $p.phone_number & 111 | " " & $p.hire_date & " " & $p.job_id & " " & $p.job_title & " " & $p.manager_id & 112 | " " & $p.manager_first_name & " " & $p.manager_last_name 113 | 114 | newOracleContext(octx,DpiAuthMode.SYSDBA) 115 | # create the context with authentication Method 116 | # SYSDBA and default encoding (UTF8) 117 | var conn: OracleConnection 118 | 119 | createConnection(octx, connectionstr, oracleuser, pw, conn) 120 | # create the connection with the desired server, 121 | # credentials and the context 122 | 123 | conn.executeDDL(hr_pa_employee_spec) 124 | conn.executeDDL(hr_pa_employee_body) 125 | echo "package hr.test_pa_employee created" 126 | 127 | var getEmpSql : SqlQuery = osql"""SELECT 128 | employee_id, 129 | first_name, 130 | last_name, 131 | email, 132 | phone_number, 133 | hire_date, 134 | job_id, 135 | job_title, 136 | manager_id, 137 | manager_first_name, 138 | manager_last_name 139 | FROM 140 | TABLE ( hr.test_pa_employee.getemployeesby_department_id( :department_id) ) 141 | """ 142 | 143 | var pstmt : PreparedStatement 144 | conn.newPreparedStatement(getEmpSql , pstmt, 5) 145 | # create prepared statement for the specified query and 146 | # the number of buffered result-rows. the number of buffered 147 | # rows (window) is fixed and can't changed later on 148 | 149 | withPreparedStatement(pstmt): 150 | # use template "withPreparedStatement" to recycle it automatically 151 | # after leaving this block. 152 | var rs: ResultSet 153 | var param = addBindParameter(pstmt,Int64ColumnTypeParam,"department_id") 154 | # example: bind by name 155 | 156 | param.setInt64(some(80.int64)) 157 | # create and set the bind parameter. query bind 158 | # parameter are always non-columnar. 159 | 160 | executeStatement(pstmt,rs) 161 | # execute the statement. if the resulting rows 162 | # fit into entire window we are done here. 163 | 164 | for row in rs.resultSetRowIterator: 165 | echo $row.toEmployee 166 | 167 | # FIXME: bulk insert employee example 168 | 169 | var hr_pa_employee_drop : SqlQuery = osql"""drop package hr.test_pa_employee""" 170 | conn.executeDDL(hr_pa_employee_drop) 171 | echo "package hr.test_pa_employee dropped" 172 | 173 | conn.releaseConnection 174 | destroyOracleContext(octx) 175 | -------------------------------------------------------------------------------- /examples/hr_select.nim: -------------------------------------------------------------------------------- 1 | import db_oracle 2 | import options 3 | 4 | # example: table select 5 | # intended to run with a local oracle instance (XE) 6 | # if you like to access a remote instance adjust user,pw 7 | # and connection string 8 | include ora_credentials 9 | 10 | var octx: OracleContext 11 | 12 | newOracleContext(octx,DpiAuthMode.SYSDBA) 13 | # create the context with authentication Method 14 | # SYSDBA and default encoding (UTF8) 15 | var conn: OracleConnection 16 | 17 | createConnection(octx, connectionstr, oracleuser, pw, conn) 18 | # create the connection with the desired server, 19 | # credentials and the context 20 | 21 | var query2: SqlQuery = osql""" select 22 | rowid, 23 | EMPLOYEE_ID, 24 | FIRST_NAME, 25 | LAST_NAME, 26 | EMAIL, 27 | PHONE_NUMBER, 28 | HIRE_DATE, 29 | JOB_ID, 30 | SALARY, 31 | COMMISSION_PCT, 32 | MANAGER_ID, 33 | DEPARTMENT_ID 34 | from hr.employees where department_id = :param1 """ 35 | 36 | var pstmt: PreparedStatement 37 | 38 | newPreparedStatement(conn, query2, pstmt, 10) 39 | # create prepared statement for the specified query and 40 | # the number of buffered result-rows. the number of buffered 41 | # rows (window) is fixed and can't changed later on 42 | 43 | withPreparedStatement(pstmt): 44 | # use withPreparedStatement to recycle it automatically 45 | # after leaving this block. 46 | var rs: ResultSet 47 | var param = addBindParameter(pstmt,Int64ColumnTypeParam,"param1") 48 | # example: bind by name 49 | 50 | param.setInt64(some(80.int64)) 51 | # create and set the bind parameter. query bind 52 | # parameter are always non-columnar. 53 | 54 | executeStatement(pstmt,rs) 55 | # execute the statement. if the resulting rows 56 | # fit into entire window we are done here. 57 | 58 | for i,ct in rs.resultColumnTypeIterator: 59 | # consume columnTypes of the resultSet 60 | echo "cname:" & rs.rsColumnNames[i] & " colnumidx: " & 61 | $(i+1) & " " & $ct 62 | 63 | for row in rs.resultSetRowIterator: 64 | # the result iterator fetches internally further 65 | # rows if present 66 | # example of value retrieval by columnname or index 67 | # the column of the row is accessed by colnum or name 68 | echo $row[0].fetchRowId & 69 | " " & $row[1].fetchInt64 & 70 | " " & $row["FIRST_NAME"].fetchString & 71 | " " & $row["LAST_NAME"].fetchString & 72 | " " & $row["EMAIL"].fetchString & 73 | " " & $row[10].fetchInt64 & # manager_id 74 | " " & $row[11].fetchInt64 # department_id 75 | # columns can be accessed by column-index or column_name 76 | 77 | echo "query1 executed - param department_id = 80 " 78 | echo "reexecute with param department_id = 10 " 79 | # reexecute preparedStatement with a different parameter value 80 | param.setInt64(some(10.int64)) 81 | executeStatement(pstmt, rs) 82 | 83 | for row in rs.resultSetRowIterator: 84 | # retrieve column values by columnname or index 85 | echo $row[0].fetchRowId & 86 | " " & $row["EMPLOYEE_ID"].fetchInt64 & 87 | " " & $row["FIRST_NAME"].fetchString & 88 | " " & $row["LAST_NAME"].fetchString & 89 | " " & $row[10].fetchInt64 & 90 | " " & $row[11].fetchInt64 91 | 92 | conn.releaseConnection 93 | destroyOracleContext(octx) -------------------------------------------------------------------------------- /examples/ora_credentials.nim: -------------------------------------------------------------------------------- 1 | const 2 | oracleuser: string = "sys" 3 | pw: string = "" 4 | connectionstr: string = """(DESCRIPTION = (ADDRESS = 5 | (PROTOCOL = TCP) 6 | (HOST = localhost) 7 | (PORT = 1521)) 8 | (CONNECT_DATA =(SERVER = DEDICATED) 9 | (SERVICE_NAME = XEPDB1 ) 10 | ))""" 11 | # if there is a local Oracle XE instance running, let this file unchanged -------------------------------------------------------------------------------- /nimodpi.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | version = "0.1.0" 3 | author = "Michael Krauter" 4 | description = " oracle odpi-c wrapper " 5 | license = "MIT" 6 | skipDirs = @["demo"] 7 | srcDir = "src" 8 | 9 | # Dependencies 10 | requires "nim >= 1.2.6" 11 | requires "nimterop >= 0.6.11" 12 | 13 | 14 | task demo, "running demo": 15 | exec "nim odpic_demo" 16 | 17 | task db_oracle, "running db_oracle examples ": 18 | exec "nim oracle_demo" 19 | 20 | -------------------------------------------------------------------------------- /src/db_oracle.nim: -------------------------------------------------------------------------------- 1 | import os,times,options,strformat,tables 2 | import nimodpi 3 | export nimodpi 4 | 5 | # Copyright (c) 2019 Michael Krauter 6 | # MIT-license - please see the LICENSE-file for details. 7 | 8 | #[ 9 | nim oracle database access layer. this is the high level API. 10 | 11 | - native proc names preserved for easier linkage to the odpi-c docs 12 | - new experimental API with exposed original ODPI-C api. 13 | - 14 | - this API is far away from being perfect. the design goal is to provide 15 | - a simple, easy to use API without thinking about internals and without 16 | - implementation quirks. 17 | - 18 | - some features: 19 | - resultset zero-copy approach: the caller is responsible 20 | - for copying the data (helper templates present) 21 | - within the application domain. "peeking" data is possible via pointers. 22 | - copy takes place into odpi-c domain when bindparameter or inserts are used. 23 | - 24 | - 25 | - only the basic types are implemented (numeric, rowid, varchar2, timestamp) 26 | - pl/sql procedure exploitation is possible with in/out and inout-parameters 27 | - consuming refcursor (out) is also possible (see examples at the end of the file) 28 | - TODO: implement LOB/BLOB handling 29 | - 30 | - designing a vendor generic database API often leads to 31 | clumpsy workaround solutions. 32 | due to that a generic API is out of scope of this project. 33 | it's more valueable to wrap the vendor 34 | specific parts into an own module with a 35 | question/answer style API according to the business-case 36 | - 37 | - besides the connections there are three basic important objects: 38 | - ParamType is used for parameter binding (in/out/inout) 39 | and consuming result columns 40 | - PreparedStatement is used for executing dml/ddl with or 41 | without parameters. internal resources 42 | are not freed unless destroy is called. 43 | - ResultSets are used to consume query results and can be reused. 44 | - it provides a row-iterator-style 45 | - API and a direct access API of the buffer (by index) 46 | - 47 | - some additional hints: do not share connection/preparedStatement 48 | between different threads. 49 | - the context can be shared between threads according to the 50 | ODPI-C documentation (untested). 51 | - index-access of a resultset is not bounds checked. 52 | - if an internal error happens, execution is terminated 53 | and the cause could be extracted with the 54 | - template getErrstr out of the context. 55 | - 56 | - for testing and development the XE database is sufficient. 57 | - version 18c also supports partitioning, PDB's and so on. 58 | - 59 | - database resident connection pool is not tested but should work 60 | - 61 | - some definitions: 62 | - read operation is database->client direction. 63 | - write operation is client -> database direction. 64 | - 65 | - TODO: 66 | * move object related API into own module 67 | * object type API needs to be reviewed (resultset/binds) (at the 68 | moment the object type needs to be specified for resultset obj access) 69 | and the API itself is not easy enough. 70 | * implement tooling for easier sqlplus exploitaition 71 | * tooling for easy compiletime-query validation, 72 | continous query notification, examples 73 | ]# 74 | 75 | type 76 | OracleContext* = object 77 | ## stateless wrapper for the context and createParams 78 | oracleContext: ptr dpiContext 79 | versionInfo: dpiVersionInfo 80 | commonParams: dpiCommonCreateParams 81 | connectionParams: dpiConnCreateParams 82 | 83 | NlsLang* = distinct string 84 | 85 | OracleConnection* = object 86 | ## wrapper only at the moment 87 | connection: ptr dpiConn 88 | context: OracleContext 89 | 90 | BindIdx = distinct range[1.int .. int.high] 91 | ## bind parameter type 92 | BindInfoType = enum byPosition, byName 93 | ## bind parameter info type 94 | 95 | BindInfo = object 96 | # tracks the bindType. 97 | case kind*: BindInfoType 98 | of byPosition: paramVal*: BindIdx 99 | of byName: paramName*: string 100 | ## bind parameter info type. tracks the bindInfoType 101 | ## and contains either the BindIndexValue (paramVal) 102 | ## or the BindParameterName (paramName) 103 | 104 | ColumnType* = tuple[nativeType: DpiNativeCType, 105 | dbType: DpiOracleType, 106 | colsize: int, 107 | sizeIsBytes: bool, 108 | scale: int8, 109 | name : string, 110 | precision : int16, 111 | fsPrecision : uint8 ] # scale used for some numeric types 112 | # FIXME: refactor to object and/or recycle driver type directly 113 | ColumnTypeList* = seq[ColumnType] 114 | 115 | OracleObjTypeRef* = ref object 116 | ## thin wrapper for the object type database handle. 117 | ## these type of objects need to be disposed if no longer needed. 118 | ## used for read or write operations for both types and collections. 119 | relatedConn : OracleConnection 120 | baseHdl : ptr dpiObjectType 121 | objectTypeInfo : dpiObjectTypeInfo 122 | isCollection : bool 123 | elementDataTypeInfo : ptr dpiDataTypeInfo 124 | elementObjectTypeInfo : ptr dpiObjectTypeInfo 125 | # the type of the collections member 126 | # elementDataTypeInfo and elementObjectTypeInfo only valid 127 | # for collection types 128 | childObjectType : OracleObjTypeRef 129 | # only set if the objectType is a collection 130 | attributes : seq[ptr dpiObjectAttr] 131 | # only populated if the type is not a collection 132 | columnTypeList : ColumnTypeList 133 | # columnTypeList and attributes are in the 134 | # same order (for non collection types) 135 | namedColumnTypes : TableRef[string,OracleObjTypeRef] 136 | # in case of a object column type the column name refers 137 | # to the introspected objectType 138 | tmpAttrData : ptr dpiData 139 | # allocated area for fetching/setting attributes 140 | # by dpiObject handle 141 | # FIXME: remove - bind data handles related to the 142 | # object and not the object type 143 | 144 | 145 | ParamType* = object 146 | ## ParameterType. describes how the database type is mapped onto 147 | ## the query 148 | bindPosition*: BindInfo 149 | # bind position and type -> rename 2 bindinfo 150 | queryInfo: dpiQueryInfo 151 | # populated for returning statements 152 | columnType*: ColumnType 153 | # tracks the native- and dbtype 154 | objectTypeHandle : ptr dpiObjectType 155 | # object type handle for plsql types 156 | # used for in/out/inout parameter bind values 157 | # freed automatically beecause owner is dpiQueryInfo 158 | rObjTypeInfo : ptr dpiObjectTypeInfo 159 | # result object type info - only populated 160 | # for result parameters at the moment 161 | rAttributeHdl : seq[ptr dpiObjectAttr] 162 | # result attribute handle 163 | # needed for reading/writing object attributes 164 | rObjectTypeRef : OracleObjTypeRef 165 | # new objecttype handle for reading/writing objects 166 | paramVar: ptr dpiVar 167 | # ODPI-C ptr to the variable def 168 | buffer: ptr dpiData 169 | # ODPI-C ptr to the variables data buffer 170 | # provided by ODPI-C 171 | rowBufferSize: int 172 | isPlSqlArray: bool 173 | isFetched: bool 174 | # flag used for refcursor reexecute prevention. 175 | # if a refcursor is within the statement it can't be reused 176 | 177 | ParamTypeRef* = ref ParamType 178 | ## handle to the param type 179 | 180 | ParamTypeList* = seq[ParamTypeRef] 181 | 182 | const 183 | NlsLangDefault = "UTF8".NlsLang 184 | RefCursorColumnTypeParam* = (DpiNativeCType.STMT, 185 | DpiOracleType.OTSTMT, 186 | 1, false, 1.int8,"",0.int16,0.uint8).ColumnType 187 | ## predefined parameter for refcursor 188 | Int64ColumnTypeParam* = (DpiNativeCType.INT64, 189 | DpiOracleType.OTNUMBER, 190 | 1, false, 1.int8,"",0.int16,0.uint8).ColumnType 191 | ## predefined parameter for the Int64 column type (OTNUMBER) 192 | FloatColumnTypeParam* = (DpiNativeCType.FLOAT, 193 | DpiOracleType.OTNATIVE_FLOAT, 194 | 1,false,0.int8,"",0.int16,0.uint8).ColumnType 195 | ## predefined parameter for the Float column type (OTNATIVE_FLOAT) 196 | DoubleColumnTypeParam* = ( DpiNativeCType.DOUBLE, 197 | DpiOracleType.OTNATIVE_DOUBLE, 198 | 1,false,0.int8,"",0.int16,0.uint8).ColumnType 199 | ## predefined parameter for the Double column type (OTNATIVE_DOUBLE) 200 | ZonedTimestampTypeParam* = (DpiNativeCType.TIMESTAMP, 201 | DpiOracleType.OTTIMESTAMP_TZ, 202 | 1,false,0.int8,"",0.int16,0.uint8).ColumnType 203 | ## predefined parameter for the Timestamp with Timezone type (OTTIMESTAMP_TZ) 204 | # FIXME: populate scale, precision for fp-types 205 | 206 | type 207 | SqlQuery* = distinct cstring 208 | ## fixme: use sqlquery from db_common 209 | PlSql* = distinct cstring 210 | 211 | PreparedStatement* = object 212 | relatedConn: OracleConnection 213 | query*: cstring 214 | boundParams: ParamTypeList 215 | # holds all parameters - mixed in/out or inout 216 | stmtCacheKey*: cstring 217 | scrollable: bool # unused 218 | columnCount: uint32 # deprecated 219 | pStmt: ptr dpiStmt 220 | # ODPI-C ptr to the statement 221 | statementInfo*: dpiStmtInfo # populated within the execute stage 222 | # used to allocate new vars for plsql 223 | # type params 224 | bufferedRows*: int # refers to the maxArraySize 225 | # used for both reading and writing to the database 226 | rsOutputCols: ParamTypeList 227 | # auto-introspected list for returning statements 228 | rsCurrRow*: int # reserved for iterating 229 | rsMoreRows*: bool # 230 | rsBufferRowIndex*: int 231 | # ODPI-C pointer type - unused at the moment 232 | rsRowsFetched*: int # stats counter 233 | rsColumnNames*: seq[string] 234 | # populated on the first executeStatement 235 | ## PreparedStatement is used for both read and write operations 236 | ## (working with queries and object types). 237 | 238 | ResultSet* = PreparedStatement 239 | ## PreparedStatement type alias 240 | 241 | DpiRow* = object 242 | rset : ptr ResultSet 243 | rawRow : seq[ptr dpiData] 244 | ## contains all data pointers of the current iterated row 245 | ## of the ResultSet 246 | 247 | OracleObjRef* = ref object of RootObj 248 | ## thin wrapper of an object instance 249 | ## related to a specific type handle. this kind of 250 | ## object need to be disposed if no longer needed. 251 | ## typically used for both write and operations (client->database). 252 | objType : OracleObjTypeRef 253 | objHdl : ptr dpiObject 254 | # the odpi-c object handle. this ref needs to be freed if no 255 | # longer in use 256 | # paramVar : ptr dpiVar 257 | dataHelper : ptr dpiData 258 | # helper used for adding to collection 259 | bufferedColumn : seq[ptr dpiData] 260 | # the attributes of the object. only 261 | # initialized if object owns attributes 262 | # FIXME: evaluate if needed 263 | bufferedBytesTypeBase : ptr byte 264 | 265 | OracleCollectionRef* = ref object of OracleObjRef 266 | ## thin wrapper of a collection type element. 267 | ## this kind of object need to be disposed if no 268 | ## longer needed. 269 | elementHelper : ptr dpiData 270 | # one dpiData chunk 271 | # moved to typedef 272 | incarnatedChilds : TableRef[int, OracleObjRef] 273 | # tracks incarnated objects for auto-disposal 274 | type 275 | Lob* = object 276 | ## TODO: implement 277 | lobtype : ParamType 278 | chunkSize : uint32 279 | lobref : ptr dpiLob 280 | 281 | DpiLobType* = enum CLOB = DpiOracleType.OTCLOB, 282 | NCLOB = DpiOracleType.OTNCLOB, 283 | BLOB = DpiOracleType.OTBLOB 284 | 285 | include odpi_obj2string 286 | # contains some utility templates for object to string conversion (debug) 287 | include odpi_to_nimtype 288 | # some helper template for dealing with byte sequences/date types 289 | include nimtype_to_odpi 290 | # helper templates for copying nim types into ODPI-C domain 291 | 292 | template newStringColTypeParam*(strlen: int): ColumnType = 293 | ## helper to construct a string ColumnType with specified len 294 | (DpiNativeCType.BYTES, DpiOracleType.OTVARCHAR, strlen, false, 0.int8, 295 | "",0.int16,0.uint8 296 | ) 297 | 298 | template newRawColTypeParam*(bytelen: int): ColumnType = 299 | ## helper to construct a string ColumnType with specified len 300 | (DpiNativeCType.BYTES, DpiOracleType.OTRAW,bytelen,true, 0.int8, 301 | "",0.int16,0.uint8 302 | ) 303 | 304 | template `[]`*(rs: ResultSet, colidx: int): ParamTypeRef = 305 | ## selects the column of the resultSet. 306 | rs.rsOutputCols[colidx] 307 | 308 | template `[]`*(row: DpiRow, colname: string): ptr dpiData = 309 | ## access of the iterators dataCell by column name. 310 | ## if the column name is not present an nil ptr is returned 311 | var cellptr : ptr dpiData = cast[ptr dpiData](0) 312 | for i in row.rawRow.low .. row.rawRow.high: 313 | if cmp(colname, row.rset.rsOutputCols[i].columnType.name) == 0: 314 | # specified row found 315 | cellptr = row.rawRow[i] 316 | break; 317 | cellptr 318 | 319 | template `[]`*(row: DpiRow, index : int ): ptr dpiData = 320 | ## access of the iterators dataCell by index (starts with 0) 321 | row.rawRow[index] 322 | 323 | template toObject*( val : ptr dpiData) : ptr dpiData = 324 | ## FIXME: eval if needed 325 | val.value.asObject 326 | 327 | template `[]`(data: ptr dpiData, idx: int): ptr dpiData = 328 | ## direct access of a column's cell within the columnbuffer by index. 329 | ## internal - used by DpiRow for calculating the ptr. 330 | cast[ptr dpiData]((cast[int](data)) + (sizeof(dpiData)*idx)) 331 | 332 | template `[]`*(rs: ParamTypeRef, rowidx: int): ptr dpiData = 333 | ## selects the row of the specified column 334 | ## the value could be extracted by the dpiData/dpiDataBuffer ODPI-C API 335 | ## or with the fetch-templates 336 | ## 337 | ## further reading: 338 | ## https://oracle.github.io/odpi/doc/structs/dpiData.html 339 | ## Remark: not all type combinations are implemented by ODPI-C 340 | rs.buffer[rowidx] 341 | 342 | proc `[]`*(rs: var PreparedStatement, bindidx: BindIdx): ParamTypeRef = 343 | ## retrieves the paramType for setting the parameter value by index 344 | ## use the setParam templates for setting the value after that. 345 | ## before fetching make sure that the parameter is already created 346 | for i in rs.boundParams.low .. rs.boundParams.high: 347 | if not rs.boundParams[i].isNil and 348 | rs.boundParams[i].bindPosition.kind == byPosition and 349 | rs.boundParams[i].bindPosition.paramVal.int == bindidx.int: 350 | result = rs.boundParams[i] 351 | return 352 | 353 | raise newException(IOError, "PreparedStatement[] parameterIdx : " & 354 | $bindidx.int & " not found!") 355 | {.effects.} 356 | 357 | proc `[]`*(rs: var PreparedStatement, paramName: string): ParamTypeRef = 358 | ## retrieves the pointer for setting the parameter value by paramname 359 | ## before fetching make sure that the parameter is already created otherwise 360 | ## an IOError is thrown 361 | ## TODO: use map for paramNames 362 | for i in rs.boundParams.low .. rs.boundParams.high: 363 | if not rs.boundParams[i].isNil and 364 | rs.boundParams[i].bindPosition.kind == byName and 365 | cmp(paramName, rs.boundParams[i].bindPosition.paramName) == 0: 366 | result = rs.boundParams[i] 367 | return 368 | 369 | raise newException(IOError, "PreparedStatement[] parameter : " & 370 | paramName & " not found!") 371 | {.effects.} 372 | 373 | template isDbNull*(val: ptr dpiData): bool = 374 | ## returns true if the value is dbNull (value not present) 375 | val.isNull.bool 376 | 377 | template setDbNull*(val: ptr dpiData) = 378 | ## sets the value to dbNull (no value present) 379 | val.isNull = 1.cint 380 | 381 | template setNotDbNull*(val: ptr dpiData) = 382 | ## sets value present 383 | val.isNull = 0.cint 384 | 385 | template getColumnCount*(rs: var ResultSet): int = 386 | ## returns the number of columns for the ResultSet 387 | rs.rsColumnNames.len 388 | 389 | template getColumnType*(rs : var ResultSet, idx : BindIdx) : ColumnType = 390 | ## fetches the columntype of the resulting column set by index 391 | rs[idx.int].columnType 392 | 393 | template getBoundColumnType*( ps : var PreparedStatement, idx : BindIdx) : ColumnType = 394 | ## fetches the columntype of a bound parameter by index 395 | ps.boundParams[idx.int].columnType 396 | 397 | template getNativeType*(pt: ParamTypeRef): DpiNativeCType = 398 | ## peeks the native type of a param. useful to determine 399 | ## which type the result column would be 400 | pt.nativeType 401 | 402 | template getDbType*(pt: ParamTypeRef): DpiOracleType = 403 | ## peeks the db type of the given param 404 | pt.dbType 405 | 406 | template newParamTypeList(len: int): ParamTypeList = 407 | # internal template ParamTypeList construction 408 | newSeq[ParamTypeRef](len) 409 | 410 | template isSuccess*(result: DpiResult): bool = 411 | result == DpiResult.SUCCESS 412 | 413 | template isFailure*(result: DpiResult): bool = 414 | result == DpiResult.FAILURE 415 | 416 | template osql*(sql: string): SqlQuery = 417 | ## template to construct an oracle-SqlQuery type 418 | SqlQuery(sql.cstring) 419 | 420 | proc newOracleContext*(outCtx: var OracleContext, 421 | authMode : DpiAuthMode, 422 | encoding : NlsLang = NlsLangDefault ) = 423 | ## constructs a new OracleContext needed to access the database. 424 | ## if DpiResult.SUCCESS is returned the outCtx is populated. 425 | ## In case of an error an IOException is thrown. 426 | ## the commonParams createMode is always DPI_MODE_CREATE_THREADED 427 | var ei: dpiErrorInfo 428 | if DpiResult( 429 | dpiContext_createWithParams(DPI_MAJOR_VERSION, DPI_MINOR_VERSION, nil, addr( 430 | outCtx.oracleContext), ei.addr) 431 | ).isSuccess: 432 | discard dpiContext_initCommonCreateParams(outCtx.oracleContext, 433 | outCtx.commonParams.addr) 434 | discard dpiContext_initConnCreateParams(outCtx.oracleContext, 435 | outCtx.connectionParams.addr) 436 | outCtx.connectionParams.authMode = authMode.ord.uint32 437 | outCtx.commonParams.encoding = encoding.cstring 438 | outCtx.commonParams.createMode = DPI_MODE_CREATE_THREADED 439 | else: 440 | raise newException(IOError, "newOracleContext: " & 441 | $ei ) 442 | {.effects.} 443 | 444 | template getErrstr(ocontext: ptr dpiContext): string = 445 | ## checks if the last operation results with error or not 446 | var ei: dpiErrorInfo 447 | dpiContext_getError(ocontext, ei.addr) 448 | $ei 449 | 450 | proc destroyOracleContext*(ocontext: var OracleContext) = 451 | ## destroys the present oracle context. the resultcode of this operation could 452 | ## be checked with hasError 453 | # TODO: evaluate what happens if there are open connections present 454 | if DpiResult(dpiContext_destroy(ocontext.oracleContext)).isFailure: 455 | raise newException(IOError, "newOracleContext: " & 456 | getErrstr(ocontext.oracleContext)) 457 | {.effects.} 458 | 459 | proc createConnection*(octx: var OracleContext, 460 | connectstring: string, 461 | username: string, 462 | passwd: string, 463 | ocOut: var OracleConnection, 464 | stmtCacheSize : int = 50) = 465 | ## creates a connection for the given context and credentials. 466 | ## throws IOException in an error case 467 | #FIXME: check wallet operation 468 | ocOut.context = octx 469 | if DpiResult(dpiConn_create(octx.oracleContext, username.cstring, 470 | username.cstring.len.uint32, 471 | passwd.cstring, passwd.cstring.len.uint32, connectstring, 472 | connectstring.len.uint32, 473 | octx.commonParams.addr, octx.connectionParams.addr, addr( 474 | ocOut.connection))).isFailure: 475 | raise newException(IOError, "createConnection: " & 476 | getErrstr(octx.oracleContext)) 477 | {.effects.} 478 | else: 479 | if DpiResult(dpiConn_setStmtCacheSize(ocOut.connection, 480 | stmtCacheSize.uint32)).isFailure: 481 | raise newException(IOError, "createConnection: " & 482 | getErrstr(octx.oracleContext)) 483 | {.effects.} 484 | 485 | proc releaseConnection*(conn: var OracleConnection) = 486 | ## releases the connection 487 | if DpiResult(dpiConn_release(conn.connection)).isFailure: 488 | raise newException(IOError, "createConnection: " & 489 | getErrstr(conn.context.oracleContext)) 490 | {.effects.} 491 | 492 | proc terminateExecution*(conn : var OracleConnection) = 493 | ## terminates any running/pending execution on the server 494 | ## associated to the given connection 495 | if DpiResult(dpiConn_breakExecution(conn.connection)).isFailure: 496 | raise newException(IOError, "terminateExecution: " & 497 | getErrstr(conn.context.oracleContext)) 498 | {.effects.} 499 | 500 | proc setDbOperationAttribute*(conn : var OracleConnection,attribute : string) = 501 | ## end to end tracing for auditTrails and Enterprise Manager 502 | if DpiResult(dpiConn_setDbOp(conn.connection, 503 | $(attribute.cstring),attribute.len.uint32)).isFailure: 504 | raise newException(IOError, "setDbOperationAttribute: " & 505 | getErrstr(conn.context.oracleContext)) 506 | {.effects.} 507 | 508 | proc subscribe(conn : var OracleConnection, 509 | params : ptr dpiSubscrCreateParams, 510 | outSubscr : ptr ptr dpiSubscr) = 511 | ## creates a new subscription (notification) on database events 512 | # TODO: test missing 513 | if DpiResult(dpiConn_subscribe(conn.connection, 514 | params,outSubscr)).isFailure: 515 | raise newException(IOError, "subscribe: " & 516 | getErrstr(conn.context.oracleContext)) 517 | {.effects.} 518 | 519 | proc unsubscribe(conn : var OracleConnection, subscription : ptr dpiSubscr) = 520 | ## unsubscribe the database event subscription 521 | # TODO: test missing 522 | if DpiResult(dpiConn_unsubscribe(conn.connection,subscription)).isFailure: 523 | raise newException(IOError, "unsubscribe: " & 524 | getErrstr(conn.context.oracleContext)) 525 | {.effects.} 526 | 527 | proc newPreparedStatement*(conn: var OracleConnection, 528 | query: var SqlQuery, 529 | outPs: var PreparedStatement, 530 | bufferedRows: int, 531 | stmtCacheKey: string = "") = 532 | ## constructs a new prepared statement object linked 533 | ## to the given specified query. 534 | ## the statement cache key is optional. 535 | ## throws IOException in an error case 536 | outPs.scrollable = false # always false due to missing implementation 537 | outPs.query = cast[cstring](query) 538 | outPs.columnCount = 0 539 | outPs.relatedConn = conn 540 | 541 | outPs.stmtCacheKey = stmtCacheKey.cstring 542 | outPs.boundParams = newSeq[ParamTypeRef](0) 543 | outPs.bufferedRows = bufferedRows 544 | 545 | if DpiResult(dpiConn_prepareStmt(conn.connection, 0.cint, outPs.query, 546 | outPs.query.len.uint32, outPs.stmtCacheKey, 547 | outPs.stmtCacheKey.len.uint32, outPs.pStmt.addr)).isFailure: 548 | raise newException(IOError, "newPreparedStatement: " & 549 | getErrstr(conn.context.oracleContext)) 550 | {.effects.} 551 | 552 | if DpiResult(dpiStmt_setFetchArraySize( 553 | outPs.pStmt, 554 | bufferedRows.uint32) 555 | ).isFailure: 556 | raise newException(IOError, "newPreparedStatement: " & 557 | getErrstr(conn.context.oracleContext)) 558 | {.effects.} 559 | 560 | template newPreparedStatement*(conn: var OracleConnection, 561 | query: var SqlQuery, 562 | outPs: var PreparedStatement, 563 | stmtCacheKey: string = "") = 564 | ## convenience template to construct a preparedStatement 565 | ## for ddl 566 | newPreparedStatement(conn, query, outPs, 1, stmtCacheKey) 567 | 568 | 569 | template setClientLibPrefetchRows*(ps : var PreparedStatement, numRows : uint32) = 570 | ## overrides the clients lib DPI_DEFAULT_PREFETCH_ROWS default. setting it to 0 571 | ## will completely disable prefetching 572 | if DpiResult(dpiStmt_setPrefetchRows(ps.pstmt,numRows)).isFailure: 573 | raise newException(IOError, "setClientLibPrefetchRows: " & 574 | " error while setting number of prefetched rows : " & getErrstr( 575 | ps.relatedConn.context.oracleContext)) 576 | {.effects.} 577 | 578 | template getClientLibPrefetchRows*(ps : var PreparedStatement) : uint32 = 579 | ## gets the number of rows the client lib is prefetching. 580 | var numrows : uint32 = 0 581 | if DpiResult(dpiStmt_getPrefetchRows(ps.pstmt,numrows.addr)).isFailure: 582 | raise newException(IOError, "getClientLibPrefetchRows: " & 583 | " error while getting number of prefetched rows : " & getErrstr( 584 | ps.relatedConn.context.oracleContext)) 585 | {.effects.} 586 | numrows 587 | 588 | template releaseParameter(ps : var PreparedStatement, param : ParamTypeRef) = 589 | ## frees the internal dpiVar resources 590 | if DpiResult(dpiVar_release(param.paramVar)).isFailure: 591 | raise newException(IOError, " releaseParameter: " & 592 | "error while calling dpiVar_release : " & getErrstr( 593 | ps.relatedConn.context.oracleContext)) 594 | {.effects.} 595 | 596 | template initAttributes(objType : OracleObjTypeRef ) = 597 | ## introspects and initializes the object types attributes if present 598 | objType.columnTypeList = newSeq[ColumnType](objType.objectTypeInfo.numAttributes) 599 | objType.namedColumnTypes = newTable[string,OracleObjTypeRef]() 600 | 601 | # setup attribute list 602 | # todo: check if attributes present 603 | if objType.objectTypeInfo.numAttributes > 0.uint16: 604 | objType.attributes = 605 | newSeq[ptr dpiObjectAttr](objType.objectTypeInfo.numAttributes) 606 | 607 | if DpiResult(dpiObjectType_getAttributes(objType.baseHdl, 608 | objType.objectTypeInfo.numAttributes, 609 | objType.attributes[0].addr 610 | )).isFailure: 611 | raise newException(IOError, "lookupAttributes: " & 612 | getErrstr(objType.relatedConn.context.oracleContext)) 613 | {.effects.} 614 | 615 | 616 | var attrInfo : dpiObjectAttrInfo 617 | 618 | for i in objType.columnTypeList.low .. objType.columnTypeList.high: 619 | discard dpiObjectAttr_getInfo(objType.attributes[i],attrInfo.addr) 620 | var attrName : string = newString(attrInfo.nameLength) 621 | copyMem(addr(attrName[0]),attrInfo.name,attrInfo.nameLength) 622 | 623 | objType.columnTypeList[i] = (DpiNativeCType(attrInfo.typeInfo.defaultNativeTypeNum), 624 | DpiOracleType(attrInfo.typeInfo.oracleTypeNum), 625 | attrInfo.typeInfo.clientSizeInBytes.int, 626 | true, 627 | attrInfo.typeInfo.scale, 628 | attrName, 629 | attrInfo.typeInfo.precision, 630 | attrInfo.typeInfo.fsPrecision 631 | ) 632 | 633 | 634 | proc initObjectTypeHdl(objType : OracleObjTypeRef, hdl : ptr dpiObjectType ) = 635 | ## init the OracleObjTypeRef out of it's ODPI-C object-type. 636 | ## if the type is a collection it's childObjectType is also introspected 637 | # FIXME: at the moment nested Types need rework 638 | objType.baseHdl = hdl 639 | 640 | if DpiResult(dpiObjectType_getInfo(objType.baseHdl, 641 | objType.objectTypeInfo.addr)).isFailure: 642 | raise newException(IOError, "lookupInfoType: " & 643 | getErrstr(objType.relatedConn.context.oracleContext)) 644 | {.effects.} 645 | 646 | if objType.objectTypeInfo.isCollection == 1: 647 | objType.isCollection = true 648 | objType.elementDataTypeInfo = objType.objectTypeInfo.elementTypeInfo.addr 649 | discard dpiObjectType_getInfo(objType.elementDataTypeInfo.objectType, 650 | objType.elementObjectTypeInfo) 651 | # introspect element type of the container 652 | 653 | var objinf : dpiObjectTypeInfo 654 | discard dpiObjectType_getInfo(objType.elementDataTypeInfo.objectType,objinf.addr) 655 | var childobjname = fetchObjectTypeName(objinf) 656 | echo "collection type detected. child-type: " & childobjname 657 | objType.childObjectType = OracleObjTypeRef() 658 | # objType.childObjectType.isCollection = false 659 | objType.childObjectType.relatedConn = objType.relatedConn 660 | objType.childObjectType.initObjectTypeHdl(objType.elementDataTypeInfo.objectType) 661 | # objType.childObjectType = lookupObjectType(objType.relatedConn,childobjname) 662 | # auto introspect the child type. at the moment nested collections not supported 663 | 664 | else: 665 | objType.isCollection = false 666 | objType.initAttributes 667 | # objectType is no collection - init attributes 668 | 669 | objType.tmpAttrData = cast[ptr dpiData](alloc0(sizeof(dpiData))) 670 | # alloc mem for raw fetching attribute values 671 | 672 | #FIXME: rework. at the moment no Object-Type-Attributes possible 673 | 674 | 675 | 676 | template newVar(ps: var PreparedStatement, param: ParamTypeRef) = 677 | # internal template to create a new in/out/inout binding variable 678 | # according to the ParamTypeRef's settings 679 | var isArray: uint32 = 0 680 | if param.isPlSqlArray: 681 | isArray = 1 682 | # quirky 683 | 684 | if DpiResult(dpiConn_newVar( 685 | ps.relatedConn.connection, 686 | cast[dpiOracleTypeNum](param.columnType.dbType.ord), 687 | cast[dpiNativeTypeNum](param.columnType.nativeType.ord), 688 | param.rowBufferSize.uint32, #maxArraySize 689 | param.columnType.colsize.uint32, #size 690 | param.columnType.sizeIsBytes.cint, 691 | isArray.cint, 692 | param.objectTypeHandle, 693 | param.paramVar.addr, # out 694 | param.buffer.addr # out 695 | )).isFailure: 696 | raise newException(IOError, "bindParameter: " & 697 | "error while calling dpiConn_newVar : " & getErrstr( 698 | ps.relatedConn.context.oracleContext)) 699 | {.effects.} 700 | 701 | 702 | proc newParamTypeRef(ps: var PreparedStatement, 703 | bindInfo: BindInfo, 704 | coltype: ColumnType, 705 | isPlSqlArray: bool, 706 | boundRows: int, 707 | objHdl : ptr dpiObjectType = nil): ParamTypeRef = 708 | result = ParamTypeRef(bindPosition: bindInfo, 709 | columnType: coltype, 710 | paramVar: nil, 711 | buffer: nil, 712 | rowBufferSize: boundRows, 713 | isPlSqlArray: isPlSqlArray, 714 | isFetched: false, 715 | objectTypeHandle : objHdl) 716 | 717 | proc addBindParameter*(ps: var PreparedStatement, 718 | coltype: ColumnType, 719 | paramName: string) : ParamTypeRef = 720 | ## constructs a non-array bindparameter by parameterName. 721 | ## throws IOException in case of error. 722 | ## see https://oracle.github.io/odpi/doc/user_guide/data_types.html 723 | ## for supported type combinations of ColumnType. 724 | ## the parametername must be referenced within the 725 | ## query with colon. example : 726 | ## after adding the parameter, the value can be set with the typed setters 727 | ## on the ParamType. the type of the parameter is implicit in,out or in/out. 728 | ## this depends on the underlying query 729 | result = newParamTypeRef(ps, BindInfo(kind: BindInfoType.byName, 730 | paramName: paramName), 731 | coltype,false,1.int) 732 | newVar(ps, result) 733 | ps.boundParams.add(result) 734 | 735 | proc addBindParameter*(ps: var PreparedStatement, 736 | coltype: ColumnType, 737 | idx: BindIdx) : ParamTypeRef = 738 | ## constructs a non-array bindparameter by parameter index. 739 | ## throws IOException in case of error. 740 | ## see https://oracle.github.io/odpi/doc/user_guide/data_types.html 741 | ## for supported type combinations of ColumnType. 742 | ## the parameterindex must be referenced within the 743 | ## query with colon. example :. 744 | ## the parameter value can be set with the typed setters on the ParamType. 745 | ## the type of the parameter is implicit in,out or in/out. 746 | ## this depends on the underlying query 747 | result = newParamTypeRef(ps, BindInfo(kind: BindInfoType.byPosition, 748 | paramVal: idx), coltype,false,1.int) 749 | newVar(ps, result) 750 | ps.boundParams.add(result) 751 | 752 | proc addArrayBindParameter*(ps: var PreparedStatement, 753 | coltype: ColumnType, 754 | paramName: string, 755 | rowCount : int, 756 | isPlSqlArray : bool = false) : ParamTypeRef = 757 | ## constructs an array bindparameter by parameterName. 758 | ## array parameters are used for bulk-insert or plsql-array-handling 759 | ## throws IOException in case of error. isPlSqlArray is true only for 760 | ## index by tables. 761 | ## see https://oracle.github.io/odpi/doc/user_guide/data_types.html 762 | ## for supported type combinations of ColumnType. 763 | ## the parametername must be referenced within the 764 | ## query with a colon. example : 765 | ## after adding the parameter value can be set with the typed setters 766 | ## on the ParamType. the type of the parameter is implicit in,out or in/out. 767 | ## this depends on the underlying query 768 | if ps.bufferedRows < rowCount: 769 | raise newException(IOError, "addArrayBindParameter: " & 770 | "given rowCount of " & $rowCount & 771 | " exceeds the preparedStatements maxBufferedRows" ) 772 | {.effects.} 773 | 774 | result = newParamTypeRef(ps, BindInfo(kind: BindInfoType.byName, 775 | paramName: paramName), 776 | coltype,isPlSqlArray,rowCount) 777 | newVar(ps, result) 778 | ps.boundParams.add(result) 779 | 780 | proc addArrayBindParameter*(ps: var PreparedStatement, 781 | coltype: ColumnType, 782 | idx: BindIdx, 783 | rowCount : int, 784 | isPlSqlArray : bool = false): ParamTypeRef = 785 | ## constructs a array bindparameter by parameter index. 786 | ## array parameters are used for bulk-insert or plsql-array-handling. 787 | ## throws IOException in case of error. isPlSqlArray is true only for 788 | ## index by tables. 789 | ## see https://oracle.github.io/odpi/doc/user_guide/data_types.html 790 | ## for supported type combinations of ColumnType. 791 | ## the parameterindex must be referenced within the 792 | ## query with a colon. example :. 793 | ## the parameter value can be set with the typed setters on the ParamType. 794 | ## the type of the parameter is implicit in,out or in/out. 795 | ## this depends on the underlying query 796 | if ps.bufferedRows < rowCount: 797 | raise newException(IOError, "addArrayBindParameter: " & 798 | "given rowCount of " & $rowCount & 799 | " exceeds the preparedStatements maxBufferedRows" ) 800 | {.effects.} 801 | 802 | result = newParamTypeRef(ps, BindInfo(kind: BindInfoType.byPosition, 803 | paramVal: idx), 804 | coltype,isPlSqlArray,rowcount) 805 | newVar(ps, result) 806 | ps.boundParams.add(result) 807 | 808 | 809 | proc addObjectBindParameter*(ps: var PreparedStatement, 810 | idx: BindIdx, 811 | objType : OracleObjTypeRef, 812 | rowCount : int , 813 | isPlSqlArray : bool = false ): ParamTypeRef = 814 | ## constructs a object bindparameter by parameter index. 815 | ## this bindparameter is an array parameter type. isPlSqlArray is true only for 816 | ## index by tables. 817 | ## array parameters are used for bulk-insert or plsql-array-handling. 818 | ## throws IOException in case of error. 819 | ## the parameterindex must be referenced within the 820 | ## query with a colon. example :. 821 | ## the parameter value can be set with the typed setters on the ParamType. 822 | ## the type of the parameter is implicit in,out or in/out. 823 | ## this depends on the underlying query 824 | if ps.bufferedRows < rowCount: 825 | raise newException(IOError, "addObjectBindParameter: " & 826 | "given rowCount of " & $rowCount & 827 | " exceeds the preparedStatements maxBufferedRows" ) 828 | {.effects.} 829 | 830 | result = newParamTypeRef(ps, 831 | BindInfo(kind: BindInfoType.byPosition, 832 | paramVal: idx), 833 | (DpiNativeCType.OBJECT, DpiOracleType.OTOBJECT,0, 834 | false, 0.int8, "",0.int16,0.uint8), 835 | isPlSqlArray,rowcount,objType.baseHdl) 836 | newVar(ps, result) 837 | ps.boundParams.add(result) 838 | 839 | proc addObjectBindParameter*(ps: var PreparedStatement, 840 | paramName: string, 841 | objType : OracleObjTypeRef, 842 | rowCount : int, 843 | isPlSqlArray : bool = false) : ParamTypeRef = 844 | ## constructs a object bindparameter by parameter name (paramName) 845 | ## this bindparameter is an array parameter type. isPlSqlArray is true only for 846 | ## index by tables. 847 | ## array parameters are used for bulk-insert or plsql-array-handling. 848 | ## throws IOException in case of error. 849 | ## the parameterindex must be referenced within the 850 | ## query with a colon. example :. 851 | ## the parameter value can be set with the typed setters on the ParamType. 852 | ## the type of the parameter is implicit in,out or in/out. 853 | ## this depends on the underlying query 854 | if ps.bufferedRows < rowCount: 855 | raise newException(IOError, "addObjectBindParameter: " & 856 | "given rowCount of " & $rowCount & 857 | " exceeds the preparedStatements maxBufferedRows" ) 858 | {.effects.} 859 | 860 | result = newParamTypeRef(ps, 861 | BindInfo(kind: BindInfoType.byName,paramName: paramName), 862 | (DpiNativeCType.OBJECT, DpiOracleType.OTOBJECT,0, 863 | false, 0.int8,"",0.int16,0.uint8), 864 | isPlSqlArray,rowcount,objType.baseHdl) 865 | newVar(ps, result) 866 | ps.boundParams.add(result) 867 | 868 | 869 | proc releaseOracleObjType( objType : OracleObjTypeRef, isDerived : bool ) = 870 | ## releases the object type obtained by lookupObjectType. 871 | dealloc(objType.tmpAttrData) 872 | 873 | if objType.isCollection: 874 | objType.childObjectType.releaseOracleObjType(true) 875 | 876 | for i in objType.attributes.low .. objType.attributes.high: 877 | if DpiResult(dpiObjectAttr_release(objType.attributes[i])).isFailure: 878 | raise newException(IOError, "releaseOracleObjType: " & 879 | getErrstr(objType.relatedConn.context.oracleContext)) 880 | {.effects.} 881 | 882 | # release base handle 883 | if not isDerived: 884 | # only release not derived handles. if derived, ODPI-C will handle it 885 | if DpiResult(dpiObjectType_release(objType.baseHdl)).isFailure: 886 | raise newException(IOError, "releaseOracleObjType: " & 887 | getErrstr(objType.relatedConn.context.oracleContext)) 888 | {.effects.} 889 | 890 | proc releaseOracleObjType*( objType : OracleObjTypeRef) = 891 | objType.releaseOracleObjType(false) 892 | 893 | template destroy(rsVar : ParamTypeRef , prepStmt : var PreparedStatement) = 894 | 895 | # release objectParamType handles if present 896 | if not rsVar.rObjectTypeRef.isNil: 897 | rsVar.rObjectTypeRef.releaseOracleObjType(true) 898 | ## internal release of the paramtype refs 899 | ## odpic handles 900 | if rsVar.rAttributeHdl.len > 0: 901 | # frees Attribute handles 902 | for i in rsVar.rAttributeHdl.low .. rsVar.rAttributeHdl.high: 903 | discard dpiObjectAttr_release(rsVar.rAttributeHdl[i]) 904 | 905 | if DpiResult(dpiVar_release(rsVar.paramVar)).isFailure: 906 | raise newException(IOError, "destroy: " & 907 | getErrstr(prepStmt.relatedConn.context.oracleContext)) 908 | {.effects.} 909 | 910 | 911 | proc destroy*(prepStmt: var PreparedStatement) = 912 | ## frees the preparedStatements internal resources. 913 | ## this should be called if 914 | ## the prepared statement is no longer in use. 915 | ## additional resources of a present resultSet 916 | ## are also freed. a preparedStatement with refCursor can not 917 | ## be reused 918 | # TODO: evaluate why 919 | for i in prepStmt.boundParams.low .. prepStmt.boundParams.high: 920 | prepStmt.boundParams[i].destroy(prepStmt) 921 | 922 | for i in prepStmt.rsOutputCols.low .. prepStmt.rsOutputCols.high: 923 | prepStmt.rsOutputCols[i].destroy(prepStmt) 924 | 925 | prepStmt.boundParams.setLen(0) 926 | prepStmt.rsOutputCols.setLen(0) 927 | 928 | if DpiResult(dpiStmt_release(prepStmt.pStmt)).isFailure: 929 | raise newException(IOError, "destroy: " & 930 | getErrstr(prepStmt.relatedConn.context.oracleContext)) 931 | {.effects.} 932 | 933 | 934 | proc executeAndInitResultSet(prepStmt: var PreparedStatement, 935 | dpiMode: uint32 = DpiModeExec.DEFAULTMODE.ord, 936 | isRefCursor: bool = false ) = 937 | ## initialises the derived ResultSet 938 | ## from the given preparedStatement by calling execute on it 939 | prepStmt.rsMoreRows = true 940 | prepStmt.rsRowsFetched = 0 941 | 942 | if not isRefCursor: 943 | # TODO: get rid of quirky isRefCursor flag 944 | if DpiResult( 945 | dpiStmt_execute(prepStmt.pStmt, 946 | dpiMode, 947 | prepStmt.columnCount.addr) 948 | ).isFailure: 949 | raise newException(IOError, "executeAndInitResultSet: " & 950 | getErrstr(prepStmt.relatedConn.context.oracleContext)) 951 | {.effects.} 952 | else: 953 | discard dpiStmt_getNumQueryColumns(prepStmt.pStmt, 954 | prepStmt.columnCount.addr) 955 | if DpiResult( 956 | dpiStmt_setFetchArraySize(prepStmt.pStmt, 957 | prepStmt.bufferedRows.uint32) 958 | ).isFailure: 959 | raise newException(IOError, "executeAndInitResultSet(refcursor): " & 960 | getErrstr(prepStmt.relatedConn.context.oracleContext)) 961 | {.effects.} 962 | 963 | if prepStmt.rsOutputCols.len <= 0 and prepStmt.columnCount.int > 0: 964 | # create the needed buffercolumns(resultset) automatically 965 | # only once 966 | prepStmt.rsOutputCols = newParamTypeList(prepStmt.columnCount.int) 967 | prepStmt.rsColumnNames = newSeq[string](prepStmt.columnCount.int) 968 | if DpiResult(dpiStmt_getInfo(prepStmt.pStmt, 969 | prepStmt.statementInfo.addr)).isFailure: 970 | raise newException(IOError, "executeAndInitResultSet: " & 971 | getErrstr(prepStmt.relatedConn.context.oracleContext)) 972 | {.effects.} 973 | 974 | var qInfo: dpiQueryInfo 975 | 976 | for i in countup(1, prepStmt.columnCount.int): 977 | # extract needed params out of the metadata 978 | # the columnindex starts with 1 979 | # TODO: if a type is not supported by ODPI-C 980 | # log error within the ParamType 981 | if DpiResult(dpiStmt_getQueryInfo(prepStmt.pStmt, 982 | i.uint32, 983 | qInfo.addr)).isFailure: 984 | raise newException(IOError, "executeAndInitResultSet: " & 985 | getErrstr(prepStmt.relatedConn.context.oracleContext)) 986 | {.effects.} 987 | 988 | var colname = newString(qInfo.nameLength) 989 | copyMem(addr(colname[0]), qInfo.name.ptr, colname.len) 990 | 991 | prepStmt.rsColumnNames[i-1] = colname 992 | prepStmt.rsOutputCols[i-1] = ParamTypeRef( 993 | bindPosition: BindInfo(kind: BindInfoType.byPosition, 994 | paramVal: BindIdx(i)), 995 | queryInfo: qInfo, 996 | columnType: (nativeType: DpiNativeCType( 997 | qinfo.typeinfo.defaultNativeTypeNum), 998 | dbType: DpiOracleType( 999 | qinfo.typeinfo.oracleTypeNum), 1000 | colSize: qinfo.typeinfo.clientSizeInBytes.int, 1001 | sizeIsBytes: true, 1002 | scale: qinfo.typeinfo.scale, 1003 | name : colname, 1004 | precision : qinfo.typeinfo.precision, 1005 | fsPrecision : qinfo.typeinfo.fsPrecision 1006 | ), 1007 | objectTypeHandle : qinfo.typeinfo.objectType, 1008 | paramVar: nil, 1009 | buffer: nil, 1010 | rowBufferSize: prepStmt.bufferedRows) 1011 | # each out-resultset param owns the rowbuffersize 1012 | # of the prepared statement 1013 | # TODO: if the objectType is not nil, query the number of attributes 1014 | # dpiObjType->getInfo->dpiObjectTypeInfo ->elementTypeInfo->dpiDataTypeInfo->dpiObjType 1015 | # and the object-type 1016 | if not prepStmt.rsOutputCols[i-1].objectTypeHandle.isNil: 1017 | var rcolobjtype = OracleObjTypeRef() 1018 | # special case: baseObjectHandle derived out of resultset and should not be freed afterwards 1019 | rcolobjtype.initObjectTypeHdl(prepStmt.rsOutputCols[i-1].objectTypeHandle) 1020 | # init object type and in case of collection the childtype 1021 | prepStmt.rsOutputCols[i-1].rObjectTypeRef = rcolobjtype 1022 | else: 1023 | prepStmt.rsOutputCols[i-1].rAttributeHdl = @[] 1024 | 1025 | newVar(prepStmt,prepStmt.rsOutputCols[i-1]) 1026 | # FIXME: vars not needed for fetching object types 1027 | if DpiResult(dpiStmt_define(prepStmt.pStmt, 1028 | (i).uint32, 1029 | prepStmt.rsOutputCols[i-1].paramVar) 1030 | ).isFailure: 1031 | raise newException(IOError, "addOutColumn: " & $prepStmt.rsOutputCols[i-1] & 1032 | getErrstr(prepStmt.relatedConn.context.oracleContext)) 1033 | {.effects.} 1034 | 1035 | proc openRefCursor(ps : var PreparedStatement, 1036 | p : var ParamTypeRef, 1037 | outRefCursor : var ResultSet, 1038 | bufferedRows : int, 1039 | dpiMode: uint32 = DpiModeExec.DEFAULTMODE.ord) = 1040 | if p.isFetched: 1041 | # quirky 1042 | raise newException(IOError, "openRefCursor: " & 1043 | """ reexecute of the preparedStatement with refCursor not supported """) 1044 | {.effects.} 1045 | 1046 | if p.columnType.nativeType == DpiNativeCType.STMT and 1047 | p.columnType.dbType == DpiOracleType.OTSTMT: 1048 | p.isFetched = true 1049 | outRefCursor.pstmt = p[0].value.asStmt 1050 | outRefCursor.relatedConn = ps.relatedConn 1051 | outRefCursor.bufferedRows = bufferedRows 1052 | 1053 | executeAndInitResultSet(outRefCursor,dpiMode,true) 1054 | 1055 | else: 1056 | raise newException(IOError, "openRefCursor: " & 1057 | """ bound parameter has not the required 1058 | DpiNativeCType.STMT/DpiOracleType.OTSTMT 1059 | combination """) 1060 | {.effects.} 1061 | 1062 | 1063 | template openRefCursor*(ps: var PreparedStatement, idx : BindIdx, 1064 | outRefCursor: var ResultSet, 1065 | bufferedRows: int, 1066 | dpiMode: uint32 = DpiModeExec.DEFAULTMODE.ord) = 1067 | ## opens the refcursor on the specified bind parameter. 1068 | ## executeStatement must be called before open it. 1069 | ## throws IOError in case of an error 1070 | var param : ParamTypeRef = ps[idx.BindIdx] 1071 | openRefCursor(ps,param,outRefCursor,bufferedRows,dpiMode) 1072 | 1073 | template openRefCursor*(ps: var PreparedStatement, 1074 | paramName : string, 1075 | outRefCursor: var ResultSet, 1076 | bufferedRows: int, 1077 | dpiMode: uint32 = DpiModeExec.DEFAULTMODE.ord) = 1078 | ## opens the refcursor on the specified bind parameter. 1079 | ## executeStatement must be called before open it. 1080 | ## throws IOError in case of an error 1081 | var param = ps[paramName] 1082 | openRefCursor(ps,param,outRefCursor,bufferedRows,dpiMode) 1083 | 1084 | template resetParamRows(ps : var PreparedStatement) = 1085 | ## resets the parameter rows to the spawned maxrow 1086 | ## window size of the prepared statement 1087 | for i in ps.boundParams.low .. ps.boundParams.high: 1088 | ps.boundParams[i].rowBufferSize = ps.bufferedRows 1089 | 1090 | 1091 | template updateBindParams(prepStmt: var PreparedStatement) = 1092 | ## reset the params for reexecution of the prepared statement 1093 | ## (rowBufferSize and bindPosition) 1094 | for i in prepStmt.boundParams.low .. prepStmt.boundParams.high: 1095 | let bp = prepStmt.boundParams[i] 1096 | 1097 | if bp.rowBufferSize > 1: 1098 | # set the num of elements according to the rowBufferSize 1099 | if DpiResult(dpiVar_setNumElementsInArray(bp.paramVar, 1100 | bp.rowBufferSize.uint32)).isFailure: 1101 | raise newException(IOError, "updateBindParams: " & $bp & " " & 1102 | getErrstr(prepStmt.relatedConn.context.oracleContext)) 1103 | {.effects.} 1104 | 1105 | if bp.bindPosition.kind == BindInfoType.byPosition: 1106 | if DpiResult(dpiStmt_bindByPos(prepStmt.pStmt, 1107 | bp.bindPosition.paramVal.uint32, 1108 | bp.paramVar 1109 | ) 1110 | ).isFailure: 1111 | raise newException(IOError, "updateBindParams: " & $bp & " " & 1112 | getErrstr(prepStmt.relatedConn.context.oracleContext)) 1113 | {.effects.} 1114 | 1115 | elif bp.bindPosition.kind == BindInfoType.byName: 1116 | if DpiResult(dpiStmt_bindByName(prepStmt.pStmt, 1117 | bp.bindPosition.paramName, 1118 | bp.bindPosition.paramName.len.uint32, 1119 | bp.paramVar 1120 | ) 1121 | ).isFailure: 1122 | raise newException(IOError, "updateBindParams: " & 1123 | $bp.bindPosition.paramName & 1124 | getErrstr(prepStmt.relatedConn.context.oracleContext)) 1125 | {.effects.} 1126 | 1127 | template executeStatement*(prepStmt: var PreparedStatement, 1128 | outRs: var ResultSet, 1129 | dpiMode: uint32 = DpiModeExec.DEFAULTMODE.ord) = 1130 | ## executes the PreparedStatement. 1131 | ## the results can be fetched 1132 | ## on a col by col base. once executed the bound columns can be reused 1133 | ## 1134 | ## multiple dpiModeExec values can be "or"ed together. 1135 | ## raises IOError in case of a backend-error 1136 | 1137 | # probe if binds present 1138 | if prepStmt.boundParams.len > 0: 1139 | updateBindParams(prepStmt) 1140 | 1141 | executeAndInitResultSet(prepStmt,dpiMode,false) 1142 | outRs = cast[ResultSet](prepStmt) 1143 | 1144 | template executeBulkUpdate(prepStmt: var PreparedStatement, 1145 | numRows: int, 1146 | dpiMode: uint32 = DpiModeExec.DEFAULTMODE.ord) = 1147 | ## executes the statement without returning any rows. 1148 | ## suitable for bulk insert/update 1149 | if prepStmt.boundParams.len > 0: 1150 | updateBindParams(prepStmt) 1151 | 1152 | if DpiResult(dpiStmt_executeMany(prepStmt.pStmt, 1153 | dpimode, 1154 | numRows.uint32)).isFailure: 1155 | raise newException(IOError, "executeMany: " & 1156 | getErrstr(prepStmt.relatedConn.context.oracleContext)) 1157 | {.effects.} 1158 | 1159 | template fetchNextRows*(rs: var ResultSet) = 1160 | ## fetches next rows (if present) into the internal ODPI-C buffer. 1161 | ## if DpiResult.FAILURE is returned the internal error could be retrieved 1162 | ## by calling getErrstr on the OracleContext. 1163 | ## 1164 | ## After fetching, the buffer-window can be accessed by using the 1165 | ## "[][]" operator on the resultSet for native values. 1166 | ## For typed objects please use [].setObject or [].getObject. 1167 | ## No bounds check is performed. 1168 | ## the colidx should not exceed the maximum column-count and the 1169 | ## rowidx should not exceed the given fetchArraySize 1170 | ## 1171 | ## remark: no data-copy is performed. 1172 | ## blobs and strings (pointer types) should be copied into the application 1173 | ## domain before calling fetchNextRows again. 1174 | ## value types are always copied on assignment. 1175 | ## use this template only if you need a "fine granuled" access over the ResultSet - 1176 | ## for all other cases use the iterator resultSetRowIterator instead. 1177 | if rs.rsMoreRows: 1178 | var moreRows: cint # out 1179 | var bufferRowIndex: uint32 # out 1180 | var rowsFetched: uint32 #out 1181 | 1182 | if DpiResult(dpiStmt_fetchRows(rs.pStmt, 1183 | rs.bufferedRows.uint32, 1184 | bufferRowIndex.addr, 1185 | rowsFetched.addr, 1186 | moreRows.addr)).isSuccess: 1187 | rs.rsMoreRows = moreRows.bool 1188 | rs.rsBufferRowIndex = bufferRowIndex.int 1189 | rs.rsRowsFetched = rowsFetched.int 1190 | else: 1191 | raise newException(IOError, "fetchNextRows: " & 1192 | getErrstr(rs.relatedConn.context.oracleContext)) 1193 | {.effects.} 1194 | 1195 | iterator objectAttributeTypeIterator*(objType : OracleObjTypeRef) : tuple[idx : int, 1196 | ct : ColumnType, 1197 | hdl : ptr dpiObjectAttr] = 1198 | ## iterates over the non-collection objects attributes. raises IOError if the 1199 | ## named type is a collection 1200 | if objType.isCollection: 1201 | raise newException(IOError,"objectType is a collection. only non-collection types are iterable") 1202 | for i in countup(objType.columnTypeList.low,objType.columnTypeList.high): 1203 | yield (i,objType.columnTypeList[i],objType.attributes[i]) 1204 | 1205 | 1206 | iterator resultColumnTypeIterator*(rs : var ResultSet) : tuple[idx : int, ct: ColumnType] = 1207 | ## iterates over the resultSets columnTypes if present 1208 | #FIXME: eval if practical 1209 | for i in countup(rs.rsOutputCols.low,rs.rsOutputCols.high): 1210 | yield (i,rs[i].columnType) 1211 | 1212 | iterator resultParamTypeIterator*(rs : var ResultSet) : tuple[idx : int, pt : ParamTypeRef] = 1213 | ## iterates over the resultsets parametertypes if present 1214 | for i in countup(rs.rsOutputCols.low,rs.rsOutputCols.high): 1215 | yield (i,rs[i]) 1216 | 1217 | iterator resultSetRowIterator*(rs: var ResultSet): DpiRow = 1218 | ## iterates over the resultset row by row. no data copy is performed 1219 | ## and ptr type values should be copied into the application 1220 | ## domain before the next window is requested (blob/strings i.e.) 1221 | ## do not use this iterator in conjunction with fetchNextRows because 1222 | ## it's already used internally. 1223 | ## in an error case an IOException is thrown with the error message retrieved 1224 | ## out of the OracleContext. 1225 | var p: DpiRow = DpiRow(rawRow : newSeq[ptr dpiData](rs.rsOutputCols.len), 1226 | rSet : rs.addr) 1227 | rs.rsCurrRow = 0 1228 | rs.rsRowsFetched = 0 1229 | 1230 | fetchNextRows(rs) 1231 | 1232 | while rs.rsCurrRow < rs.rsRowsFetched: 1233 | for i in rs.rsOutputCols.low .. rs.rsOutputCols.high: 1234 | p.rawRow[i] = rs.rsOutputCols[i][rs.rsCurrRow] 1235 | # construct column 1236 | yield p 1237 | inc rs.rsCurrRow 1238 | if rs.rsCurrRow == rs.rsRowsFetched and rs.rsMoreRows: 1239 | fetchNextRows(rs) 1240 | rs.rsCurrRow = 0 1241 | 1242 | iterator bulkBindIterator*(pstmt: var PreparedStatement, 1243 | maxRows : int, 1244 | maxRowBufferWindow : int): 1245 | tuple[rowcounter:int, 1246 | buffercounter:int] = 1247 | ## convenience iterator for bulk binding. 1248 | ## it will countup to maxRows but will return the next 1249 | ## index of the rowBuffer. maxRowBufferIdx is the high watermark. 1250 | ## if it is reached executeStatement is called automatically 1251 | ## and the internal counter is reset to 0. 1252 | ## both internal counters (maxRows and maxRowBufferWindow) start 1253 | ## with 0 1254 | if maxRows < maxRowBufferWindow: 1255 | raise newException(IOError,"bulkBindIterator: " & 1256 | "constraint maxRows < maxRowBufferWindow violated " ) 1257 | {.effects.} 1258 | 1259 | if pstmt.bufferedRows < maxRowBufferWindow: 1260 | raise newException(IOError,"bulkBindIterator: " & 1261 | "constraint bufferedRows < maxRowBufferWindow violated. " & 1262 | " bufferdRows are: " & $pstmt.bufferedRows & " and maxRowBufferWindow" & 1263 | " is: " & $maxRowBufferWindow ) 1264 | {.effects.} 1265 | 1266 | pstmt.resetParamRows 1267 | # reset the parameter rows to the preparedstatements window 1268 | 1269 | var rowBufferIdx = 0.int 1270 | 1271 | for i in countup(0,maxRows): 1272 | yield (rowcounter:i,buffercounter:rowBufferIdx) 1273 | if rowBufferIdx == maxRowBufferWindow: 1274 | # if high watermark reached flush the buffer 1275 | # to db and reset the buffer counter 1276 | pstmt.executeBulkUpdate(maxRowBufferWindow+1) 1277 | rowBufferIdx = 0 1278 | else: 1279 | inc rowBufferIdx 1280 | 1281 | if rowBufferIdx > 0: 1282 | # probe if unsent rows present 1283 | pstmt.executeBulkUpdate(rowBufferIdx) 1284 | 1285 | template withTransaction*(dbconn: OracleConnection, 1286 | body: untyped) = 1287 | ## used to encapsulate the operation with a transaction. 1288 | ## if an exception is thrown (within the body) the 1289 | ## transaction will be rolled back. 1290 | ## note that a dml operation creates a implicit transaction. 1291 | ## the DpiResult is used as a feedback channel on 1292 | ## the commit/rollback-operation. at the 1293 | ## moment it contains only the returncode from the commit/rolback ODPI-C call 1294 | block: 1295 | try: 1296 | body 1297 | if DpiResult(dpiConn_commit(dbconn.connection)).isFailure: 1298 | raise newException(IOError, "withTransaction: " & 1299 | getErrstr(rs.relatedConn.context.oracleContext) ) 1300 | {.effects.} 1301 | except: 1302 | discard dpiConn_rollback(dbconn.connection) 1303 | raise 1304 | 1305 | 1306 | template withPreparedStatement*(pstmt: var PreparedStatement, body: untyped) {.dirty.} = 1307 | ## releases the preparedStatement after leaving 1308 | ## the block. 1309 | # FIXME: prevent nesting 1310 | block: 1311 | try: 1312 | body 1313 | finally: 1314 | pstmt.destroy 1315 | 1316 | proc executeDDL*(conn: var OracleConnection, 1317 | sql: var SqlQuery, 1318 | dpiMode: uint32 = DpiModeExec.DEFAULTMODE.ord) = 1319 | ## convenience template to execute a ddl statement or pl/sql block 1320 | ## without parameters ( no results returned ) 1321 | var rs: ResultSet 1322 | var p: PreparedStatement 1323 | newPreparedStatement(conn, sql, p) 1324 | 1325 | withPreparedStatement(p): 1326 | discard dpiStmt_getInfo(p.pStmt, p.statementInfo.addr) 1327 | if p.statementInfo.isPLSQL == 1.int or p.statementInfo.isDML == 0.int: 1328 | p.executeStatement(rs, dpiMode) 1329 | else: 1330 | raise newException(IOError, "executeDDL: " & 1331 | " statement is no DDL. please use executeStatement instead.") 1332 | {.effects.} 1333 | 1334 | proc introspectObjectType( base: OracleObjTypeRef , attr : ptr dpiObjectAttr ) : OracleObjTypeRef = 1335 | ## introspects the attribute - raises exception if the attribute is not type: OBJECT 1336 | ## or the object's column was already introspected 1337 | discard 1338 | 1339 | 1340 | proc lookupObjectType*(conn : var OracleConnection, 1341 | typeName : string) : OracleObjTypeRef = 1342 | ## database-object-type lookup - needed for variable-object-binding. 1343 | ## the returned OracleObjTypeRef is always bound to a specific connection. 1344 | ## the typeName can be prefixed with the schemaname the object resides. 1345 | ## if the OracleObjTypeRef is no longer used, the internal resources must be freed 1346 | ## with "releaseOracleObjType" or utilize the helper template "withOracleObjType" 1347 | let tname : cstring = $typeName 1348 | result = OracleObjTypeRef() 1349 | result.relatedConn = conn 1350 | var objHdl : ptr dpiObjectType 1351 | echo "lookupObjectType: " & typeName 1352 | if DpiResult(dpiConn_getObjectType(conn.connection, 1353 | tname, 1354 | tname.len.uint32, 1355 | objHdl.addr)).isFailure: 1356 | raise newException(IOError, "lookupObjectType: " & 1357 | getErrstr(conn.context.oracleContext)) 1358 | {.effects.} 1359 | result.initObjectTypeHdl(objHdl) 1360 | return result 1361 | 1362 | 1363 | template isCollection*( obj : OracleObjRef ) : bool = 1364 | ## checks if the element is a collection. true or false 1365 | ## is returned and no exception is thrown 1366 | obj.objType.isCollection 1367 | 1368 | 1369 | template setupObjBufferedColumn( obj : OracleObjRef ) = 1370 | ## internal template to initialize 1371 | ## the objects column buffer. 1372 | ## the objects objType must be already initialized. 1373 | ## all attributes are evaluated and extra memory is allocated 1374 | ## in case of string and byte-types 1375 | var buffbase : int 1376 | var attrlen : int = 1 1377 | let dpiSize = sizeof(dpiData) 1378 | 1379 | if obj.objType.attributes.len > 0: 1380 | attrlen = attrlen + obj.objType.attributes.len 1381 | obj.bufferedColumn = newSeq[ptr dpiData](obj.objType.attributes.len) 1382 | # alloc one chunk to hold all dpiData structs 1383 | # allocate one extra chunk for the dataHelper ptr 1384 | buffbase = cast[int](alloc0( attrlen * dpiSize )) 1385 | # alloc0 is used because a connection should not shared between threads 1386 | var buffbasetmp = buffbase + dpiSize 1387 | # jump over first chunk 1388 | 1389 | for i in obj.bufferedColumn.low .. obj.bufferedColumn.high: 1390 | obj.bufferedColumn[i] = cast[ptr dpiData](buffbasetmp) 1391 | buffbasetmp = buffbasetmp + dpiSize 1392 | else: 1393 | buffbase = cast[int](alloc0( attrlen * dpiSize )) 1394 | 1395 | obj.dataHelper = cast[ptr dpiData](buffbase) 1396 | 1397 | var totalbytes = 0 1398 | 1399 | var colmap = newTable[int,ColumnType]() 1400 | 1401 | for i,column in obj.objType.columnTypeList: 1402 | if column.nativeType == DpiNativeCType.BYTES: 1403 | if not column.sizeIsBytes: 1404 | raise newException(IOError,"client_size_types in character not implemented!") 1405 | totalbytes += column.colsize 1406 | colmap[i] = column 1407 | # eval all column and detect DpiNativeCType.BYTES ones 1408 | # allocate one block, compute pointers and init dpiData section 1409 | obj.bufferedBytesTypeBase = cast[ptr byte](alloc0( totalbytes + 1 )) 1410 | 1411 | var buffer : ptr dpiData 1412 | var segptr : int = cast[int](obj.bufferedBytesTypeBase) 1413 | 1414 | for colnum,coltype in colmap: 1415 | # pointer computation and init each dpiData segment 1416 | buffer = obj.bufferedColumn[colnum] 1417 | buffer.value.asBytes.length = coltype.colSize.uint32 1418 | buffer.value.asBytes.ptr = cast[cstring](segptr) 1419 | segptr += coltype.colSize 1420 | 1421 | proc newOracleObject*( objType : OracleObjTypeRef ) : OracleObjRef = 1422 | ## creates a new oracle object with the specified OracleObjTypeRef which can be used 1423 | ## for binding or reading/fetching from the database. 1424 | ## at the moment subobjects or mutual recursive types are not supported. 1425 | ## ODPI-C and internal resources are hold till releaseOracleObject is called. 1426 | ## variable length references must stay valid till the object sent to the database. 1427 | ## for collection types use: newOracleCollection. 1428 | ## if the native type is not of DpiNativeCType.OBJECT or an ODPI-C error happens 1429 | ## an IOError is thrown. 1430 | 1431 | if objType.isCollection: 1432 | raise newException(IOError, "newOracleObject: " & 1433 | "objectType is a collection. use newOracleCollection instead. type was: " & $objType ) 1434 | {.effects.} 1435 | result = OracleObjRef() 1436 | if DpiResult(dpiObjectType_createObject(objType.baseHdl,result.objHdl.addr)).isFailure: 1437 | raise newException(IOError, "newOracleObject: " & 1438 | getErrstr(objType.relatedConn.context.oracleContext)) 1439 | {.effects.} 1440 | result.objType = objType 1441 | 1442 | # setup buffered columns 1443 | result.setupObjBufferedColumn 1444 | 1445 | template newOracleObjectWrapper(coll : OracleCollectionRef, handle : ptr dpiObject ) : OracleObjRef = 1446 | ## internal proc to construct the object wrapper out of a existing collection 1447 | ## the obtained object is bound to the specified collection-type 1448 | result = OracleObjRef() 1449 | result.objType = coll.objType.childObjectType 1450 | result.objHdl = handle 1451 | result.setupObjBufferedColumn 1452 | result 1453 | 1454 | 1455 | template initOracleCollection(collType: OracleObjTypeRef, collection: OracleCollectionRef) = 1456 | ## initializes a OracleCollection with the params out of the associated OracleObjType 1457 | collection.objType = collType 1458 | collection.setupObjBufferedColumn 1459 | collection.elementHelper = cast[ptr dpiData](alloc0( sizeof(dpiData) )) 1460 | collection.incarnatedChilds = newTable[int, OracleObjRef]() 1461 | 1462 | 1463 | proc newOracleCollection*(collectionType : OracleObjTypeRef ) : OracleCollectionRef = 1464 | ## create a new oracle collection object out of the OracleObjTypeRef, which can be used 1465 | ## for binding(todo:eval) or reading/fetching from the database. 1466 | ## ODPI-C and internal resources are hold till releaseOracleCollection is called 1467 | ## (see withCollection helper template). 1468 | ## variable length references must stay valid till the object sent to the database. 1469 | ## for collection types use: newOracleCollection 1470 | if not collectionType.isCollection: 1471 | raise newException(IOError, "newOracleCollection: " & 1472 | "objectType is not a collection-type and was: " & $collectionType ) 1473 | {.effects.} 1474 | result = OracleCollectionRef() 1475 | 1476 | collectionType.initOracleCollection(result) 1477 | 1478 | if DpiResult(dpiObjectType_createObject(collectionType.baseHdl,result.objHdl.addr)).isFailure: 1479 | raise newException(IOError, "newOracleObject: " & 1480 | getErrstr(collectionType.relatedConn.context.oracleContext)) 1481 | {.effects.} 1482 | 1483 | 1484 | proc releaseOracleObject*( obj : OracleObjRef ) = 1485 | ## releases the objects internal references and deallocs buffermem. 1486 | dealloc(obj.dataHelper) 1487 | dealloc(obj.bufferedBytesTypeBase) 1488 | # fetch chunkbase 1489 | 1490 | if DpiResult(dpiObject_release(obj.objHdl)).isFailure: 1491 | raise newException(IOError, "releaseOracleObject: " & 1492 | getErrstr(obj.objType.relatedConn.context.oracleContext)) 1493 | {.effects.} 1494 | 1495 | proc releaseOracleCollection*(obj : OracleCollectionRef ) = 1496 | ## releases the objects internal references and deallocs buffermem. 1497 | ## bound objects created by the frontend must be released separately 1498 | ## before calling this proc 1499 | dealloc(obj.elementHelper) 1500 | 1501 | if obj.incarnatedChilds.len > 0: 1502 | for v in obj.incarnatedChilds.mvalues: 1503 | releaseOracleObject(v) 1504 | 1505 | obj.incarnatedChilds.clear 1506 | 1507 | template lookupObjAttrIndexByName( objType : OracleObjTypeRef, attrName : string ) : int = 1508 | ## used by the attributeValue getter/setter to obtain the index by name. 1509 | ## the attrName must be always UPPERCASE. 1510 | var ctypeidx = -1 1511 | for i in objType.columnTypeList.low .. objType.columnTypeList.high: 1512 | if cmp(attrName, objType.columnTypeList[i].name) == 0: 1513 | ctypeidx = i 1514 | break; 1515 | 1516 | if ctypeidx == -1: 1517 | raise newException(IOError, "lookupObjAttrIndexByName: " & 1518 | " attrName " & attrName & " not found!") 1519 | {.effects.} 1520 | ctypeidx 1521 | 1522 | template lookupObjAttrIndexByName( obj : OracleObjRef, attrName : string ) : int = 1523 | lookupObjAttrIndexByName(obj.objType,attrName) 1524 | 1525 | template lookupRowIndexByName( ps : ptr PreparedStatement, attrName : string) : int = 1526 | var rowidx = -1 1527 | 1528 | for i in ps.rsOutputCols.low .. ps.rsOutputCols.high: 1529 | if cmp(attrName, ps.rsOutputCols[i].columnType.name) == 0: 1530 | rowidx = i 1531 | break; 1532 | 1533 | if rowidx == -1: 1534 | raise newException(IOError, "lookupRowIndexByName: " & 1535 | " attrName " & attrName & " not found!") 1536 | {.effects.} 1537 | rowidx 1538 | 1539 | proc getAttributeValue( obj : OracleObjRef, idx : int ) : ptr dpiData = 1540 | ## internal getter for fetching the value of a type(dbObject) 1541 | ## out of the odpic-domain 1542 | let ctype = obj.objType.columnTypeList[idx] 1543 | let attr = obj.objType.attributes[idx] 1544 | #FIXME: implement: If the native type is DPI_NATIVE_TYPE_BYTES and the Oracle type of the attribute 1545 | # is DPI_ORACLE_TYPE_NUMBER, a buffer must be supplied in the value.asBytes.ptr attribute 1546 | # and the maximum length of that buffer must be supplied in the value.asBytes.length attribute 1547 | # before calling this function. 1548 | # if the returned handle is an object, it must be freed with dpiObject_release 1549 | if DpiResult(dpiObject_getAttributeValue( 1550 | obj.objHdl, 1551 | attr, 1552 | ctype.nativeType.dpiNativeTypeNum, 1553 | obj.bufferedColumn[idx] ) 1554 | ).isFailure: 1555 | raise newException(IOError, "getAttributeValue: " & 1556 | getErrstr(obj.objType.relatedConn.context.oracleContext)) 1557 | {.effects.} 1558 | 1559 | return obj.bufferedColumn[idx] 1560 | 1561 | template getCollection( row : DpiRow, colIdx : int) : OracleCollectionRef = 1562 | ## experimental getter for resultSet collection handling 1563 | ## the object reference returned must be freed (see template withOracleCollection) 1564 | var result = OracleCollectionRef() 1565 | row.rset.rsOutputCols[colIdx].rObjectTypeRef.initOracleCollection(result) 1566 | result.objHdl = row[colIdx].value.asObject 1567 | result 1568 | 1569 | template getColumnIndexByName(rset : ptr ResultSet, colname : string) : int = 1570 | rset.lookupRowIndexByName 1571 | 1572 | template getCollection( row : DpiRow, colName : string ) : OracleCollectionRef = 1573 | ## experimental getter for resultSet collection handling 1574 | ## the object reference returned must be freed 1575 | getCollection(row,row.rset.lookupRowIndexByName(colname)) 1576 | 1577 | 1578 | proc setAttributeValue( obj : OracleObjRef, idx: int ) = 1579 | ## internal setter for propagating the value of 1580 | ## a type(dbObject) into odpi-c domain 1581 | let ctype = obj.objType.columnTypeList[idx] 1582 | let attr = obj.objType.attributes[idx] 1583 | 1584 | if DpiResult(dpiObject_setAttributeValue( 1585 | obj.objHdl, 1586 | attr, 1587 | ctype.nativeType.dpiNativeTypeNum, 1588 | obj.bufferedColumn[idx]) 1589 | ).isFailure: 1590 | raise newException(IOError, "setAttributeValue: " & 1591 | getErrstr(obj.objType.relatedConn.context.oracleContext)) 1592 | {.effects.} 1593 | 1594 | 1595 | 1596 | proc copyOracleObj*( obj : OracleObjRef ) : OracleObjRef = 1597 | ## copies an object and returns a new independent new one 1598 | ## which must be released if no longer needed - it it's added to a 1599 | ## collection this is done automatically 1600 | result = OracleObjRef() 1601 | result.objType = obj.objType 1602 | 1603 | if DpiResult(dpiObject_copy(obj.objHdl,result.objHdl.addr)).isFailure: 1604 | raise newException(IOError, "copyOracleObj: " & 1605 | getErrstr(obj.objType.relatedConn.context.oracleContext)) 1606 | {.effects.} 1607 | 1608 | result.setupObjBufferedColumn 1609 | 1610 | proc copyOracleColl*( obj : OracleCollectionRef ) : OracleCollectionRef = 1611 | ## copies an object and returns a new independent new one 1612 | ## which must be released if no longer needed 1613 | result = OracleCollectionRef() 1614 | result.objType = obj.objType 1615 | 1616 | result.objType.childObjectType = obj.objType.childObjectType 1617 | 1618 | if DpiResult(dpiObject_copy(obj.objHdl,result.objHdl.addr)).isFailure: 1619 | raise newException(IOError, "copyOracleColl: " & 1620 | getErrstr(obj.objType.relatedConn.context.oracleContext)) 1621 | {.effects.} 1622 | 1623 | result.setupObjBufferedColumn 1624 | result.elementHelper = cast[ptr dpiData](alloc0( sizeof(dpiData) )) 1625 | result.incarnatedChilds = newTable[int, OracleObjRef]() 1626 | 1627 | template withOracleObjType*( objtype : OracleObjTypeRef, body: untyped) = 1628 | ## releases the objectTypes resources after leaving 1629 | ## the block. exceptions are not catched. 1630 | try: 1631 | body 1632 | finally: 1633 | objtype.releaseOracleObjType 1634 | 1635 | template withOracleObject*( obj : OracleObjRef, body: untyped) = 1636 | ## releases the objects resources after leaving 1637 | ## the block. exceptions are not catched. 1638 | try: 1639 | body 1640 | finally: 1641 | obj.releaseOracleObject 1642 | 1643 | template withOracleCollection*( obj : OracleCollectionRef, body: untyped) = 1644 | ## releases the collections resources (and all bound objects) 1645 | ## after leaving the block. exceptions are not catched. 1646 | try: 1647 | body 1648 | finally: 1649 | obj.releaseOracleCollection 1650 | 1651 | 1652 | proc fetchCollectionSize*( collObj : OracleCollectionRef ) : int = 1653 | ## fetches the size of a collection object. if the object 1654 | ## is not a collection an IOException is thrown 1655 | var csize : int32 1656 | if DpiResult(dpiObject_getSize(collObj.objHdl,csize.addr)).isFailure: 1657 | raise newException(IOError, "fetchCollectionSize: " & 1658 | getErrstr(collObj.objType.relatedConn.context.oracleContext)) 1659 | {.effects.} 1660 | 1661 | result = csize.int 1662 | 1663 | proc deleteElementByIndex*( collObj: OracleCollectionRef, index : int ) = 1664 | ## delete an element from the collection. the element itself 1665 | ## is returned and must be freed manually if needed. keep in mind that the 1666 | ## collection is sparse. 1667 | if DpiResult(dpiObject_deleteElementByIndex(collObj.objHdl,index.int32)).isFailure: 1668 | raise newException(IOError, "deleteElementByIndex: " & 1669 | getErrstr(collObj.objType.relatedConn.context.oracleContext)) 1670 | {.effects.} 1671 | # check if element is already present 1672 | if collObj.incarnatedChilds.hasKey(index): 1673 | var obj = collObj.incarnatedChilds[index] 1674 | releaseOracleObject(obj) 1675 | 1676 | proc isElementPresent*( collObj : OracleCollectionRef, index : int) : bool = 1677 | ## probes if the element is present at the specified index. 1678 | ## returns true if so; otherwise false. 1679 | ## an IOException is thrown if the given object is not a collection 1680 | var exists : cint = 0 1681 | if DpiResult(dpiObject_getElementExistsByIndex(collObj.objHdl, 1682 | index.int32, 1683 | exists.addr)).isFailure: 1684 | raise newException(IOError, "isElementPresent: " & 1685 | getErrstr(collObj.objType.relatedConn.context.oracleContext)) 1686 | {.effects.} 1687 | if exists == 1 : true else: false 1688 | 1689 | 1690 | proc getFirstIndex*( collObj : OracleCollectionRef ) : tuple[index: int, isPresent : bool] = 1691 | ## returns the first used index of a collection. isPresent indicates if 1692 | ## there was an index position found. 1693 | var idx : int32 = 0 1694 | var present : cint = 0 1695 | if DpiResult(dpiObject_getFirstIndex(collObj.objHdl, 1696 | idx.addr, 1697 | present.addr)).isFailure: 1698 | raise newException(IOError, "getFirstIndex: " & 1699 | getErrstr(collObj.objType.relatedConn.context.oracleContext)) 1700 | {.effects.} 1701 | if present == 1 : result.isPresent = true else: result.isPresent = false 1702 | result.index = idx.int 1703 | 1704 | proc getLastIndex*( collObj : OracleCollectionRef ) : tuple[index: int, isPresent : bool] = 1705 | ## returns the last used index of a collection. isPresent indicates if 1706 | ## there was an index position found. an IOException is thrown if the 1707 | ## object is not a collection. 1708 | var idx : int32 = 0 1709 | var present : cint = 0 1710 | if DpiResult(dpiObject_getLastIndex(collObj.objHdl, 1711 | idx.addr, 1712 | present.addr)).isFailure: 1713 | raise newException(IOError, "getLastIndex: " & 1714 | getErrstr(collObj.objType.relatedConn.context.oracleContext)) 1715 | {.effects.} 1716 | if present == 1 : result.isPresent = true else: result.isPresent = false 1717 | result.index = idx.int 1718 | 1719 | proc getNextIndex*( collObj : OracleCollectionRef, 1720 | index : int ) : tuple[index: int, isPresent : bool] = 1721 | ## returns the next used index from the given index position of a collection. 1722 | ## isPresent indicates if there was an index position found. 1723 | ## an IOException is thrown if the 1724 | ## object is not a collection. 1725 | var idx : int32 = 0 1726 | var present : cint = 0 1727 | if DpiResult(dpiObject_getNextIndex(collObj.objHdl,index.int32, 1728 | idx.addr, 1729 | present.addr)).isFailure: 1730 | raise newException(IOError, "getNextIndex: " & 1731 | getErrstr(collObj.objType.relatedConn.context.oracleContext)) 1732 | {.effects.} 1733 | if present == 1 : result.isPresent = true else: result.isPresent = false 1734 | result.index = idx.int 1735 | 1736 | 1737 | proc getPreviousIndex*( collObj : OracleCollectionRef, 1738 | index : int ) : tuple[index: int, isPresent : bool] = 1739 | ## returns the previous used index from the given index position of a collection. 1740 | ## isPresent indicates if there was an index position found. 1741 | ## an IOException is thrown if the 1742 | ## object is not a collection. 1743 | var idx : int32 = 0 1744 | var present : cint = 0 1745 | if DpiResult(dpiObject_getPrevIndex(collObj.objHdl,index.int32, 1746 | idx.addr, 1747 | present.addr)).isFailure: 1748 | raise newException(IOError, "getPreviousIndex: " & 1749 | getErrstr(collObj.objType.relatedConn.context.oracleContext)) 1750 | {.effects.} 1751 | if present == 1 : result.isPresent = true else: result.isPresent = false 1752 | result.index = idx.int 1753 | 1754 | proc appendElement*( collObj : OracleCollectionRef, 1755 | obj2append : OracleObjRef) = 1756 | ## appends the object (param: obj) to ( param : collObj) at the 1757 | ## end of the collection. 1758 | ## throws an IOError if the collectionObject 1759 | ## is not a collection or if the object to append is 1760 | ## already related to a collection 1761 | ## or an internal error occurs. 1762 | 1763 | dpiData_setObject(obj2append.dataHelper,obj2append.objHdl) 1764 | obj2append.dataHelper.setNotDbNull 1765 | 1766 | if DpiResult(dpiObject_appendElement(collObj.objHdl, 1767 | DpiNativeCType.Object.ord, 1768 | obj2append.dataHelper)).isFailure: 1769 | raise newException(IOError, "appendElement: " & 1770 | getErrstr(obj2append.objType.relatedConn.context.oracleContext)) 1771 | {.effects.} 1772 | var idx = getLastIndex(collObj) 1773 | collObj.incarnatedChilds[idx.index] = obj2append 1774 | 1775 | proc trimCollection*( collObj : OracleCollectionRef, resizeVal : int ) = 1776 | ## Trims the given numer of elements from the end of the collection. 1777 | ## an IOException is thrown if the object is not a collection. 1778 | ## the elements itself must be freed manually. 1779 | if DpiResult(dpiObject_trim(collObj.objHdl,resizeVal.uint32)).isFailure: 1780 | raise newException(IOError, "trimCollection: " & 1781 | getErrstr(collObj.objType.relatedConn.context.oracleContext)) 1782 | {.effects.} 1783 | # FIXME: TODO: adjust incarnatedChilds map 1784 | 1785 | proc setElementValue2Backend(collObj : OracleCollectionRef, 1786 | index : int ) = 1787 | ## internal collection object setter with index. the collObj.elementHelper 1788 | ## dpiData structure must already populated before calling this proc. 1789 | ## this proc is generic and collection dependent if the object-type 1790 | ## is another object or native type. 1791 | ## 1792 | ## an IOException is thrown if the object is not a collection 1793 | ## or an internal error occurs 1794 | if DpiResult(dpiObject_setElementValueByIndex(collObj.objHdl, 1795 | index.int32, 1796 | collObj.objType.elementDataTypeInfo.defaultNativeTypeNum, 1797 | collObj.elementHelper)).isFailure: 1798 | raise newException(IOError, "setElementValueByIndex: " & 1799 | getErrstr(collObj.objType.relatedConn.context.oracleContext)) 1800 | {.effects.} 1801 | 1802 | proc getElementValueFromBackend(collObj : OracleCollectionRef, 1803 | index : int) : ptr dpiData = 1804 | ## internal collection object getter with index. returns the 1805 | ## populated dpiData structure. due to the ptr handling an explicit 1806 | ## setter is not provided. can be used both for DpiNativeCType.OBJECT 1807 | ## or native types (the value from member dpiDataTypeInfo.defaultNativeTypeNum is used) 1808 | ## an IOException is thrown if the object is not a collection 1809 | ## or an internal error occurs. 1810 | ## FIXME: implement external buffer for 1811 | ## DPI_NATIVE_TYPE_BYTES and the Oracle type of the attribute is DPI_ORACLE_TYPE_NUMBER 1812 | ## 1813 | ## https://oracle.github.io/odpi/doc/functions/dpiObject.html 1814 | if DpiResult(dpiObject_getElementValueByIndex(collObj.objHdl,index.int32, 1815 | collObj.objType.elementDataTypeInfo.defaultNativeTypeNum, 1816 | collObj.elementHelper)).isFailure: 1817 | raise newException(IOError, "getElementValueByIndex: " & 1818 | getErrstr(collObj.objType.relatedConn.context.oracleContext)) 1819 | {.effects.} 1820 | # retrieve child-type with DpiNativeCType.Object.ord 1821 | ## TODO: how to introspect dpiData? 1822 | ## FIXME: dpiObject_release called? 1823 | ## collObj.objType.elementDataTypeInfo.defaultNativeTypeNum 1824 | return collObj.elementHelper 1825 | 1826 | proc getElementValue( srcObj : ptr dpiData, srcType : OracleObjTypeRef, idx : int) : ptr dpiObject = 1827 | # returns the value dpiObject *dpiData_getObject(dpiData *data) 1828 | # TODO: implement 1829 | discard 1830 | 1831 | proc setCollectionElement(collObj : OracleCollectionRef, 1832 | index : int, 1833 | value : OracleObjRef) = 1834 | ## sets a collection element at the specified index location. 1835 | ## before calling, the internal helper structure must be populated. 1836 | ## the first index location is 0 1837 | dpiData_setObject(collObj.elementHelper,value.objHdl) 1838 | collObj.setElementValue2Backend(index) 1839 | 1840 | proc getCollectionElement(collObj : OracleCollectionRef, index : int) : OracleObjRef = 1841 | ## fetches object buffer at the specified index. 1842 | ## if the related element type is not of type DpiNativeCType.OBJECT an IOException 1843 | ## is thrown. the collection index starts with 0. 1844 | ## if the object was already fetched, the internal tracked object is returned. 1845 | ## for native typed collection elements please use the 1846 | ## fetch/set templates. 1847 | if not collObj.objType. 1848 | elementDataTypeInfo.defaultNativeTypeNum == 1849 | DpiNativeCType.OBJECT.ord: 1850 | raise newException(IOError, "getCollectionElement: " & 1851 | "related native type must be OBJECT but was: " & 1852 | $collObj.objType.elementDataTypeInfo ) 1853 | {.effects.} 1854 | 1855 | if collObj.isElementPresent(index): 1856 | if collObj.incarnatedChilds.hasKey(index): 1857 | # echo "obj incarnated - read map" 1858 | # TODO: eval why it's called that often with "[][]" access 1859 | result = collObj.incarnatedChilds[index] 1860 | else: 1861 | let objptr = cast[ptr dpiObject](collObj.getElementValueFromBackend(index). 1862 | value.asObject) 1863 | result = newOracleObjectWrapper(collObj,objptr) 1864 | collObj.incarnatedChilds[index] = result 1865 | else: 1866 | raise newException(IOError,"getCollectionElement: no elem present at index " & $index) 1867 | 1868 | 1869 | proc setObject*(param : ParamTypeRef, rownum : int, value : OracleObjRef ) = 1870 | ## sets a bind parameter with value type: OracleObjRef 1871 | if DpiResult(dpiVar_setFromObject(param.paramVar, 1872 | rownum.uint32, 1873 | value.objHdl)).isFailure: 1874 | raise newException(IOError, "setObject: " & 1875 | getErrstr(value.objType.relatedConn.context.oracleContext)) 1876 | {.effects.} 1877 | 1878 | proc setCollection*(param : ParamTypeRef, rownum : int, value : OracleCollectionRef ) = 1879 | ## sets a bind parameter with value type: OracleCollectionRef 1880 | if DpiResult(dpiVar_setFromObject(param.paramVar, 1881 | rownum.uint32, 1882 | value.objHdl)).isFailure: 1883 | raise newException(IOError, "setCollection: " & 1884 | getErrstr(value.objType.relatedConn.context.oracleContext)) 1885 | {.effects.} 1886 | 1887 | 1888 | proc newTempLob*( conn : var OracleConnection, 1889 | lobtype : DpiLobType, 1890 | outlob : var Lob ) = 1891 | ## creates a new temp lob 1892 | if DpiResult(dpiConn_newTempLob(conn.connection, 1893 | lobtype.ord.dpiOracleTypeNum, 1894 | addr(outlob.lobref))).isFailure: 1895 | raise newException(IOError, "createConnection: " & 1896 | getErrstr(conn.context.oracleContext)) 1897 | {.effects.} 1898 | 1899 | # TODO: varray,blob,clob and AQ support 1900 | # TODO: FIXME: remove Objects bufferedColumn quirk 1901 | 1902 | include setfetchtypes 1903 | # includes all fetch/set templates 1904 | 1905 | when isMainModule: 1906 | ## the HR Schema is used (XE) for the following tests 1907 | ## and the connection is local sys. so all objects need to be schema prefixed 1908 | const 1909 | oracleuser: string = "sys" 1910 | pw: string = "" 1911 | connectionstr: string = """(DESCRIPTION = (ADDRESS = 1912 | (PROTOCOL = TCP) 1913 | (HOST = localhost) 1914 | (PORT = 1521)) 1915 | (CONNECT_DATA =(SERVER = DEDICATED) 1916 | (SERVICE_NAME = XEPDB1 ) 1917 | ))""" 1918 | 1919 | var octx: OracleContext 1920 | 1921 | newOracleContext(octx,DpiAuthMode.SYSDBA) 1922 | # create the context with authentication Method 1923 | # SYSDBA and default encoding (UTF8) 1924 | var conn: OracleConnection 1925 | 1926 | createConnection(octx, connectionstr, oracleuser, pw, conn) 1927 | # create the connection with the desired server, 1928 | # credentials and the context 1929 | 1930 | var query: SqlQuery = osql"""select 100 as col1, 'äöü' as col2 from dual 1931 | union all 1932 | select 200 , 'äöü2' from dual 1933 | union all 1934 | select 300 , 'äöü3' from dual 1935 | """ 1936 | # query is unused at the moment 1937 | 1938 | var query2: SqlQuery = osql""" select 1939 | rowid, 1940 | EMPLOYEE_ID, 1941 | FIRST_NAME, 1942 | LAST_NAME, 1943 | EMAIL, 1944 | PHONE_NUMBER, 1945 | HIRE_DATE, 1946 | JOB_ID, 1947 | SALARY, 1948 | COMMISSION_PCT, 1949 | MANAGER_ID, 1950 | DEPARTMENT_ID 1951 | from hr.employees where department_id = :param1 """ 1952 | 1953 | 1954 | var pstmt: PreparedStatement 1955 | 1956 | newPreparedStatement(conn, query2, pstmt, 10) 1957 | # create prepared statement for the specified query and 1958 | # the number of buffered result-rows. the number of buffered 1959 | # rows (window) is fixed and can't changed later on 1960 | var rs: ResultSet 1961 | 1962 | var param = addBindParameter(pstmt, 1963 | Int64ColumnTypeParam, 1964 | "param1") 1965 | param.setInt64(some(80.int64)) 1966 | # create and set the bind parameter. query bind 1967 | # parameter are always non-columnar. 1968 | 1969 | executeStatement(pstmt,rs) 1970 | # execute the statement. if the resulting rows 1971 | # fit into entire window we are done here. 1972 | 1973 | for i,ct in rs.resultColumnTypeIterator: 1974 | # consume columnTypes of the resultSet 1975 | echo "cname:" & rs.rsColumnNames[i] & " colnumidx: " & 1976 | $(i+1) & " " & $ct 1977 | 1978 | for row in rs.resultSetRowIterator: 1979 | # the result iterator fetches internally further 1980 | # rows if present 1981 | # example of value retrieval by columnname or index 1982 | # the column of the row is accessed by colnum or name 1983 | echo $row[0].fetchRowId & 1984 | " " & $row[1].fetchDouble & 1985 | " " & $row["FIRST_NAME"].fetchString & 1986 | " " & $row["LAST_NAME"].fetchString & 1987 | " " & $row[10].fetchInt64 & 1988 | " " & $row[11].fetchInt64 1989 | 1990 | echo "query1 executed - param department_id = 80 " 1991 | # reexecute preparedStatement with a different parameter value 1992 | param.setInt64(some(10.int64)) 1993 | executeStatement(pstmt, rs) 1994 | # the parameter is changed here and the query is 1995 | # re-executed 1996 | 1997 | for row in rs.resultSetRowIterator: 1998 | # retrieve column values by columnname or index 1999 | echo $row[0].fetchRowId & 2000 | " " & $row[1].fetchDouble & 2001 | " " & $row["FIRST_NAME"].fetchString & 2002 | " " & $row["LAST_NAME"].fetchString & 2003 | " " & $row[10].fetchInt64 & 2004 | " " & $row[11].fetchInt64 2005 | 2006 | echo "query1 executed 2nd run - param department_id = 10 " 2007 | 2008 | pstmt.destroy 2009 | # the prepared statement is destroyed and the 2010 | # resources are freed (mandatory). the withPreparedStatement 2011 | # template calls this implicit. 2012 | 2013 | # TODO: plsql example with types and select * from table( .. ) 2014 | 2015 | var refCursorQuery: SqlQuery = osql""" begin 2016 | open :1 for select 'teststr' StrVal from dual 2017 | union all 2018 | select 'teststr1' from dual; 2019 | open :refc2 for select first_name,last_name 2020 | from hr.employees 2021 | where department_id = :3; 2022 | end; """ 2023 | # implicit results example (ref cursor) 2024 | # FIXME: test typed plsql blocks 2025 | 2026 | var pstmt2 : PreparedStatement 2027 | # refcursor example 2028 | newPreparedStatement(conn, refCursorQuery, pstmt2, 1) 2029 | # this query block contains two independent refcursors 2030 | # and the snd is parameterised. 2031 | 2032 | withPreparedStatement(pstmt2): 2033 | discard pstmt2.addBindParameter(RefCursorColumnTypeParam,BindIdx(1)) 2034 | # to consume the refcursor a bindParameter is needed (bind by index) 2035 | discard pstmt2.addBindParameter(RefCursorColumnTypeParam,"refc2") 2036 | # example of bind by name (alternative to bind by index) 2037 | discard pstmt2.addBindParameter(Int64ColumnTypeParam,BindIdx(3)) 2038 | # filter: parameter for department_id 2039 | pstmt2[3.BindIdx].setInt64(some(80.int64)) 2040 | # sets the value of the bind parameter 2041 | 2042 | pstmt2.executeStatement(rs) 2043 | 2044 | echo "refCursor 1 results: " 2045 | var refc: ResultSet 2046 | 2047 | pstmt2.openRefCursor(1.BindIdx, refc, 1, DpiModeExec.DEFAULTMODE.ord) 2048 | # opens the refcursor. once consumed it can't be reopended (TODO: check 2049 | # if that's a ODPI-C limitation) 2050 | for row in refc.resultSetRowIterator: 2051 | echo $row[0].fetchString 2052 | 2053 | echo "refCursor 2 results: filter with department_id = 80 " 2054 | 2055 | var refc2 : ResultSet 2056 | pstmt2.openRefCursor("refc2", refc2, 10, DpiModeExec.DEFAULTMODE.ord) 2057 | 2058 | for row in refc2.resultSetRowIterator: 2059 | echo $row[0].fetchString & " " & $row[1].fetchString 2060 | 2061 | 2062 | # object-type for testing the obj-features 2063 | # nested obj not implemented at the moment. 2064 | var demoCreateObj : SqlQuery = osql""" 2065 | create or replace type HR.DEMO_OBJ FORCE as object ( 2066 | NumberValue number(15,8), 2067 | StringValue varchar2(60), 2068 | FixedCharValue char(10), 2069 | DateValue date, 2070 | TimestampValue timestamp, 2071 | RawValue raw(25) 2072 | ); 2073 | """ 2074 | conn.executeDDL(demoCreateObj) 2075 | 2076 | var ctableq = osql""" CREATE TABLE HR.DEMOTESTTABLE( 2077 | C1 VARCHAR2(20) NOT NULL 2078 | , C2 NUMBER(5,0) 2079 | , C3 raw(20) 2080 | , C4 NUMBER(5,3) 2081 | , C5 NUMBER(15,5) 2082 | , C6 TIMESTAMP 2083 | , C7 HR.DEMO_OBJ 2084 | , CONSTRAINT DEMOTESTTABLE_PK PRIMARY KEY(C1) 2085 | ) """ 2086 | 2087 | conn.executeDDL(ctableq) 2088 | echo "table hr.demotesttable created" 2089 | 2090 | var demoObjType = conn.lookupObjectType("HR.DEMO_OBJ") 2091 | 2092 | var insertStmt: SqlQuery = 2093 | osql""" insert into HR.DEMOTESTTABLE(C1,C2,C3,C4,C5,C6,C7) 2094 | values (:1,:2,:3,:4,:5,:6,:7) """ 2095 | 2096 | var pstmt3 : PreparedStatement 2097 | conn.newPreparedStatement(insertStmt, pstmt3, 10) 2098 | # bulk insert example. 21 rows are inserted with a buffer 2099 | # window of 9 and 3 rows. 2100 | # once the preparedStatement is instanciated - 2101 | # the buffer row window is fixed and can't resized 2102 | 2103 | discard pstmt3.addArrayBindParameter(newStringColTypeParam(20), 2104 | BindIdx(1), 10) 2105 | discard pstmt3.addArrayBindParameter(Int64ColumnTypeParam, 2106 | BindIdx(2), 10) 2107 | discard pstmt3.addArrayBindParameter(newRawColTypeParam(5), 2108 | 3.BindIdx,10) 2109 | discard pstmt3.addArrayBindParameter(FloatColumnTypeParam, 2110 | 4.BindIdx,10) 2111 | discard pstmt3.addArrayBindParameter(DoubleColumnTypeParam, 2112 | 5.BindIdx,10) 2113 | discard pstmt3.addArrayBindParameter(ZonedTimestampTypeParam, 2114 | 6.BindIdx,10) 2115 | discard pstmt3.addObjectBindParameter(7.BindIdx,demoObjType,10) 2116 | # if a nativeType/oracleType combination is not implemented by ODPI-C 2117 | # you will receive an exception here 2118 | 2119 | var rset: ResultSet 2120 | # TODO: checkout new prefetch size feature 2121 | # https://cx-oracle.readthedocs.io/en/latest/user_guide/tuning.html#choosing-values-for-arraysize-and-prefetchrows 2122 | pstmt3.withPreparedStatement: 2123 | var tseq = some(@[(0xAA).byte,0xBB.byte,0xCC.byte]) 2124 | var pks = @[some("test_äüö0"),some("test_äüö1"),some("test_äüö2"), 2125 | some("test_äüö3"),some("test_äüö4"),some("5"), 2126 | some("6"),some("7"),some("8"),some("9"), 2127 | some("10"),some("11"),some("12")] 2128 | # strings and bytearrays are pointer types and need to stay valid till the transaction is committed. 2129 | # exception: OracleObject types (each objcolumn of string/bytearray are buffered) 2130 | 2131 | var objBuffer = newSeq[OracleObjRef](13) 2132 | for i in 0..12: 2133 | echo "new object" 2134 | objBuffer[i] = demoObjType.newOracleObject 2135 | 2136 | # init obj buffer for the next example 2137 | conn.withTransaction: # commit after this block 2138 | # cleanup of preparedStatement beyond this block 2139 | var varc2 : Option[int64] 2140 | 2141 | #var obj1 = demoObjType.newOracleObject 2142 | #obj1.setDouble(0,some(22.float64)) 2143 | #obj1.setString(1,some("teststring")) 2144 | 2145 | for i,bufferRowIdx in pstmt3.bulkBindIterator(12,8): 2146 | # i: count over 13 entries - with a buffer window of 9 elements 2147 | # if the buffer window is filled 2148 | # contents are flushed to the database. 2149 | if i == 9: 2150 | varc2 = none(int64) # simulate dbNull 2151 | else: 2152 | varc2 = some(i.int64) 2153 | # unfortunately setString/setBytes have a different 2154 | # API than the value-parameter types 2155 | echo pks[i].get 2156 | pstmt3[1.BindIdx][bufferRowIdx].setString(pks[i]) #pk 2157 | pstmt3[2.BindIdx][bufferRowIdx].setInt64(varc2) 2158 | pstmt3[3.BindIdx][bufferRowIdx].setBytes(tseq) 2159 | pstmt3[4.BindIdx][bufferRowIdx].setFloat(some(i.float32+0.12.float32)) 2160 | pstmt3[5.BindIdx][bufferRowIdx].setDouble(some(i.float64+99.12345.float64)) 2161 | pstmt3[6.BindIdx][bufferRowIdx].setDateTime(some(getTime().local)) 2162 | objBuffer[i].setDouble("NUMBERVALUE",some(i.float64)) 2163 | # access by columnName 2164 | objBuffer[i].setString(1,some("teststring" & $i)) 2165 | # or columnIndex 2166 | pstmt3[7.BindIdx].setObject(bufferRowIdx,objBuffer[i]) 2167 | 2168 | for i in objBuffer.low..objBuffer.high: 2169 | objBuffer[i].releaseOracleObject 2170 | objBuffer.setLen(0) 2171 | 2172 | # example: reuse of preparedStatement and insert another 8 rows 2173 | # with a buffer window of 3 elements 2174 | var pks2 = @[some("test_äüö15"),some("test_äüö16"),some("test_äüö17"),some("test_äüö18"), 2175 | some("test_äüö19"),some("20"),some("21"),some("22")] 2176 | 2177 | conn.withTransaction: 2178 | var seqnone = none(seq[byte]) 2179 | for i,bufferRowIdx in pstmt3.bulkBindIterator(7,2): 2180 | pstmt3[1.BindIdx][bufferRowIdx].setString(pks2[i]) #pk 2181 | pstmt3[2.BindIdx][bufferRowIdx].setInt64(some(i.int64)) 2182 | pstmt3[3.BindIdx][bufferRowIdx].setBytes(seqnone) # dbNull 2183 | pstmt3[4.BindIdx][bufferRowIdx].setFloat(none(float32)) # dbNull 2184 | pstmt3[5.BindIdx][bufferRowIdx].setDouble(none(float64)) # dbNull 2185 | pstmt3[6.BindIdx][bufferRowIdx].setDateTime(none(DateTime)) # dbNull 2186 | pstmt3[7.BindIdx][bufferRowIdx].setDbNull 2187 | # TODO: eval howto set a bound object within existing buffer to dbNull 2188 | 2189 | var selectStmt: SqlQuery = osql"""select c1,c2,rawtohex(c3) 2190 | as c3,c4,c5,c6,c7 from hr.demotesttable""" 2191 | # read now the committed stuff 2192 | 2193 | var pstmt4 : PreparedStatement 2194 | var rset4 : ResultSet 2195 | 2196 | conn.newPreparedStatement(selectStmt, pstmt4, 5) 2197 | # test with smaller window: 5 rows are buffered internally for reading 2198 | 2199 | echo "prepare reading hr.demotesttable" 2200 | withPreparedStatement(pstmt4): 2201 | pstmt4.executeStatement(rset4) 2202 | for row in rset4.resultSetRowIterator: 2203 | echo $row[0].fetchString & " " & $row[1].fetchInt64 & 2204 | " " & $row[2].fetchString & 2205 | # " " & $fetchFloat(row[3].data) & 2206 | # TODO: eval float32 vals 2207 | " " & $row[4].fetchDouble & " " & $row[5].fetchDateTime 2208 | # FIXME: fetchFloat returns wrong values / dont work as expected 2209 | 2210 | echo "finished reading hr.demotesttable" 2211 | echo "prepared statement removed" 2212 | # drop the table 2213 | var dropStmt: SqlQuery = osql"drop table hr.demotesttable" 2214 | conn.executeDDL(dropStmt) 2215 | # cleanup - table drop 2216 | echo "HR.DEMOTESTTABLE dropped" 2217 | var demoInOUT : SqlQuery = osql""" 2218 | CREATE OR REPLACE FUNCTION HR.DEMO_INOUT 2219 | ( 2220 | PARAM1 IN VARCHAR2, 2221 | PARAM2 IN OUT NOCOPY VARCHAR2, 2222 | PARAM3 OUT NOCOPY VARCHAR2 2223 | ) RETURN VARCHAR2 AS 2224 | px1 varchar2(50); 2225 | BEGIN 2226 | if param2 is not null then 2227 | px1 := param1 || '_test_' || param2; 2228 | param3 := param2; 2229 | param2 := px1; 2230 | else 2231 | param3 := 'param2_was_null'; 2232 | end if; 2233 | RETURN 'FUNCTION_DEMO_INOUT_CALLED'; 2234 | END DEMO_INOUT; 2235 | """ 2236 | 2237 | conn.executeDDL(demoInOut) 2238 | # incarnate function 2239 | 2240 | var demoCallFunc = osql"""begin :1 := HR.DEMO_INOUT(:2, :3, :4); end; """ 2241 | # :1 result variable type varchar2 2242 | # :2 in variable type varchar2 2243 | # :3 inout variable type varchar2 2244 | # :4 out variable type varchar2 2245 | var callFunc : PreparedStatement 2246 | var callFuncResult : ResultSet 2247 | newPreparedStatement(conn,demoCallFunc,callFunc) 2248 | 2249 | withPreparedStatement(callFunc): 2250 | # call the function with direct parameter access 2251 | let param1 = callFunc.addBindParameter(newStringColTypeParam(50),BindIdx(1)) 2252 | let param2 = callFunc.addBindParameter(newStringColTypeParam(20),BindIdx(2)) 2253 | let param3 = callFunc.addBindParameter(newStringColTypeParam(20),BindIdx(3)) 2254 | let param4 = callFunc.addBindParameter(newStringColTypeParam(20),BindIdx(4)) 2255 | # direct param access in this example 2256 | var p1 = some("teststr äüö") 2257 | var p2 = some("p2") 2258 | param2.setString(p1) 2259 | param3.setString(p2) 2260 | callFunc.executeStatement(callFuncResult) 2261 | var r1 = param1.fetchString() 2262 | # fetch functions result 2263 | var r3 = param3.fetchString() 2264 | # fetch functions inout var 2265 | var r4 = param4.fetchString() 2266 | # fetch functions out var 2267 | 2268 | echo "param :1 (result) = " & r1.get 2269 | if r3.isSome: 2270 | echo "param :3 (inout) = " & r3.get 2271 | echo "param :4 (out) = " & r4.get 2272 | 2273 | var cleanupDemo : SqlQuery = osql" drop function hr.demo_inout " 2274 | conn.executeDDL(cleanupDemo) 2275 | 2276 | 2277 | # create collection type 2278 | var demoCreateColl : SqlQuery = osql""" 2279 | create or replace TYPE HR.DEMO_COLL IS TABLE OF HR.DEMO_OBJ; """ 2280 | 2281 | conn.executeDDL(demoCreateColl) 2282 | 2283 | var demoAggr : SqlQuery = osql""" 2284 | create or replace FUNCTION HR.DEMO_COLAGGR 2285 | -- naive collection aggregation (no real world example) 2286 | ( 2287 | PARAM1 IN HR.DEMO_COLL 2288 | ) RETURN VARCHAR2 AS 2289 | px1 varchar2(3000) := 'table_parameter_was_null'; 2290 | BEGIN 2291 | if param1 is not null then 2292 | px1 := ''; 2293 | if param1.count > 0 then 2294 | for i in param1.first .. param1.last 2295 | loop 2296 | px1 := px1 || param1(i).StringValue; 2297 | end loop; 2298 | else 2299 | px1 := 'table_is_empty'; 2300 | end if; 2301 | end if; 2302 | dbms_output.put_line(px1); 2303 | RETURN px1; 2304 | END DEMO_COLAGGR; 2305 | """ 2306 | 2307 | # create target function 2308 | conn.executeDDL(demoAggr) 2309 | 2310 | # lookup type and print results 2311 | echo "lookup demo_obj" 2312 | var objtype = conn.lookupObjectType("HR.DEMO_OBJ") 2313 | var obj = objtype.newOracleObject 2314 | 2315 | obj.setDouble(0,some(100.float64)) 2316 | obj.setString(1,some("teststring")) 2317 | echo $(obj[0].fetchDouble) 2318 | echo $(obj[1].fetchString) 2319 | 2320 | # not public 2321 | # value from buffer 2322 | 2323 | echo $obj.fetchDouble(0) # value from odpi-c 2324 | # public api 2325 | 2326 | var copyOf = obj.copyOracleObj 2327 | copyOf.setDouble(0,some(200.float64)) 2328 | 2329 | echo $copyOf.fetchDouble(0) # value from odpi-c 2330 | echo "lookup column-type" 2331 | 2332 | var colltype = conn.lookupObjectType("HR.DEMO_COLL") 2333 | echo "begin create oracle collection" 2334 | var collection = colltype.newOracleCollection 2335 | echo "oracleCollection created" 2336 | # echo $colltype.elementDataTypeInfo 2337 | var objinf : dpiObjectTypeInfo 2338 | 2339 | 2340 | echo "--------------------- begin collection type tests -----------------------" 2341 | 2342 | withOracleCollection(collection): 2343 | collection.appendElement(copyof) 2344 | collection.appendElement(obj) 2345 | 2346 | echo " size of collection: " & $collection.fetchCollectionSize 2347 | 2348 | var copyOfColl = collection.copyOracleColl 2349 | 2350 | echo "before copy: childtype is: " & $collection.objType.childObjectType.isNil 2351 | 2352 | 2353 | # copies the entire collection 2354 | withOracleCollection(copyOfColl): 2355 | echo " size of copied collection: " & 2356 | $copyOfColl.fetchCollectionSize 2357 | echo "elem 0 " & $copyOfColl.isElementPresent(0) 2358 | echo "elem 1 " & $copyOfColl.isElementPresent(1) 2359 | echo $copyOfColl.getFirstIndex 2360 | echo $copyOfColl.getLastIndex 2361 | 2362 | var res : ptr dpiObject 2363 | # begin testcode 2364 | if not copyOfColl.objType. 2365 | elementDataTypeInfo.defaultNativeTypeNum == 2366 | DpiNativeCType.OBJECT.ord: 2367 | raise newException(IOError, "getCollectionElement: " & 2368 | "related native type must be OBJECT but was: " & 2369 | $copyOfColl.objType.elementDataTypeInfo ) 2370 | 2371 | if copyOfColl.isElementPresent(0): 2372 | echo "elem 0 in collection present" 2373 | 2374 | else: 2375 | echo "elem 0 in collection not present. set to dbNull" 2376 | copyOfColl.elementHelper.setDbNull 2377 | # res = cast[ptr dpiObject](copyOfColl.elementHelper) 2378 | 2379 | # end testcode 2380 | echo "---------- end of coltype testcode ---------------" 2381 | # fetches the member objects attribute at index 0 of the collections 2382 | # member object at index 1 2383 | copyOfColl[0].setDouble(0,some(22.float64)) # = some(22.float64) 2384 | copyOfColl[1].setDouble(0,some(10.float64)) 2385 | echo $copyOfColl[1][0].fetchDouble 2386 | #fetch the first attribute of a collection member at attribute position 0 2387 | echo $copyOfColl[0][0].fetchDouble 2388 | #fetch the first attribute of a collection member at attribute position 0 2389 | 2390 | # initialize string fields and call the aggregate udf 2391 | echo "begin aggregate udf tests" 2392 | collection[0].setString(1,some("hello nim! ")) 2393 | collection[1].setString(1,some("from oracle ")) 2394 | copyOfColl[0].setDateTime(4,some(getTime().local)) 2395 | copyOfColl[1].setDateTime(4,some(getTime().local)) 2396 | 2397 | # TODO: call the procedure with select statement 2398 | # in another example 2399 | var demoCallAggr = osql"""begin :1 := HR.DEMO_COLAGGR(:2); end; """ 2400 | var callFuncAggr : PreparedStatement 2401 | var callFuncResultAggr : ResultSet 2402 | 2403 | newPreparedStatement(conn,demoCallAggr,callFuncAggr,5) 2404 | 2405 | withPreparedStatement(callFuncAggr): 2406 | # call the function with direct parameter access and object as parameter 2407 | let param1 = callFuncAggr.addBindParameter(newStringColTypeParam(500),BindIdx(1)) 2408 | let param2 = callFuncAggr.addObjectBindParameter(BindIdx(2),colltype,5) 2409 | param2.setObject(0,copyOfColl) # todo: consolidate API 2410 | callFuncAggr.executeStatement(callFuncResult) 2411 | echo "result of hr.demo_colaggr: " & $param1.fetchString() 2412 | 2413 | # same example with invocation from sql 2414 | var nestedtabStmt = osql""" 2415 | create table HR.NDEMO ( 2416 | ntestname varchar2(30) 2417 | , testcol hr.demo_coll 2418 | , CONSTRAINT NDEMO_PK PRIMARY KEY(ntestname) 2419 | ) 2420 | nested table testcol store as nested_demo_coll 2421 | """ 2422 | conn.executeDDL(nestedtabStmt) 2423 | echo "--------------------- nested table HR.NDEMO created -----------------------" 2424 | 2425 | var nestedtableInsert = osql""" insert into HR.NDEMO(NTESTNAME,TESTCOL) values (:1,:2) """ 2426 | 2427 | var nInsertPstmt : PreparedStatement 2428 | var nInsertRset : ResultSet 2429 | newPreparedStatement(conn,nestedtableInsert,nInsertPstmt,10) 2430 | 2431 | withPreparedStatement(nInsertPstmt): 2432 | conn.withTransaction: 2433 | let param1 = nInsertPstmt.addArrayBindParameter( 2434 | newStringColTypeParam(30), 2435 | BindIdx(1),10) 2436 | let param2 = nInsertPstmt.addObjectBindParameter(BindIdx(2),colltype,10) 2437 | var keys = @[some("testname1"),some("testname2"),some("testname3"), 2438 | some("testname4"),some("testname5"),some("testname6")] 2439 | for i,bufferRowIdx in nInsertPstmt.bulkBindIterator(5,0): 2440 | # commit after each row required or 2441 | # the objects contents are overwritten 2442 | copyOfColl[0].setString(1,some("hello nim! round " & $i)) 2443 | copyOfColl[1].setString(1,some("from oracle ")) 2444 | copyOfColl[0].setDateTime(4,some(getTime().local)) 2445 | copyOfColl[1].setDateTime(4,some(getTime().local)) 2446 | copyOfColl[1].setBytes(5,some(@[(0xAA+i).byte,0xBB,0xCC])) 2447 | # feed the copyOfColl collection 2448 | # update object per round 2449 | nInsertPstmt[1.BindIdx][bufferRowIdx].setString(keys[i]) 2450 | # access the cell via prepared statement and bindindex 2451 | nInsertPstmt[2.BindIdx].setCollection(bufferRowIdx,copyOfColl) 2452 | # variant2: param2.setCollection(bufferRowIdx,copyOfColl) 2453 | # access the cell directly via the bind parameter 2454 | 2455 | echo "insert finished " 2456 | var nestedtableSelect = osql" select NTESTNAME,TESTCOL from HR.NDEMO " 2457 | var nSelectPstmt : PreparedStatement 2458 | var nSelectRset : ResultSet 2459 | newPreparedStatement(conn,nestedtableSelect,nSelectPstmt,10) 2460 | echo "nested table select prepared" 2461 | withPreparedStatement(nSelectPstmt): 2462 | nSelectPstmt.executeStatement(nSelectRset) 2463 | # eval the result columns: 2464 | for i,pt in nSelectRset.resultParamTypeIterator: 2465 | echo "nested_table_column: " & $i & " type: " & $pt 2466 | 2467 | for row in nSelectRset.resultSetRowIterator: 2468 | echo $row[0].fetchString 2469 | var cr = row.getCollection("TESTCOL") 2470 | var colsize = cr.fetchCollectionSize 2471 | var objRef : OracleObjRef 2472 | echo "collection fetched. numElements " & $colsize 2473 | 2474 | var hexcolumn : string 2475 | 2476 | withOracleCollection(cr): 2477 | for i in 0..colsize-1: 2478 | var hcol = cr[i].fetchBytes("RAWVALUE") 2479 | if hcol.isSome: 2480 | hexcolumn = hcol.get.hex2str 2481 | else: 2482 | hexcolumn = $hcol 2483 | echo $cr[i].fetchDouble("NUMBERVALUE") & " | " & 2484 | $cr[i].fetchString("STRINGVALUE") & " | " & 2485 | $cr[i].fetchString("FIXEDCHARVALUE") & " | " & 2486 | $cr[i].fetchDateTime("DATEVALUE") & " | " & 2487 | $cr[i].fetchDateTime("TIMESTAMPVALUE") & " | " & hexcolumn 2488 | # access object attributes by attrName or attribute column index 2489 | 2490 | # TODO: try to filter against a nested-table object 2491 | 2492 | # var demoCallAggr2 = osql""" select * from HR.NDEMO """ 2493 | # var callFuncAggr2 : PreparedStatement 2494 | # var callFuncResultAggr2 : ResultSet 2495 | # newPreparedStatement(conn,demoCallAggr2,callFuncAggr2,5) 2496 | 2497 | # withPreparedStatement(callFuncAggr2): 2498 | # let param1 = callFuncAggr2.addObjectBindParameter(BindIdx(1),colltype,5) 2499 | # param1.setObject(0,copyOfColl) # todo: consolidate API 2500 | # callFuncAggr2.executeStatement(callFuncResultAggr2) 2501 | 2502 | # for row in resultSetRowIterator(callFuncResultAggr2): 2503 | # echo $row[0].fetchString 2504 | 2505 | #FIXME: edition based redefinition example 2506 | #FIXME: varray example (getElementValueByIndex) 2507 | 2508 | # drop tab with nested table 2509 | var dropNDEMOTab = osql"drop table HR.NDEMO " 2510 | conn.executeDDL(dropNDEMOTab) 2511 | 2512 | echo "releasing object type demo_coll and demo_obj" 2513 | objtype.releaseOracleObjType 2514 | colltype.releaseOracleObjType 2515 | 2516 | var dropDemoAggr : SqlQuery = osql" drop function HR.DEMO_COLAGGR " 2517 | conn.executeDDL dropDemoAggr 2518 | 2519 | var dropDemoColl : SqlQuery = osql" drop type HR.DEMO_COLL " 2520 | conn.executeDDL dropDemoColl 2521 | 2522 | var dropDemoObj : SqlQuery = osql" drop type HR.DEMO_OBJ " 2523 | conn.executeDDL(dropDemoObj) 2524 | 2525 | echo "begin execute immediate test" 2526 | # test for issue: Generic execute statement missing #7 2527 | var doExecuteImmediate : SqlQuery = osql""" 2528 | BEGIN EXECUTE IMMEDIATE 'CREATE TABLE hr.ora_bench_table (key VARCHAR2(32) PRIMARY KEY, 2529 | data VARCHAR2(4000), no_partitions NUMBER DEFAULT 32, partition_key NUMBER(5)) 2530 | PARTITION BY RANGE (partition_key) 2531 | (PARTITION p00000 VALUES LESS THAN (1), PARTITION p00001 VALUES LESS THAN (2), 2532 | PARTITION p00002 VALUES LESS THAN (3), PARTITION p00003 VALUES LESS THAN (4), 2533 | PARTITION p00004 VALUES LESS THAN (5), PARTITION p00005 VALUES LESS THAN (6), 2534 | PARTITION p00006 VALUES LESS THAN (7), PARTITION p00007 VALUES LESS THAN (8), 2535 | PARTITION p00008 VALUES LESS THAN (9), PARTITION p00009 VALUES LESS THAN (10), 2536 | PARTITION p00010 VALUES LESS THAN (11), PARTITION p00011 VALUES LESS THAN (12), 2537 | PARTITION p00012 VALUES LESS THAN (13), PARTITION p00013 VALUES LESS THAN (14), 2538 | PARTITION p00014 VALUES LESS THAN (15), PARTITION p00015 VALUES LESS THAN (16), 2539 | PARTITION p00016 VALUES LESS THAN (17), PARTITION p00017 VALUES LESS THAN (18), 2540 | PARTITION p00018 VALUES LESS THAN (19), PARTITION p00019 VALUES LESS THAN (20), 2541 | PARTITION p00020 VALUES LESS THAN (21), PARTITION p00021 VALUES LESS THAN (22), 2542 | PARTITION p00022 VALUES LESS THAN (23), PARTITION p00023 VALUES LESS THAN (24), 2543 | PARTITION p00024 VALUES LESS THAN (25), PARTITION p00025 VALUES LESS THAN (26), 2544 | PARTITION p00026 VALUES LESS THAN (27), PARTITION p00027 VALUES LESS THAN (28), 2545 | PARTITION p00028 VALUES LESS THAN (29), PARTITION p00029 VALUES LESS THAN (30), 2546 | PARTITION p00030 VALUES LESS THAN (31), PARTITION p00031 VALUES LESS THAN (32))'; 2547 | EXECUTE IMMEDIATE 'CREATE TRIGGER hr.ora_bench_table_before_insert 2548 | BEFORE INSERT ON hr.ora_bench_table 2549 | FOR EACH ROW BEGIN :new.partition_key := MOD (ASCII (SUBSTR 2550 | (:new.key, 1, 1)) * 251 + ASCII (SUBSTR (:new.key, 2, 1)), :new.no_partitions); END 2551 | ora_bench_table_before_insert;'; END; 2552 | """ 2553 | 2554 | conn.executeDDL(doExecuteImmediate) 2555 | 2556 | var dropObj : SqlQuery = osql"drop table hr.ora_bench_table" 2557 | conn.executeDDL(dropObj) 2558 | 2559 | echo "end execute immediate test" 2560 | conn.releaseConnection 2561 | destroyOracleContext(octx) 2562 | -------------------------------------------------------------------------------- /src/nimodpi.nim: -------------------------------------------------------------------------------- 1 | import os 2 | import nimterop/[build, cimport] 3 | 4 | 5 | # Copyright (c) 2019 Michael Krauter 6 | # MIT-license - please see the LICENSE-file for details. 7 | 8 | #[ 9 | nim oracle wrapper (ODPI-C). 10 | 11 | include this file into your project if you like to access an oracle database 12 | (no lib, static binding so far). 13 | 14 | This is the low-level wrapper part (C lang); so no extra convenience glue logic provided. 15 | 16 | if you like a nimish access layer, include db_oracle instead into your project. see 17 | the /examples directory 18 | 19 | ]# 20 | 21 | const 22 | baseDir = currentSourcePath.parentDir()/"build" 23 | srcDir = baseDir/"odpi" 24 | 25 | static: 26 | cDebug() 27 | # cDisableCaching() 28 | 29 | gitPull("https://github.com/oracle/odpi.git", outdir = srcDir, 30 | plist = """ 31 | include/*.h 32 | embed/*.c 33 | src/* 34 | """, checkout = "tags/v4.6.0" ) 35 | 36 | cIncludeDir(srcDir/"include") 37 | 38 | cOverride: 39 | type 40 | DpiOracleType* {.pure.} = enum OTNONE = DPI_ORACLE_TYPE_NONE, 41 | OTVARCHAR = DPI_ORACLE_TYPE_VARCHAR, 42 | OTNVARCHAR = DPI_ORACLE_TYPE_NVARCHAR, 43 | OTCHAR = DPI_ORACLE_TYPE_CHAR, 44 | OTNCHAR = DPI_ORACLE_TYPE_NCHAR, 45 | OTROWID = DPI_ORACLE_TYPE_ROWID, 46 | OTRAW = DPI_ORACLE_TYPE_RAW, 47 | OTNATIVE_FLOAT = DPI_ORACLE_TYPE_NATIVE_FLOAT, 48 | OTNATIVE_DOUBLE = DPI_ORACLE_TYPE_NATIVE_DOUBLE, 49 | OTNATIVE_INT = DPI_ORACLE_TYPE_NATIVE_INT, 50 | OTNUMBER = DPI_ORACLE_TYPE_NUMBER, 51 | OTDATE = DPI_ORACLE_TYPE_DATE, 52 | OTTIMESTAMP = DPI_ORACLE_TYPE_TIMESTAMP, 53 | OTTIMESTAMP_TZ = DPI_ORACLE_TYPE_TIMESTAMP_TZ, 54 | OTTIMESTAMP_LTZ = DPI_ORACLE_TYPE_TIMESTAMP_LTZ, 55 | OTINTERVAL_DS = DPI_ORACLE_TYPE_INTERVAL_DS, 56 | OTINTERVAL_YM = DPI_ORACLE_TYPE_INTERVAL_YM, 57 | OTCLOB = DPI_ORACLE_TYPE_CLOB, 58 | OTNCLOB = DPI_ORACLE_TYPE_NCLOB, 59 | OTBLOB = DPI_ORACLE_TYPE_BLOB, 60 | OTBFILE = DPI_ORACLE_TYPE_BFILE, 61 | OTSTMT = DPI_ORACLE_TYPE_STMT, 62 | OTBOOLEAN = DPI_ORACLE_TYPE_BOOLEAN, 63 | OTOBJECT = DPI_ORACLE_TYPE_OBJECT, 64 | OTLONG_VARCHAR = DPI_ORACLE_TYPE_LONG_VARCHAR, 65 | OTLONG_RAW = DPI_ORACLE_TYPE_LONG_RAW, 66 | OTNATIVE_UINT = DPI_ORACLE_TYPE_NATIVE_UINT, 67 | OTMAX = DPI_ORACLE_TYPE_MAX 68 | 69 | DpiNativeCType* {.pure.} = enum INT64 = DPI_NATIVE_TYPE_INT64, 70 | UINT64 = DPI_NATIVE_TYPE_UINT64, 71 | FLOAT = DPI_NATIVE_TYPE_FLOAT, 72 | DOUBLE = DPI_NATIVE_TYPE_DOUBLE, 73 | BYTES = DPI_NATIVE_TYPE_BYTES, 74 | TIMESTAMP = DPI_NATIVE_TYPE_TIMESTAMP, 75 | INTERVAL_DS = DPI_NATIVE_TYPE_INTERVAL_DS, 76 | INTERVAL_YM = DPI_NATIVE_TYPE_INTERVAL_YM, 77 | LOB = DPI_NATIVE_TYPE_LOB, 78 | OBJECT = DPI_NATIVE_TYPE_OBJECT, 79 | STMT = DPI_NATIVE_TYPE_STMT, 80 | BOOLEAN = DPI_NATIVE_TYPE_BOOLEAN, 81 | ROWID = DPI_NATIVE_TYPE_ROWID 82 | 83 | DpiStmtType* {.pure.} = enum UNKNOWN = DPI_STMT_TYPE_UNKNOWN, 84 | SELECT = DPI_STMT_TYPE_SELECT, 85 | UPDATE = DPI_STMT_TYPE_UPDATE, 86 | DELETE = DPI_STMT_TYPE_DELETE, 87 | INSERT = DPI_STMT_TYPE_INSERT, 88 | CREATE = DPI_STMT_TYPE_CREATE, 89 | DROP = DPI_STMT_TYPE_DROP, 90 | ALTER = DPI_STMT_TYPE_ALTER, 91 | BEGIN = DPI_STMT_TYPE_BEGIN, 92 | DECLARE = DPI_STMT_TYPE_DECLARE, 93 | CALL = DPI_STMT_TYPE_CALL, 94 | EXPLAIN_PLAN = DPI_STMT_TYPE_EXPLAIN_PLAN, 95 | MERGE = DPI_STMT_TYPE_MERGE, 96 | ROLLBACK = DPI_STMT_TYPE_ROLLBACK, 97 | COMMIT = DPI_STMT_TYPE_COMMIT 98 | 99 | DpiResult* {.pure.} = enum FAILURE = DPI_FAILURE, SUCCESS = DPI_SUCCESS 100 | 101 | DpiAuthMode* {.pure.} = enum DEFAULTAUTHMODE = DPI_MODE_AUTH_DEFAULT, 102 | SYSDBA = DPI_MODE_AUTH_SYSDBA, 103 | SYSOPER = DPI_MODE_AUTH_SYSOPER, 104 | PRELIM = DPI_MODE_AUTH_PRELIM, 105 | SYSASM = DPI_MODE_AUTH_SYSASM, 106 | SYSBKP = DPI_MODE_AUTH_SYSBKP, 107 | SYSDGD = DPI_MODE_AUTH_SYSDGD, 108 | SYSKMT = DPI_MODE_AUTH_SYSKMT, 109 | SYSRAC = DPI_MODE_AUTH_SYSRAC 110 | 111 | 112 | DpiNativeCType* {.pure.} = enum INT64 = DPI_NATIVE_TYPE_INT64, 113 | UINT64 = DPI_NATIVE_TYPE_UINT64, 114 | FLOAT = DPI_NATIVE_TYPE_FLOAT, 115 | DOUBLE = DPI_NATIVE_TYPE_DOUBLE, 116 | BYTES = DPI_NATIVE_TYPE_BYTES, 117 | TIMESTAMP = DPI_NATIVE_TYPE_TIMESTAMP, 118 | INTERVAL_DS = DPI_NATIVE_TYPE_INTERVAL_DS, 119 | INTERVAL_YM = DPI_NATIVE_TYPE_INTERVAL_YM, 120 | LOB = DPI_NATIVE_TYPE_LOB, 121 | OBJECT = DPI_NATIVE_TYPE_OBJECT, 122 | STMT = DPI_NATIVE_TYPE_STMT, 123 | BOOLEAN = DPI_NATIVE_TYPE_BOOLEAN, 124 | ROWID = DPI_NATIVE_TYPE_ROWID 125 | DpiModeExec* {.pure.} = enum DEFAULTMODE = DPI_MODE_EXEC_DEFAULT, 126 | DESCRIBE_ONLY = DPI_MODE_EXEC_DESCRIBE_ONLY, 127 | COMMIT_ON_SUCCESS = DPI_MODE_EXEC_COMMIT_ON_SUCCESS, 128 | BATCH_ERRORS = DPI_MODE_EXEC_BATCH_ERRORS, 129 | PARSE_ONLY = DPI_MODE_EXEC_PARSE_ONLY, 130 | ARRAY_DML_ROWCOUNTS = DPI_MODE_EXEC_ARRAY_DML_ROWCOUNTS 131 | 132 | cCompile(srcDir/"/embed/dpi.c") 133 | cImport(srcDir/"/include/dpi.h", recurse = true) 134 | -------------------------------------------------------------------------------- /src/nimtype_to_odpi.nim: -------------------------------------------------------------------------------- 1 | template setString2DpiData(src : Option[string], dest: ptr dpiData) = 2 | ## sets the pointer to the nimstring into odpi-domain. 3 | # buffer created with newVar provide a ptr to the buffer area 4 | copyMem(dest.value.asBytes.ptr,unsafeAddr(src.get[0]),src.get.len) 5 | dest.value.asBytes.length = src.get.len.uint32 6 | 7 | 8 | template setSeq2DpiData(src : Option[seq[byte]], dest: ptr dpiData) = 9 | ## sets the pointer to the nim sequence into odpi-domain. 10 | copyMem(dest.value.asBytes.ptr,unsafeAddr(src.get[0]), src.get.len) 11 | dest.value.asBytes.length = src.get.len.uint32 12 | -------------------------------------------------------------------------------- /src/odpi_obj2string.nim: -------------------------------------------------------------------------------- 1 | # this file contains various object to string repr conversion 2 | # procs 3 | 4 | const hChars = "0123456789ABCDEF" 5 | 6 | proc hex2Str*(par: openArray[byte]): string = 7 | result = newString(2 + ((par.len) shl 1)) # len mul 2 plus 2 extra chars 8 | result[0] = '0' 9 | result[1] = 'x' 10 | for i in countup(0, par.len-1): 11 | result[2 + cast[int](i shl 1)] = 12 | hChars[cast[int](par[i] shr 4)] # process hs nibble 13 | result[3 + cast[int](i shl 1)] = 14 | hChars[cast[int](par[i] and 0xF)] # process ls nibble 15 | 16 | # toString procs 17 | proc fetchObjectTypeName(p : var dpiObjectTypeInfo) : string = 18 | result = newString(p.nameLength+p.schemaLength+1) 19 | copyMem(addr(result[0]),p.schema,p.schemaLength) 20 | result[p.schemaLength] = '.' 21 | copyMem(addr(result[p.schemaLength+1]),p.name,p.nameLength) 22 | 23 | #FIXME: support for dpiAttrInfo 24 | 25 | template `$`*(p : var SqlQuery) : string = 26 | $p 27 | 28 | template `$`*(p: var dpiStmtInfo): string = 29 | ## string repr of a statementInfo obj 30 | "dpiStatementInfo: isQuery " & $p.isQuery & " isPlSql: " & $p.isPLSQL & 31 | " isDDL: " & $p.isDDL & " isDML: " & $p.isDML & " isReturning: " & 32 | $p.isReturning & " " & $DpiStmtType(p.statementType) 33 | 34 | template `$`*(p: var dpiQueryInfo): string = 35 | ## string repr of the dpiQueryInfo obj 36 | "dpiQueryInfo: name " & $p.name & " namelen: " & $p.nameLength & 37 | " nullok: " & $p.nullOk 38 | 39 | template `$`*(p: ptr dpiDataTypeInfo): string = 40 | ## string repr of the dpiDataTypeInfo obj / not all vals exposed 41 | "dpiDataTypeInfo: oracleTypeNum " & $DpiOracleType(p.oracleTypeNum) & 42 | " defaultNativeTypeNum: " & $DpiNativeCType(p.defaultNativeTypeNum) & 43 | " dbsize_bytes: " & $p.dbSizeInBytes & " clientsize_bytes " & $p.clientSizeInBytes 44 | 45 | template fetchCollectionChildType ( p : ptr dpiObjectTypeInfo ) : ptr dpiObjectType = 46 | p.elementTypeInfo.objectType 47 | 48 | template `$`*(p: ptr dpiObjectTypeInfo) : string = 49 | ## string repr of the dpiObjectTypeInfo 50 | var objtype : ptr dpiObjectType = fetchCollectionChildType(p) 51 | var objtypeinfo : dpiObjectTypeInfo 52 | var childobjtypename : string = "" 53 | if not objtype.isNil: 54 | # in case of collection fetch the child type 55 | discard dpiObjectType_getInfo(objtype,objtypeinfo.addr) 56 | childobjtypename = fetchObjectTypeName(objtypeinfo) 57 | "dpiObjectTypeInfo: " & fetchObjectTypeName(p) & " num_attr: " & $p.numAttributes & 58 | " elementTypeInfo: " & $p.elementTypeInfo & " childtype: " & childobjtypename 59 | 60 | template `$`* (p: var dpiTimestamp): string = 61 | ## string representation of a timestamp column 62 | "dpiTimestamp: year:" & $p.year & " month:" & $p.month & " day:" & $p.day & 63 | " hour: " & $p.hour & " minute:" & $p.minute & " second:" & $p.second & 64 | " fsecond:" & $p.fsecond & 65 | " tzHOffset:" & $p.tzHourOffset & " tzMinOffset:" & $p.tzMinuteOffset 66 | 67 | template `$`*(p: var ColumnType): string = 68 | "dbType:" & $p.dbType & " nativeType:" & $p.nativeType 69 | 70 | template `$`*(p: var BindInfo): string = 71 | var bt : string 72 | if p.kind == BindInfoType.byPosition: 73 | bt = "by_position num: " & $p.paramVal.int 74 | else: 75 | bt = "by_name: "& $p.paramName 76 | "bindinfo: " & bt 77 | 78 | template `$`*(p: OracleObjTypeRef ): string = 79 | ## string representation OracleObjTypeRef 80 | echo "objtyperef2string" 81 | " schema: " & $p.objectTypeInfo.schema & 82 | " name: " & $p.objectTypeInfo.name & 83 | " isCollection: " & $p.objectTypeInfo.isCollection & 84 | " numAttributes " & $p.objectTypeInfo.numAttributes & 85 | $p.objectTypeInfo.elementTypeInfo 86 | 87 | template `$`*(p: OracleObjRef ): string = 88 | ## string representation OracleObjTypeRef 89 | " obj of type: " & $p.objType 90 | # missing: name, isCollection, Attributes 91 | 92 | template `$`*(p: ParamTypeRef): string = 93 | $p.bindPosition & " " & $p.columnType & " rowbuffersize: " & $p.rowbuffersize 94 | 95 | proc `$`*(p: ptr dpiRowId): string = 96 | ## string representation of the rowid (10byte) - base64 encoded 97 | var str : cstring = " " #20 chars 98 | var cstringlen : uint32 99 | discard dpiRowid_getStringValue(p,str.addr,cstringlen.addr) 100 | return $str 101 | 102 | # template `$`[T]( p : Option[T] ) : string = 103 | # outcommented because symbol clash with options module 104 | # if p.isNone: 105 | # "" 106 | # else: 107 | # $p.get 108 | 109 | proc `$`*(p: Option[seq[byte]]): string = 110 | if p.isNone: 111 | "" 112 | else: 113 | $p.get 114 | 115 | proc `$`*(p: Option[string]): string = 116 | if p.isNone: 117 | "" 118 | else: 119 | $p.get 120 | 121 | proc `$`*(p: Option[int64]): string = 122 | if p.isNone: 123 | "" 124 | else: 125 | $p.get 126 | 127 | proc `$`*(p: Option[uint64]): string = 128 | if p.isNone: 129 | "" 130 | else: 131 | $p.get 132 | 133 | proc `$`*(p: Option[float32]): string = 134 | if p.isNone: 135 | "" 136 | else: 137 | $p.get 138 | 139 | proc `$`*(p: Option[float64]): string = 140 | if p.isNone: 141 | "" 142 | else: 143 | $p.get 144 | 145 | proc `$`*(p: Option[DateTime]): string = 146 | if p.isNone: 147 | "" 148 | else: 149 | $p.get 150 | -------------------------------------------------------------------------------- /src/odpi_to_nimtype.nim: -------------------------------------------------------------------------------- 1 | # dpi2nimtype conversion helper 2 | # pointer types (strings) are copied into nim domain 3 | template toDateTime(p: ptr dpiData): DateTime = 4 | ## dpiTimestamp to DateTime 5 | let dpits = p.value.asTimestamp 6 | let utcoffset: int = dpits.tzHourOffset*3600.int + dpits.tzMinuteOffset*60 7 | 8 | proc utcTzInfo(time: Time): ZonedTime = 9 | ZonedTime(utcOffset: utcoffset, isDst: false, time: time) 10 | initDateTime(dpits.day, Month(dpits.month), dpits.year, 11 | dpits.hour, dpits.minute, dpits.second,dpits.fsecond, 12 | newTimezone("Etc/UTC", utcTzInfo, utcTzInfo)) 13 | 14 | template toNimString(data: ptr dpiData): string = 15 | ## copy template into nim domain 16 | var result = newString(data.value.asBytes.length) 17 | copyMem(addr(result[0]), data.value.asBytes.ptr, result.len) 18 | result 19 | 20 | template toNimByteSeq(data: ptr dpiData): seq[byte] = 21 | ## copy template for binary data into nim domain 22 | var result = newSeq[byte](data.value.asBytes.length) 23 | copyMem(addr(result[0]), data.value.asBytes.ptr, result.len) 24 | result 25 | 26 | template toIntervalDs(data : ptr dpiData) : Duration = 27 | initDuration(nanoseconds = data.value.asIntervalDS.fseconds, 28 | microseconds = 0, 29 | milliseconds = 0, 30 | seconds = data.value.asIntervalDS.seconds, 31 | minutes = data.value.asIntervalDS.minutes, 32 | hours = data.value.asIntervalDS.hours, 33 | days = data.value.asIntervalDS.days , 34 | weeks = 0) 35 | -------------------------------------------------------------------------------- /src/setfetchtypes.nim: -------------------------------------------------------------------------------- 1 | # fetching/setting templates for raw odpi-c access 2 | # and nim types ParamTypeRef, OracleObjRef, OracleCollectionRef 3 | # 4 | # Remark: setter always operate on dpiVar so the memory is 5 | # managed by ODPI-C. getter value types are always copied. 6 | # so the pointer types (bytes/string) in case of Option is 7 | # returned. 8 | # TODO: macro to auto-generate the fetch/set templates/proc for each 9 | # datatype or use generics 10 | 11 | template `[]`(obj: OracleObjRef, colidx: int): ptr dpiData = 12 | ## internal template to obtain the dpiData pointer for each 13 | ## attribute. used in setfetchtypes.nim to get/set an attribute value 14 | obj.bufferedColumn[colidx] 15 | # FIXME: eval if buffering needed 16 | 17 | 18 | template fetchBoolean*(val : ptr dpiData) : Option[bool] = 19 | ## fetches the specified value as boolean. the value is copied 20 | if val.isDbNull: 21 | none(bool) 22 | else: 23 | some(val.value.asBoolean) 24 | 25 | template fetchBoolean*(param : ParamTypeRef) : Option[bool] = 26 | ## fetches the sepcified value as boolean by index 0 27 | param[0].fetchBoolean 28 | 29 | template fetchBoolean*( param : OracleObjRef , index : int) : Option[bool] = 30 | ## access template for db-types 31 | getAttributeValue(param,index).fetchBoolean 32 | 33 | template fetchBoolean*( param : OracleObjRef , attrName : string) : Option[bool] = 34 | ## access template for db-types 35 | getAttributeValue(param,lookupObjAttrIndexByName(param,attrName)).fetchBoolean 36 | 37 | template setBoolean*(param : ptr dpiData, value : Option[bool] ) = 38 | ## bind parameter setter boolean type. 39 | if value.isNone: 40 | param.setDbNull 41 | else: 42 | param.setNotDbNull 43 | param.value.asBoolean = value.get 44 | 45 | template setBoolean*( param : ParamTypeRef, value : Option[bool]) = 46 | ## access template for single bind variables (index 0) 47 | param[0].setBoolean(value) 48 | 49 | template setBoolean*( param : OracleObjRef , 50 | index : int, 51 | value : Option[bool]) = 52 | ## access template for db-types 53 | param[index].setBoolean(value) 54 | # set buffered column 55 | setAttributeValue(param,index) 56 | # set backend. FIXME: remove quirk 57 | 58 | template setBoolean*( param : OracleObjRef , 59 | attrName : string, 60 | value : Option[bool]) = 61 | ## access template for db-types 62 | setBoolean(param,lookupObjAttrIndexByName(param,attrName),value) 63 | 64 | # simple type conversion templates. 65 | template fetchFloat*(val : ptr dpiData) : Option[float32] = 66 | ## fetches the specified value as float. the value is copied 67 | if val.isDbNull: 68 | none(float32) 69 | else: 70 | some(val.value.asFloat.float32) 71 | 72 | template fetchFloat*( param : ParamTypeRef) : Option[float32] = 73 | param[0].fetchFloat 74 | 75 | template fetchFloat*( param : OracleObjRef , index : int) : Option[float] = 76 | ## access template for db-types 77 | getAttributeValue(param,index).fetchFloat 78 | 79 | template fetchFloat*( param : OracleObjRef , attrName : string) : Option[float32] = 80 | ## access template for db-types 81 | getAttributeValue(param,lookupObjAttrIndexByName(param,attrName)).fetchFloat 82 | 83 | proc setFloat*( param : ptr dpiData, value : Option[float32]) = 84 | ## bind parameter setter float32 type. 85 | ## TODO: eval why template is not compiling 86 | if value.isNone: 87 | param.setDbNull 88 | else: 89 | param.setNotDbNull 90 | param.value.asFloat = value.get 91 | 92 | template setFloat*(param : ParamTypeRef, value : Option[float32]) = 93 | param[0].setFloat(value) 94 | 95 | template setFloat*( param : OracleObjRef , 96 | index : int, 97 | value : Option[float32]) = 98 | ## access template for db-types 99 | param[index].setFloat(value) 100 | setAttributeValue(param,index) 101 | 102 | template setFloat*( param : OracleObjRef , 103 | attrName : string, 104 | value : Option[float32]) = 105 | setFloat(param,lookupObjAttrIndexByName(param,attrName),value) 106 | 107 | template fetchDouble*(val : ptr dpiData) : Option[float64] = 108 | ## fetches the specified value as double (Nims 64 bit type). the value is copied 109 | if val.isDbNull: 110 | none(float64) 111 | else: 112 | some(val.value.asDouble.float64) 113 | 114 | template fetchDouble*( param : ParamTypeRef ) : Option[float64] = 115 | param[0].fetchDouble 116 | 117 | template fetchDouble*( param : OracleObjRef , index : int) : Option[float64] = 118 | ## access template for db-types 119 | getAttributeValue(param,index).fetchDouble 120 | 121 | template fetchDouble*( param : OracleObjRef , attrName : string) : Option[float64] = 122 | ## access template for db-types 123 | getAttributeValue(param,lookupObjAttrIndexByName(param,attrName)).fetchDouble 124 | 125 | proc setDouble(param : ptr dpiData, value : Option[float64]) = 126 | ## bind parameter setter float64 type. 127 | ## TODO: eval why template type is not compiling - string types are ok 128 | if value.isNone: 129 | param.setDbNull 130 | else: 131 | param.setNotDbNull 132 | param.value.asDouble = value.get 133 | 134 | template setDouble*( param : ParamTypeRef, value : Option[float64] ) = 135 | ## convenience template for accessing the first parameter within the buffer 136 | param[0].setDouble(value) 137 | 138 | template setDouble*( param : OracleObjRef , index : int, value : Option[float64]) = 139 | ## access template for db-types 140 | param[index].setDouble(value) 141 | setAttributeValue(param,index) 142 | 143 | template setDouble*( param : OracleObjRef , 144 | attrName : string, 145 | value : Option[float64]) = 146 | ## access template for db-types 147 | setDouble(param,lookupObjAttrIndexByName(param,attrName),value) 148 | 149 | template fetchUInt64*(val : ptr dpiData) : Option[uint64] = 150 | ## fetches the specified value as double (Nims 64 bit type). the value is copied 151 | if val.isDbNull: 152 | none(uint64) 153 | else: 154 | some(val.value.asUint64) 155 | 156 | template fetchUInt64*( param : ParamTypeRef ) : Option[uint64] = 157 | param[0].fetchUInt64 158 | 159 | template fetchUInt64*( param : OracleObjRef, index : int ) : Option[uint64] = 160 | getAttributeValue(param,index).fetchUInt64 161 | 162 | template fetchUInt64*( param : OracleObjRef , attrName : string) : Option[uint64] = 163 | ## access template for db-types 164 | getAttributeValue(param,lookupObjAttrIndexByName(param,attrName)).fetchUInt64 165 | 166 | template setUInt64*(param : ptr dpiData, value : Option[uint64]) = 167 | ## bind parameter setter uint64 type. 168 | if value.isNone: 169 | param.setDbNull 170 | else: 171 | param.setNotDbNull 172 | param.value.asUint64 = value.get 173 | 174 | template setUInt64*( param : ParamTypeRef, value : Option[uint64] ) = 175 | ## convenience template for accessing the first parameter within the buffer 176 | param[0].setUInt64(value) 177 | 178 | template setUInt64*( param : OracleObjRef , 179 | index : int, 180 | value : Option[uint64]) = 181 | ## access template for db-types 182 | param[index].setUInt64(value) 183 | setAttributeValue(param,index) 184 | 185 | template setUInt64*( param : OracleObjRef , 186 | attrName : string, 187 | value : Option[uint64]) = 188 | ## access template for db-types 189 | setUInt64(param,lookupObjAttrIndexByName(param,attrName),value) 190 | 191 | template fetchInt64*(val : ptr dpiData) : Option[int64] = 192 | ## fetches the specified value as double (Nims 64 bit type). the value is copied 193 | if val.isDbNull: 194 | none(int64) 195 | else: 196 | some(val.value.asInt64) 197 | 198 | template fetchInt64*( param : ParamTypeRef ) : Option[int64] = 199 | param[0].fetchInt64 200 | 201 | template fetchInt64*( param : OracleObjRef , index : int) : Option[int64] = 202 | ## access template for db-types 203 | getAttributeValue(param,index).fetchInt64 204 | 205 | template fetchInt64*( param : OracleObjRef , 206 | attrName : string) : Option[int64] = 207 | ## access template for db-types 208 | getAttributeValue(param,lookupObjAttrIndexByName(param,attrName)).fetchInt64 209 | 210 | proc setInt64*( param : ptr dpiData, value : Option[int64]) = 211 | ## bind parameter setter int64 type. 212 | if value.isNone: 213 | param.setDbNull 214 | else: 215 | param.setNotDbNull 216 | param.value.asInt64 = value.get 217 | 218 | template setInt64*( param : ParamTypeRef, value : Option[int64] ) = 219 | ## convenience template for accessing the first parameter within the buffer 220 | param[0].setInt64(value) 221 | 222 | template setInt64*( param : OracleObjRef , 223 | index : int, 224 | value : Option[int64]) = 225 | ## access template for db-types 226 | param[index].setInt64(value) 227 | setAttributeValue(param,index) 228 | 229 | template setInt64*( param : OracleObjRef , 230 | attrName : string, 231 | value : Option[int64]) = 232 | ## access template for db-types 233 | setInt64(param,lookupObjAttrIndexByName(param,attrName),value) 234 | 235 | template fetchIntervalDS*(val : ptr dpiData ) : Option[Duration] = 236 | # todo: implement 237 | if val.isDbNull: 238 | none(Duration) 239 | else: 240 | some(val.toIntervalDs) 241 | 242 | template fetchIntervalDS*(param : ParamTypeRef) : Option[Duration] = 243 | param[0].fetchIntervalDS 244 | 245 | template fetchIntervalDS*( param : OracleObjRef , index : int) : Option[Duration] = 246 | ## access template for db-types 247 | getAttributeValue(param,index).fetchIntervalDS 248 | 249 | template fetchIntervalDS*( param : OracleObjRef , attrName : string) : Option[Duration] = 250 | ## access template for db-types 251 | getAttributeValue(param,lookupObjAttrIndexByName(param,attrName)).fetchIntervalDS 252 | 253 | template setIntervalDS*(param : ptr dpiData , value : Option[Duration]) = 254 | ## bind parameter setter IntervalDS type. this setter operates always with index 1 255 | if value.isNone: 256 | param.setDbNull 257 | else: 258 | param.setNotDbNull 259 | param.value.asIntervalDS.fseconds = value.get.nanoseconds 260 | param.value.asIntervalDS.seconds = value.get.seconds 261 | param.value.asIntervalDS.minutes = value.get.minutes 262 | param.value.asIntervalDS.hours = value.get.hours 263 | param.value.asIntervalDS.days = value.get.days 264 | 265 | template setIntervalDS*( param : ParamTypeRef, value : Option[Duration]) = 266 | param[0].setIntervalDS(value) 267 | 268 | template setIntervalDS*( param : OracleObjRef , 269 | index : int, 270 | value : Option[Duration]) = 271 | ## access template for db-types 272 | param[index].setIntervalDS(value) 273 | setAttributeValue(param,index) 274 | 275 | template setIntervalDS*( param : OracleObjRef , 276 | attrName : string, 277 | value : Option[Duration]) = 278 | ## access template for db-types 279 | setIntervalDS(param,lookupObjAttrIndexByName(param,attrName),value) 280 | 281 | template fetchDateTime*( val : ptr dpiData ) : Option[DateTime] = 282 | ## fetches the specified value as DateTime. the value is copied 283 | if val.isDbNull: 284 | none(DateTime) 285 | else: 286 | some(toDateTime(val)) 287 | 288 | template fetchDateTime*( param: ParamTypeRef) : Option[DateTime] = 289 | param[0].fetchDateTime 290 | 291 | template fetchDateTime*( param : OracleObjRef , index : int) : Option[DateTime] = 292 | ## access template for db-types 293 | getAttributeValue(param,index).fetchDateTime 294 | 295 | template fetchDateTime*( param : OracleObjRef , attrName : string) : Option[DateTime] = 296 | ## access template for db-types 297 | getAttributeValue(param,lookupObjAttrIndexByName(param,attrName)).fetchDateTime 298 | 299 | proc setDateTime*(param : ptr dpiData , value : Option[DateTime] ) = 300 | ## bind parameter setter DateTime type. this setter operates always with index 1 301 | if value.isNone: 302 | param.setDbNull 303 | else: 304 | param.setNotDbNull 305 | let dt = value.get 306 | let utcoffset = dt.utcOffset 307 | let tz = dt.timezone 308 | param.value.asTimestamp.year = dt.year.int16 309 | param.value.asTimestamp.month = dt.month.uint8 310 | param.value.asTimestamp.day = dt.monthday.uint8 311 | param.value.asTimestamp.hour = dt.hour.uint8 312 | param.value.asTimestamp.minute = dt.minute.uint8 313 | param.value.asTimestamp.second = dt.second.uint8 314 | param.value.asTimestamp.fsecond = dt.nanosecond.uint32 315 | 316 | if not tz.isNil: 317 | let tzhour : int8 = cast[int8](utcoffset/3600) # get hours and throw fraction away 318 | param.value.asTimestamp.tzHourOffset = tzhour 319 | param.value.asTimestamp.tzMinuteOffset = cast[int8]((utcoffset - tzhour*3600)/60) 320 | # TODO: eval if ok 321 | 322 | template setDateTime*( param : ParamTypeRef, value : Option[DateTime] ) = 323 | param[0].setDateTime(value) 324 | 325 | template setDateTime*( param : OracleObjRef , 326 | index : int, 327 | value : Option[DateTime]) = 328 | ## access template for db-types 329 | param[index].setDateTime(value) 330 | setAttributeValue(param,index) 331 | 332 | template setDateTime*( param : OracleObjRef , 333 | attrName : string, 334 | value : Option[DateTime]) = 335 | setDateTime(param,lookupObjAttrIndexByName(param,attrName),value) 336 | 337 | 338 | template fetchString*( val : ptr dpiData ) : Option[string] = 339 | ## fetches the specified value as string. the value is copied 340 | ## out of the internal buffer 341 | if val.isDbNull: 342 | none(string) 343 | else: 344 | some(toNimString(val)) 345 | 346 | template fetchString*( param : ParamTypeRef) : Option[string] = 347 | ## convenience template for accessing the first parameter (index 0) 348 | param[0].fetchString 349 | 350 | # template fetchString*( param : ParamTypeRef, rownum : int) : Option[string] = 351 | # ## fetches the specified value as string. the value is copied out of the 352 | # ## internal buffer 353 | # param[rownum].fetchString 354 | 355 | template fetchString*( param : OracleObjRef , index : int) : Option[string] = 356 | ## access template for db-types 357 | getAttributeValue(param,index).fetchString 358 | 359 | template fetchString*( param : OracleObjRef , 360 | attrName : string) : Option[string] = 361 | ## access template for db-types 362 | getAttributeValue(param,lookupObjAttrIndexByName(param,attrName)).fetchString 363 | 364 | proc setString*(val : ptr dpiData, value : Option[string]) = 365 | ## sets the string of the odpi-data buffer 366 | ## directly (setFromBytes bypassed) 367 | ## cant be used in the context of OracleObjects because 368 | ## the buffer is not provided by ODPI-C in case of a object is 369 | ## copied by the ODPI-C copyfunction. 370 | if value.isNone: 371 | val.setDbNull 372 | else: 373 | val.setNotDbNull 374 | value.setString2DpiData(val) 375 | 376 | template setString*(param : ParamTypeRef, value : Option[string]) = 377 | param[0].setString(value) 378 | 379 | proc setString*( param : OracleObjRef , 380 | index : int, 381 | value : Option[string]) = 382 | ## access template for db-types 383 | ## TODO: eval strange template behaviour (called more often than needed) 384 | if value.isNone: 385 | param[index].setDbNull 386 | else: 387 | param[index].setString(value) 388 | setAttributeValue(param,index) 389 | 390 | 391 | # object type getter section 392 | 393 | template `[]`*(obj: OracleCollectionRef, colidx: int) : OracleObjRef = 394 | ## getter template to obtain the dpiObject pointer for each 395 | ## collection element 396 | getCollectionElement(obj,colidx) 397 | 398 | 399 | template setString*( param : OracleObjRef , 400 | attrName : string, 401 | value : Option[string]) = 402 | ## access template for db-types 403 | setString(param,lookUpObjAttrIndexByName(param,attrName),value) 404 | 405 | template fetchBytes*( val : ptr dpiData ) : Option[seq[byte]] = 406 | ## fetches the specified value as seq[byte]. the byte array is copied 407 | if val.isDbNull: 408 | none(seq[byte]) 409 | else: 410 | some(toNimByteSeq(val)) 411 | 412 | template fetchBytes*( param : ParamTypeRef ) : var Option[seq[byte]] = 413 | ## convenience template for fetching the value at position 0 414 | param[0].fetchBytes 415 | 416 | template fetchBytes*( param : OracleObjRef , index : int) : Option[seq[byte]] = 417 | ## access template for db-types 418 | getAttributeValue(param,index).fetchBytes 419 | 420 | template fetchBytes*( param : OracleObjRef , attrName : string) : Option[seq[byte]] = 421 | ## access template for db-types 422 | getAttributeValue(param,lookupObjAttrIndexByName(param,attrName)).fetchBytes 423 | 424 | 425 | template setBytes*( val : ptr dpiData, value : Option[seq[byte]] ) = 426 | ## raw template to write the value into dpiVal's memory 427 | if value.isNone: 428 | val.setDbNull 429 | else: 430 | val.setNotDbNull 431 | value.setSeq2DpiData(val) 432 | 433 | template setBytes*(param : ParamTypeRef, value : var Option[seq[byte]]) = 434 | ## convenience template to access the parameters first row 435 | param[0].setBytes(value) 436 | 437 | template setBytes*( param : OracleObjRef , 438 | index : int, 439 | value : Option[seq[byte]]) = 440 | ## access template for db-types 441 | if value.isNone: 442 | param[index].setDbNull 443 | else: 444 | setBytes(param[index],value) 445 | 446 | setAttributeValue(param,index) 447 | 448 | template setBytes*( param : OracleObjRef , 449 | attrName : string, 450 | value : Option[seq[byte]]) = 451 | ## access template for db-types 452 | setBytes(param,lookupObjAttrIndexByName(param,attrName),value) 453 | 454 | template fetchRowId*( param : ptr dpiData ) : ptr dpiRowid = 455 | ## fetches the rowId (internal representation). 456 | param.value.asRowId 457 | 458 | template setRowId*(param : ParamTypeRef , rowid : ptr dpiRowid ) = 459 | ## bind parameter setter boolean type. this setter operates always with index 0 460 | ## (todo: eval if the rowid could be nil, and add arraybinding-feature) 461 | param.buffer.setNotDbNull 462 | discard dpiVar_setFromRowid(param.paramVar,0,rowid) 463 | 464 | template setRowId*(param : ParamTypeRef , rowid : ptr dpiRowid , rownum : int = 0 ) = 465 | ## bind parameter setter boolean type. this setter operates always with index 0 466 | ## (todo: eval if the rowid could be nil, and add arraybinding-feature) 467 | param.buffer.setNotDbNull 468 | discard dpiVar_setFromRowid(param.paramVar,rownum.uint32,rowid) 469 | 470 | template fetchRefCursor*(param : ptr dpiData ) : ptr dpiStmt = 471 | ## fetches a refCursorType out of the result column. 472 | param.value.asStmt 473 | 474 | template setLob*(param : ptr dpiLob, value : Option[seq[byte]] , rownum : int = 0) = 475 | # use: int dpiVar_setFromLob(dpiVar *var, uint32_t pos, dpiLob *lob) 476 | #FIXME : implement 477 | discard 478 | 479 | template getLob*(param : ptr dpiLob ) : Option[seq[byte]] = 480 | #FIXME : implement 481 | discard 482 | --------------------------------------------------------------------------------