Skip to content

Commit

Permalink
version 0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
zolern committed Jul 1, 2019
1 parent bf4403e commit 626eee9
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 70 deletions.
184 changes: 118 additions & 66 deletions adodb.nim
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,16 @@ For Access > 2007 (\*.accdb):
{.deadCodeElim: on.}

import tables
import winim/com as wincom
import threadpool
import os
import unicode
import segfaults
import times

import winim/com
import adodb/private/sqlformat
import times

export wincom, sqlformat, times
export com, sqlformat, times

type
ADODB* = ref object
Expand All @@ -91,15 +93,22 @@ type
parent: ADORecordset
data: ADODataRow

ADOResult = ref object
rst: ADORecordset
err: ref Exception

# ADOField method & properties
proc init(self: ADOField, v: variant) =
self.data = copy(v)

proc final(self: ADOField) =
proc done(self: ADOField) =
if not self.data.isNil:
self.data.del
self.data = nil

proc final(self: ADOField) =
self.done

proc value(self: ADOField): variant = copy(self.data)

proc newADOField(v: variant): ADOField =
Expand All @@ -112,16 +121,18 @@ proc init(self: ADORecordset, rst: com) =

if rst.BOF() != -1 or rst.EOF() != -1:
let fields = rst.Fields()

for fld in fields:
let fldName: string = fld.Name()
self.fields[fldName.toLower()] = self.fields.len
let fieldCount = fields.Count()

rst.MoveFirst()

while rst.EOF() != -1:
self.data.add(@[])
for fld in fields:
for i in 0 ..< fieldCount:
let fld = fields.Item(i)

if self.fields.len < fieldCount:
let fldName: string = fld.Name()
self.fields[fldName.toLower()] = self.fields.len

self.data[self.data.len - 1].add(newADOField(fld.Value()))
rst.MoveNext()

Expand All @@ -133,6 +144,7 @@ proc close*(self: ADORecordset) =
var row = self.data.pop
while row.len > 0:
var fld = row.pop
fld.done
fld = nil

proc final(self: ADORecordset) =
Expand All @@ -142,7 +154,7 @@ proc newADORecordset(rst: com): ADORecordset =
new(result, final)
result.init(rst)

proc len*(self: ADORecordset): int {.inline.} = self.data.len
proc len*(self: ADORecordset): int = self.data.len
## returns row count of ADORecordset

template rowCount*(self: ADORecordset): int = self.len
Expand All @@ -161,12 +173,12 @@ proc `[]`*(self: ADORecordset, index: Natural): ADORow =
## return index-th (from 0 to len-1) row of recordset
result = newADORow(self, self.data[index])

iterator items*(self: ADORecordset): ADORow {.inline.} =
iterator items*(self: ADORecordset): ADORow =
## iterates through recordset rows
for row in self.data:
yield newADORow(self, row)

iterator pairs*(self: ADORecordset): (int, ADORow) {.inline.} =
iterator pairs*(self: ADORecordset): (int, ADORow) =
## iterates through rows, returns pair (index, ADORow)
for rowIdx, row in self.data:
yield (rowIdx, newADORow(self, row))
Expand All @@ -180,23 +192,23 @@ proc `[]`*(row: ADORow, field: string): variant =
let index = row.parent.fields[field.toLower()]
return row.data[index].value

proc len*(row: ADORow): int {.inline.} = row.data.len
proc len*(row: ADORow): int = row.data.len
## returns count of fields

template fieldCount*(row: ADORow): int = row.len
## alias of len

iterator items*(row: ADORow): variant {.inline.} =
iterator items*(row: ADORow): variant =
## iterates all columns of row
for fld in row.data:
yield fld.value

iterator pairs*(row: ADORow): (int, variant) {.inline.} =
iterator pairs*(row: ADORow): (int, variant) =
## iterates all columns, return pair (index, value)
for fldIdx, fld in row.data:
yield (fldIdx, fld.value)

iterator fields*(row: ADORow): (int, string, variant) {.inline.} =
iterator fields*(row: ADORow): (int, string, variant) =
## iterates all columns, return (index, field name, value)
let fields = row.parent.fields
for fldName, fldIdx in fields.pairs:
Expand All @@ -207,47 +219,87 @@ proc release(o: var com) =

if o.State() != 0:
o.Close()


o.del
o = nil

# ADODB methods and properties
proc query*(adoDb: ADODB; sql: string): ADORecordset =
## retrieve recordset by SQL statement
proc thQuery(connection, sql: string): ADOResult =
{.gcsafe.}:
## retrieve recordset by SQL statement
new(result)

var rst: com = nil
var rst: com

try:
# Create recorsed
rst = CreateObject("ADODB.Recordset")
try:
CoInitialize(nil)

# Open recordset
rst.Open(sql, adoDb.connection, 0, 1)
rst = CreateObject("ADODB.Recordset")

# Query
discard rst.Open(sql, connection, 0, 1)

# Get data
result.rst = newADORecordset(rst)

except:
result.err = getCurrentException()

# Query data
result = newADORecordset(rst)
finally:
rst.release()
finally:
rst.release()
CoUninitialize()

proc exec*(adoDb: ADODB; sql: string) =
proc query*(adoDb: ADODB; sql: string): ADORecordset =
let resFlow = spawn thQuery(adoDb.connection, sql)
let res = ^resFlow

if not res.err.isNil:
raise res.err

return res.rst


proc thExec(connection, sql: string): ADOResult =
## execute SQL statement
{.gcsafe.}:

## retrieve recordset by SQL statement
new(result)

var conn: com = nil


try:
CoInitialize(nil)
# Create connection
conn = CreateObject("ADODB.Connection")

# Set CursorLocation
conn.CursorLocation = 3

discard conn.Open(connection)

var conn: com = nil
try:
# Create connection
conn = CreateObject("ADODB.Connection")
# Execute
discard conn.Execute(sql)

# Open
discard conn.Open(adoDb.connection)
except:
result.err = getCurrentException()

# Execute
discard conn.Execute(sql)
finally:
conn.release()
finally:
conn.release()
COM_FullRelease()
CoUninitialize()

proc exec*(adoDb: ADODB; sql: string) =
let resFlow = spawn thExec(adoDb.connection, sql)
let res = ^resFlow

if not res.err.isNil:
raise res.err

proc init(self: ADODB, connection: string) =
self.connection = connection

proc newADODB*(connection: string): ADODB {.inline.} =
proc newADODB*(connection: string): ADODB =
## Constructor
new(result)
result.init(connection)
Expand All @@ -268,57 +320,57 @@ template nz*(v, valueIfNull): untyped =

# Several data aggregation methods

proc dQuery(adoDb: ADODB; functor, field, domain, where: string): variant =
# base procedure, that is used for all aggregate functions
result = nil
proc dQuery[T](adoDb: ADODB; functor, field, domain, where: string): T =
var statement = sql"SELECT {functor}({field}) AS {functor}Of{field} FROM {domain}"
if where != "":
statement.add sql" WHERE ({where})"

let rst = adoDb.query(statement)

if rst.rowCount != 0 and rst[0].fieldCount != 0:
result = rst[0][0]
let resFlow = spawn thQuery(adoDb.connection, statement)
let res = ^resFlow

rst.close
if not res.err.isNil:
raise res.err

result = fromVariant[T](res.rst[0][0])
res.rst.close

proc dMax*(adoDb: ADODB; field, domain: string; criteria: string = ""): variant {.inline.} =
proc dMax*[T](adoDb: ADODB; field, domain: string; criteria: string = ""): T =
## return Max value of field in domain, null if no data in domain
## (optional criteria can be applied)
dQuery(adoDb, "Max", field, domain , criteria)
result = dQuery[T](adoDb, "Max", field, domain , criteria)

proc dMin*(adoDb: ADODB; field, domain: string; criteria: string = ""): variant {.inline.} =
proc dMin*[T](adoDb: ADODB; field, domain: string; criteria: string = ""): T =
## return Min value of field in domain, null if no data in domain
## (optional criteria can be applied)
dQuery(adoDb, "Min", field, domain , criteria)
result = dQuery[T](adoDb, "Min", field, domain , criteria)

proc dFirst*(adoDb: ADODB; field, domain: string; criteria: string = ""): variant {.inline.} =
proc dFirst*[T](adoDb: ADODB; field, domain: string; criteria: string = ""): T =
## return first value of field in domain, null if no data in domain
## (optional criteria can be applied)
dQuery(adoDb, "First", field, domain , criteria)
result = dQuery[T](adoDb, "First", field, domain , criteria)

proc dLast*(adoDb: ADODB; field, domain: string; criteria: string = ""): variant {.inline.} =
proc dLast*[T](adoDb: ADODB; field, domain: string; criteria: string = ""): T =
## return last value of field in domain, null if no data in domain
## (optional criteria can be applied)
dQuery(adoDb, "Last", field, domain , criteria)
result = dQuery[T](adoDb, "Last", field, domain , criteria)

proc dLookup*(adoDb: ADODB; field, domain: string; criteria: string = ""): variant {.inline.} =
proc dLookup*[T](adoDb: ADODB; field, domain: string; criteria: string = ""): T =
## return value of field in domain, null if no data in domain
## (optional criteria can be applied)
dQuery(adoDb, "", field, domain , criteria)
result = dQuery[T](adoDb, "", field, domain , criteria)

proc dCount*(adoDb: ADODB; field, domain: string; criteria: string = ""): int =
## return count of rows by field in domain, 0 if no data in domain
## (optional criteria can be applied)
nz(dQuery(adoDb, "Count", field, domain , criteria), 0)
result = dQuery[int](adoDb, "Count", field, domain , criteria)

proc dSum*(adoDb: ADODB, field, domain: string; criteria: string = ""): variant {.inline.} =
proc dSum*(adoDb: ADODB, field, domain: string; criteria: string = ""): float =
## return sum of all values of field in domain, null if no data in domain
## (optional criteria can be applied)
dQuery(adoDb, "Sum", field, domain, criteria)
result = dQuery[float](adoDb, "Sum", field, domain, criteria)

proc dAvg*(adoDb: ADODB, field, domain: string; criteria: string = ""): variant {.inline.} =
proc dAvg*(adoDb: ADODB, field, domain: string; criteria: string = ""): float =
## return average value of all values of field in domain, null if no data in domain
## (optional criteria can be applied)
dQuery(adoDb, "Avg", field, domain, criteria)
result = dQuery[float](adoDb, "Avg", field, domain, criteria)

4 changes: 2 additions & 2 deletions adodb.nimble
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Package

version = "0.2.1"
version = "0.3.0"
author = "Zolern"
description = "adodb - simple access to Microsoft ADO databases"
license = "MIT"
skipDirs = @["examples", "docs"]

# Dependencies

requires "nim >= 0.19.0", "winim >= 3.1.1"
requires "nim >= 0.19.6", "winim >= 3.1.1"
1 change: 1 addition & 0 deletions adodb.nims
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
switch("threads", "on")
26 changes: 26 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Version 0.3.0
--------------
* better memory management
* better exception management
* all db calls in separate thread
* no more errors in db driver (>10 millions read/writes without crashing)
* update examples

Version 0.2.1
-------------
* support updated winim 3.1
* conform nim 0.20

Version 0.2.0
-------------
* support updated winim 3.0
* conform nim 0.19

Version 0.1.0
-------------
* Initial release
* exec and query methods
* SQL literal interpolation
* timestamp custom format
* NULL variant processing
* data aggregation functions (DSum, DMax, DCount, etc.)
4 changes: 2 additions & 2 deletions examples/aggregations.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let
uid = "user_id"


var userId: int = nz(db.dMax(uid, tbl), 0)
var userId: int = db.dMax[:int](uid, tbl)

db.exec(sql"DELETE * FROM {tbl}")

Expand All @@ -23,7 +23,7 @@ proc iteration(id: var int) =

if count >= 1000:
# keep count of records to not exceed 1000
let minID = db.dMin(uid, tbl)
let minID = db.dMin[:int](uid, tbl)
db.exec(sql"DELETE * FROM {tbl} WHERE (({uid})={minID})")

stdout.write('\r', fmt"MaxID: {id:>5}, Count: {count:>4}, Total sum: {sum:>9.2f}, Average: {avg:>6.2f}")
Expand Down

0 comments on commit 626eee9

Please sign in to comment.