feat(clients): add nim-allographer client with BaraDB driver support
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled
- Pure Nim wire protocol client (async + sync, no C FFI) - Query Builder: fluent API, SQL generator, execution, transactions - Schema Builder: create/alter/drop tables, column operations - Connection pool with aging and timeout handling - ORM integration via compile-time DB_BARADB switch - Tests: test_open, test_query - Documentation: README.md, PLAN_BARADB.md
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import ./env
|
||||
|
||||
import ./query_builder/libs/database_url
|
||||
export DatabaseUrl, DatabaseUrlQuery, ParsedDatabaseUrl, asDatabaseUrl, parseDatabaseUrl, databaseName, sqliteDatabasePath, portOrDefault, requireDatabaseUrlScheme
|
||||
|
||||
when isExistsSqlite:
|
||||
import ./query_builder/models/sqlite/sqlite_types; export SQLite3, SqliteConnections, SqlitePreparedContext
|
||||
import ./query_builder/models/sqlite/sqlite_open; export sqlite_open
|
||||
|
||||
when isExistsPostgres:
|
||||
import ./query_builder/models/postgres/postgres_types; export PostgreSQL, PostgresConnections, PostgresPreparedContext
|
||||
import ./query_builder/models/postgres/postgres_open; export postgres_open
|
||||
|
||||
when isExistsMariadb:
|
||||
import ./query_builder/models/mariadb/mariadb_types; export MariaDB, MariadbConnections, MariadbPreparedContext
|
||||
import ./query_builder/models/mariadb/mariadb_open; export mariadb_open
|
||||
|
||||
when isExistsMysql:
|
||||
import ./query_builder/models/mysql/mysql_types; export MySql, MysqlConnections, MysqlPreparedContext
|
||||
import ./query_builder/models/mysql/mysql_open; export mysql_open
|
||||
|
||||
when isExistsSurrealdb:
|
||||
import ./query_builder/models/surreal/surreal_types; export SurrealDB, SurrealConnections, SurrealPreparedContext
|
||||
import ./query_builder/models/surreal/surreal_open; export surreal_open
|
||||
|
||||
when isExistsBaradb:
|
||||
import ./query_builder/models/baradb/baradb_types; export Baradb, BaradbConnections, BaradbPreparedContext
|
||||
import ./query_builder/models/baradb/baradb_open; export baradb_open
|
||||
@@ -0,0 +1,27 @@
|
||||
import std/os
|
||||
import std/strutils
|
||||
import std/streams
|
||||
import std/parsecfg
|
||||
|
||||
for f in walkDir(getCurrentDir()):
|
||||
if f.path.split("/")[^1] == ".env":
|
||||
let path = getCurrentDir() / ".env"
|
||||
var f = newFileStream(path, fmRead)
|
||||
var p: CfgParser
|
||||
open(p, f, path)
|
||||
while true:
|
||||
let e = next(p)
|
||||
case e.kind
|
||||
of cfgEof: break
|
||||
of cfgKeyValuePair: putEnv(e.key, e.value)
|
||||
else: discard
|
||||
break
|
||||
|
||||
|
||||
const
|
||||
isExistsSqlite* = getEnv("DB_SQLITE", $false).parseBool
|
||||
isExistsPostgres* = getEnv("DB_POSTGRES", $false).parseBool
|
||||
isExistsMysql* = getEnv("DB_MYSQL", $false).parseBool
|
||||
isExistsMariadb* = getEnv("DB_MARIADB", $false).parseBool
|
||||
isExistsSurrealdb* = getEnv("DB_SURREAL", $false).parseBool
|
||||
isExistsBaradb* = getEnv("DB_BARADB", $false).parseBool
|
||||
@@ -0,0 +1,41 @@
|
||||
import ./env
|
||||
|
||||
|
||||
import ./query_builder/enums; export enums
|
||||
import ./query_builder/error; export error
|
||||
import ./query_builder/models/orm; export orm
|
||||
|
||||
when isExistsSqlite:
|
||||
import ./query_builder/models/sqlite/sqlite_types; export SQLite3, SqliteConnections, SqlitePreparedContext, sqlite_types
|
||||
import ./query_builder/models/sqlite/sqlite_query; export sqlite_query
|
||||
import ./query_builder/models/sqlite/sqlite_exec; export sqlite_exec
|
||||
import ./query_builder/models/sqlite/sqlite_transaction; export sqlite_transaction
|
||||
|
||||
when isExistsPostgres:
|
||||
import ./query_builder/models/postgres/postgres_types; export postgres_types
|
||||
import ./query_builder/models/postgres/postgres_query; export postgres_query
|
||||
import ./query_builder/models/postgres/postgres_exec; export postgres_exec
|
||||
import ./query_builder/models/postgres/poatgres_transaction; export poatgres_transaction
|
||||
|
||||
when isExistsMariadb:
|
||||
import ./query_builder/models/mariadb/mariadb_types; export mariadb_types
|
||||
import ./query_builder/models/mariadb/mariadb_query; export mariadb_query
|
||||
import ./query_builder/models/mariadb/mariadb_exec; export mariadb_exec
|
||||
import ./query_builder/models/mariadb/mariadb_transaction; export mariadb_transaction
|
||||
|
||||
when isExistsMysql:
|
||||
import ./query_builder/models/mysql/mysql_types; export mysql_types
|
||||
import ./query_builder/models/mysql/mysql_query; export mysql_query
|
||||
import ./query_builder/models/mysql/mysql_exec; export mysql_exec
|
||||
import ./query_builder/models/mysql/mysql_transaction; export mysql_transaction
|
||||
|
||||
when isExistsSurrealdb:
|
||||
import ./query_builder/models/surreal/surreal_types; export surreal_types
|
||||
import ./query_builder/models/surreal/surreal_query; export surreal_query
|
||||
import ./query_builder/models/surreal/surreal_exec; export surreal_exec
|
||||
|
||||
when isExistsBaradb:
|
||||
import ./query_builder/models/baradb/baradb_types; export baradb_types
|
||||
import ./query_builder/models/baradb/baradb_query; export baradb_query
|
||||
import ./query_builder/models/baradb/baradb_exec; export baradb_exec
|
||||
import ./query_builder/models/baradb/baradb_transaction; export baradb_transaction
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
type Order* = enum
|
||||
Asc = "ASC"
|
||||
Desc = "DESC"
|
||||
|
||||
type Collation* = enum
|
||||
None = ""
|
||||
Collate = "COLLATE"
|
||||
Numeric = "NUMERIC"
|
||||
@@ -0,0 +1,9 @@
|
||||
type
|
||||
DbError* = object of IOError ## exception that is raised if a database error occurs
|
||||
|
||||
proc dbError*(msg: string) {.noreturn, noinline.} =
|
||||
## raises an DbError exception with message `msg`.
|
||||
var e: ref DbError
|
||||
new(e)
|
||||
e.msg = msg
|
||||
raise e
|
||||
@@ -0,0 +1,706 @@
|
||||
## BaraDB Client — Self-contained Nim client library
|
||||
## No dependency on BaraDB server code.
|
||||
## Communicates via the BaraDB Wire Protocol (binary, big-endian).
|
||||
|
||||
import std/asyncdispatch
|
||||
import std/asyncnet
|
||||
import std/net as netmod
|
||||
import std/locks
|
||||
import std/strutils
|
||||
import std/endians
|
||||
|
||||
# === Wire Protocol (self-contained, no server dependency) ===
|
||||
|
||||
const
|
||||
ProtocolMagic* = 0x42415241'u32
|
||||
|
||||
type
|
||||
FieldKind* = enum
|
||||
fkNull = 0x00
|
||||
fkBool = 0x01
|
||||
fkInt8 = 0x02
|
||||
fkInt16 = 0x03
|
||||
fkInt32 = 0x04
|
||||
fkInt64 = 0x05
|
||||
fkFloat32 = 0x06
|
||||
fkFloat64 = 0x07
|
||||
fkString = 0x08
|
||||
fkBytes = 0x09
|
||||
fkArray = 0x0A
|
||||
fkObject = 0x0B
|
||||
fkVector = 0x0C
|
||||
fkJson = 0x0D
|
||||
|
||||
MsgKind* = enum
|
||||
# Client messages
|
||||
mkClientHandshake = 0x01
|
||||
mkQuery = 0x02
|
||||
mkQueryParams = 0x03
|
||||
mkExecute = 0x04
|
||||
mkBatch = 0x05
|
||||
mkTransaction = 0x06
|
||||
mkClose = 0x07
|
||||
mkPing = 0x08
|
||||
mkAuth = 0x09
|
||||
# Server messages
|
||||
mkServerHandshake = 0x80
|
||||
mkReady = 0x81
|
||||
mkData = 0x82
|
||||
mkComplete = 0x83
|
||||
mkError = 0x84
|
||||
mkAuthChallenge = 0x85
|
||||
mkAuthOk = 0x86
|
||||
mkSchemaChange = 0x87
|
||||
mkPong = 0x88
|
||||
mkTransactionState = 0x89
|
||||
|
||||
ResultFormat* = enum
|
||||
rfBinary = 0x00
|
||||
rfJson = 0x01
|
||||
rfText = 0x02
|
||||
|
||||
WireValue* = object
|
||||
case kind*: FieldKind
|
||||
of fkNull: discard
|
||||
of fkBool: boolVal*: bool
|
||||
of fkInt8: int8Val*: int8
|
||||
of fkInt16: int16Val*: int16
|
||||
of fkInt32: int32Val*: int32
|
||||
of fkInt64: int64Val*: int64
|
||||
of fkFloat32: float32Val*: float32
|
||||
of fkFloat64: float64Val*: float64
|
||||
of fkString: strVal*: string
|
||||
of fkBytes: bytesVal*: seq[byte]
|
||||
of fkArray: arrayVal*: seq[WireValue]
|
||||
of fkObject: objVal*: seq[(string, WireValue)]
|
||||
of fkVector: vecVal*: seq[float32]
|
||||
of fkJson: jsonVal*: string
|
||||
|
||||
proc writeUint32(buf: var seq[byte], val: uint32) =
|
||||
var bytes: array[4, byte]
|
||||
bigEndian32(addr bytes, unsafeAddr val)
|
||||
buf.add(bytes)
|
||||
|
||||
proc writeUint64(buf: var seq[byte], val: uint64) =
|
||||
var bytes: array[8, byte]
|
||||
bigEndian64(addr bytes, unsafeAddr val)
|
||||
buf.add(bytes)
|
||||
|
||||
proc writeString(buf: var seq[byte], s: string) =
|
||||
buf.writeUint32(uint32(s.len))
|
||||
for ch in s:
|
||||
buf.add(byte(ch))
|
||||
|
||||
proc readUint32(buf: openArray[byte], pos: var int): uint32 =
|
||||
var bytes: array[4, byte]
|
||||
for i in 0..3: bytes[i] = buf[pos + i]
|
||||
bigEndian32(addr result, unsafeAddr bytes)
|
||||
pos += 4
|
||||
|
||||
proc readUint64(buf: openArray[byte], pos: var int): uint64 =
|
||||
var bytes: array[8, byte]
|
||||
for i in 0..7: bytes[i] = buf[pos + i]
|
||||
bigEndian64(addr result, unsafeAddr bytes)
|
||||
pos += 8
|
||||
|
||||
proc readString(buf: openArray[byte], pos: var int): string =
|
||||
let len = int(readUint32(buf, pos))
|
||||
result = newString(len)
|
||||
for i in 0..<len:
|
||||
result[i] = char(buf[pos + i])
|
||||
pos += len
|
||||
|
||||
proc toBytes(s: string): seq[byte] =
|
||||
result = newSeq[byte](s.len)
|
||||
for i, c in s:
|
||||
result[i] = byte(c)
|
||||
|
||||
proc toString(s: seq[byte]): string =
|
||||
result = newString(s.len)
|
||||
for i, b in s:
|
||||
result[i] = char(b)
|
||||
|
||||
proc serializeValue*(buf: var seq[byte], val: WireValue) =
|
||||
buf.add(byte(val.kind))
|
||||
case val.kind
|
||||
of fkNull: discard
|
||||
of fkBool: buf.add(if val.boolVal: 1'u8 else: 0'u8)
|
||||
of fkInt8: buf.add(uint8(val.int8Val))
|
||||
of fkInt16:
|
||||
var bytes16: array[2, byte]
|
||||
bigEndian16(addr bytes16, unsafeAddr val.int16Val)
|
||||
buf.add(bytes16)
|
||||
of fkInt32: buf.writeUint32(uint32(val.int32Val))
|
||||
of fkInt64: buf.writeUint64(uint64(val.int64Val))
|
||||
of fkFloat32:
|
||||
var bytes32: array[4, byte]
|
||||
copyMem(addr bytes32, unsafeAddr val.float32Val, 4)
|
||||
buf.add(bytes32)
|
||||
of fkFloat64:
|
||||
var bytes: array[8, byte]
|
||||
copyMem(addr bytes, unsafeAddr val.float64Val, 8)
|
||||
buf.add(bytes)
|
||||
of fkString: buf.writeString(val.strVal)
|
||||
of fkBytes:
|
||||
buf.writeUint32(uint32(val.bytesVal.len))
|
||||
buf.add(val.bytesVal)
|
||||
of fkArray:
|
||||
buf.writeUint32(uint32(val.arrayVal.len))
|
||||
for item in val.arrayVal:
|
||||
buf.serializeValue(item)
|
||||
of fkObject:
|
||||
buf.writeUint32(uint32(val.objVal.len))
|
||||
for (name, item) in val.objVal:
|
||||
buf.writeString(name)
|
||||
buf.serializeValue(item)
|
||||
of fkVector:
|
||||
buf.writeUint32(uint32(val.vecVal.len))
|
||||
for f in val.vecVal:
|
||||
var fb: array[4, byte]
|
||||
copyMem(addr fb, unsafeAddr f, 4)
|
||||
buf.add(fb)
|
||||
of fkJson: buf.writeString(val.jsonVal)
|
||||
|
||||
proc deserializeValue*(buf: openArray[byte], pos: var int): WireValue =
|
||||
let kind = FieldKind(buf[pos])
|
||||
inc pos
|
||||
case kind
|
||||
of fkNull: result = WireValue(kind: fkNull)
|
||||
of fkBool:
|
||||
result = WireValue(kind: fkBool, boolVal: buf[pos] != 0)
|
||||
inc pos
|
||||
of fkInt8:
|
||||
result = WireValue(kind: fkInt8, int8Val: cast[int8](buf[pos]))
|
||||
inc pos
|
||||
of fkInt16:
|
||||
var bytes16: array[2, byte]
|
||||
for i in 0..1: bytes16[i] = buf[pos + i]
|
||||
var v16: int16
|
||||
bigEndian16(addr v16, unsafeAddr bytes16)
|
||||
result = WireValue(kind: fkInt16, int16Val: v16)
|
||||
pos += 2
|
||||
of fkInt32:
|
||||
result = WireValue(kind: fkInt32, int32Val: int32(readUint32(buf, pos)))
|
||||
of fkInt64:
|
||||
result = WireValue(kind: fkInt64, int64Val: int64(readUint64(buf, pos)))
|
||||
of fkFloat32:
|
||||
var v32: float32
|
||||
copyMem(addr v32, addr buf[pos], 4)
|
||||
result = WireValue(kind: fkFloat32, float32Val: v32)
|
||||
pos += 4
|
||||
of fkFloat64:
|
||||
var v: float64
|
||||
copyMem(addr v, addr buf[pos], 8)
|
||||
result = WireValue(kind: fkFloat64, float64Val: v)
|
||||
pos += 8
|
||||
of fkString:
|
||||
result = WireValue(kind: fkString, strVal: readString(buf, pos))
|
||||
of fkBytes:
|
||||
let blen = int(readUint32(buf, pos))
|
||||
var bval: seq[byte] = @[]
|
||||
for i in 0..<blen:
|
||||
bval.add(buf[pos + i])
|
||||
result = WireValue(kind: fkBytes, bytesVal: bval)
|
||||
pos += blen
|
||||
of fkArray:
|
||||
let count = int(readUint32(buf, pos))
|
||||
var arr: seq[WireValue] = @[]
|
||||
for i in 0..<count:
|
||||
arr.add(deserializeValue(buf, pos))
|
||||
result = WireValue(kind: fkArray, arrayVal: arr)
|
||||
of fkObject:
|
||||
let count = int(readUint32(buf, pos))
|
||||
var obj: seq[(string, WireValue)] = @[]
|
||||
for i in 0..<count:
|
||||
let name = readString(buf, pos)
|
||||
let val = deserializeValue(buf, pos)
|
||||
obj.add((name, val))
|
||||
result = WireValue(kind: fkObject, objVal: obj)
|
||||
of fkVector:
|
||||
let dim = int(readUint32(buf, pos))
|
||||
var vec: seq[float32] = @[]
|
||||
for i in 0..<dim:
|
||||
var fv: float32
|
||||
copyMem(addr fv, addr buf[pos], 4)
|
||||
vec.add(fv)
|
||||
pos += 4
|
||||
result = WireValue(kind: fkVector, vecVal: vec)
|
||||
of fkJson:
|
||||
result = WireValue(kind: fkJson, jsonVal: readString(buf, pos))
|
||||
|
||||
proc buildMessage*(kind: MsgKind, requestId: uint32, payload: seq[byte]): seq[byte] =
|
||||
result = @[]
|
||||
result.writeUint32(uint32(kind))
|
||||
result.writeUint32(uint32(payload.len))
|
||||
result.writeUint32(requestId)
|
||||
result.add(payload)
|
||||
|
||||
proc makeQueryMessage*(requestId: uint32, query: string): seq[byte] =
|
||||
var payload: seq[byte] = @[]
|
||||
payload.writeString(query)
|
||||
payload.add(byte(rfBinary))
|
||||
buildMessage(mkQuery, requestId, payload)
|
||||
|
||||
proc makeQueryParamsMessage*(requestId: uint32, query: string, params: seq[WireValue]): seq[byte] =
|
||||
var payload: seq[byte] = @[]
|
||||
payload.writeString(query)
|
||||
payload.add(byte(rfBinary))
|
||||
payload.writeUint32(uint32(params.len))
|
||||
for p in params:
|
||||
payload.serializeValue(p)
|
||||
buildMessage(mkQueryParams, requestId, payload)
|
||||
|
||||
proc makeAuthMessage*(requestId: uint32, token: string): seq[byte] =
|
||||
var payload: seq[byte] = @[]
|
||||
payload.writeString(token)
|
||||
buildMessage(mkAuth, requestId, payload)
|
||||
|
||||
# === Client Library ===
|
||||
|
||||
type
|
||||
ClientConfig* = object
|
||||
host*: string
|
||||
port*: int
|
||||
database*: string
|
||||
username*: string
|
||||
password*: string
|
||||
timeoutMs*: int
|
||||
maxRetries*: int
|
||||
|
||||
QueryResult* = object
|
||||
columns*: seq[string]
|
||||
columnTypes*: seq[string]
|
||||
rows*: seq[seq[string]]
|
||||
rowCount*: int
|
||||
affectedRows*: int
|
||||
executionTimeMs*: float64
|
||||
|
||||
BaraClient* = ref object
|
||||
config: ClientConfig
|
||||
socket: AsyncSocket
|
||||
connected: bool
|
||||
requestId: uint32
|
||||
|
||||
proc defaultConfig*(): ClientConfig =
|
||||
ClientConfig(
|
||||
host: "127.0.0.1", port: 9472, database: "default",
|
||||
username: "admin", password: "", timeoutMs: 30000, maxRetries: 3,
|
||||
)
|
||||
|
||||
proc newClient*(config: ClientConfig = defaultConfig()): BaraClient =
|
||||
BaraClient(config: config, socket: newAsyncSocket(), connected: false, requestId: 0)
|
||||
|
||||
proc connect*(client: BaraClient) {.async.} =
|
||||
await client.socket.connect(client.config.host, Port(client.config.port))
|
||||
client.connected = true
|
||||
|
||||
proc nextId(client: BaraClient): uint32 =
|
||||
inc client.requestId; client.requestId
|
||||
|
||||
proc close*(client: BaraClient) =
|
||||
if client.connected:
|
||||
try:
|
||||
let msg = buildMessage(mkClose, client.nextId(), @[])
|
||||
waitFor client.socket.send(toString(msg))
|
||||
except: discard
|
||||
client.socket.close()
|
||||
client.connected = false
|
||||
|
||||
proc isConnected*(client: BaraClient): bool = client.connected
|
||||
|
||||
proc wireValueToString*(wv: WireValue): string =
|
||||
case wv.kind
|
||||
of fkNull: return ""
|
||||
of fkBool: return if wv.boolVal: "true" else: "false"
|
||||
of fkInt8: return $wv.int8Val
|
||||
of fkInt16: return $wv.int16Val
|
||||
of fkInt32: return $wv.int32Val
|
||||
of fkInt64: return $wv.int64Val
|
||||
of fkFloat32: return $wv.float32Val
|
||||
of fkFloat64: return $wv.float64Val
|
||||
of fkString: return wv.strVal
|
||||
of fkBytes: return "<bytes:" & $wv.bytesVal.len & ">"
|
||||
of fkArray: return "<array:" & $wv.arrayVal.len & ">"
|
||||
of fkObject: return "<object:" & $wv.objVal.len & ">"
|
||||
of fkVector: return "<vector:" & $wv.vecVal.len & ">"
|
||||
of fkJson: return wv.jsonVal
|
||||
|
||||
proc readQueryResponse(client: BaraClient): Future[QueryResult] {.async.} =
|
||||
let headerData = await client.socket.recv(12)
|
||||
if headerData.len < 12:
|
||||
raise newException(IOError, "Connection closed")
|
||||
|
||||
var pos = 0
|
||||
let hdrData = toBytes(headerData)
|
||||
let kind = MsgKind(readUint32(hdrData, pos))
|
||||
let payloadLen = int(readUint32(hdrData, pos))
|
||||
discard readUint32(hdrData, pos)
|
||||
|
||||
let payloadStr = await client.socket.recv(payloadLen)
|
||||
var payload = toBytes(payloadStr)
|
||||
|
||||
result = QueryResult(columns: @[], rows: @[], rowCount: 0, affectedRows: 0)
|
||||
|
||||
if kind == mkReady:
|
||||
return
|
||||
if kind == mkError and payload.len >= 8:
|
||||
var epos = 0
|
||||
let code = readUint32(payload, epos)
|
||||
let emsg = readString(payload, epos)
|
||||
raise newException(IOError, "Error " & $code & ": " & emsg)
|
||||
if kind == mkData:
|
||||
var dpos = 0
|
||||
let colCount = int(readUint32(payload, dpos))
|
||||
var cols: seq[string] = @[]
|
||||
for i in 0..<colCount:
|
||||
cols.add(readString(payload, dpos))
|
||||
result.columns = cols
|
||||
var colTypes: seq[string] = @[]
|
||||
for i in 0..<colCount:
|
||||
colTypes.add($FieldKind(payload[dpos]))
|
||||
inc dpos
|
||||
result.columnTypes = colTypes
|
||||
let rowCount = int(readUint32(payload, dpos))
|
||||
for r in 0..<rowCount:
|
||||
var row: seq[string] = @[]
|
||||
for c in 0..<colCount:
|
||||
let wv = deserializeValue(payload, dpos)
|
||||
row.add(wireValueToString(wv))
|
||||
result.rows.add(row)
|
||||
result.rowCount = rowCount
|
||||
# Read following mkComplete message
|
||||
let compHeader = await client.socket.recv(12)
|
||||
if compHeader.len >= 12:
|
||||
var chPos = 0
|
||||
let chData = toBytes(compHeader)
|
||||
let compKind = MsgKind(readUint32(chData, chPos))
|
||||
let compLen = int(readUint32(chData, chPos))
|
||||
discard readUint32(chData, chPos)
|
||||
let compPayloadStr = await client.socket.recv(compLen)
|
||||
if compKind == mkComplete:
|
||||
var cpPos = 0
|
||||
result.affectedRows = int(readUint32(toBytes(compPayloadStr), cpPos))
|
||||
return
|
||||
if kind == mkComplete:
|
||||
var rpos = 0
|
||||
result.affectedRows = int(readUint32(payload, rpos))
|
||||
return
|
||||
|
||||
proc query*(client: BaraClient, sql: string): Future[QueryResult] {.async.} =
|
||||
if not client.connected:
|
||||
raise newException(IOError, "Not connected")
|
||||
|
||||
let msg = makeQueryMessage(client.nextId(), sql)
|
||||
let msgStr = toString(msg)
|
||||
await client.socket.send(msgStr)
|
||||
|
||||
return await client.readQueryResponse()
|
||||
|
||||
proc query*(client: BaraClient, sql: string, params: seq[WireValue]): Future[QueryResult] {.async.} =
|
||||
if not client.connected:
|
||||
raise newException(IOError, "Not connected")
|
||||
|
||||
let msg = makeQueryParamsMessage(client.nextId(), sql, params)
|
||||
let msgStr = toString(msg)
|
||||
await client.socket.send(msgStr)
|
||||
|
||||
return await client.readQueryResponse()
|
||||
|
||||
proc exec*(client: BaraClient, sql: string): Future[int] {.async.} =
|
||||
let qr = await client.query(sql)
|
||||
return qr.affectedRows
|
||||
|
||||
proc auth*(client: BaraClient, token: string) {.async.} =
|
||||
if not client.connected:
|
||||
raise newException(IOError, "Not connected")
|
||||
|
||||
let msg = makeAuthMessage(client.nextId(), token)
|
||||
let msgStr = toString(msg)
|
||||
await client.socket.send(msgStr)
|
||||
|
||||
let headerData = await client.socket.recv(12)
|
||||
if headerData.len < 12:
|
||||
raise newException(IOError, "Connection closed")
|
||||
|
||||
var pos = 0
|
||||
let hdrData = toBytes(headerData)
|
||||
let kind = MsgKind(readUint32(hdrData, pos))
|
||||
let payloadLen = int(readUint32(hdrData, pos))
|
||||
discard readUint32(hdrData, pos)
|
||||
|
||||
if kind == mkAuthOk:
|
||||
return
|
||||
elif kind == mkError:
|
||||
let payloadStr = await client.socket.recv(payloadLen)
|
||||
var epos = 0
|
||||
let emsg = readString(toBytes(payloadStr), epos)
|
||||
raise newException(IOError, "Auth failed: " & emsg)
|
||||
else:
|
||||
raise newException(IOError, "Unexpected auth response: 0x" & toHex(uint32(kind), 2))
|
||||
|
||||
proc ping*(client: BaraClient): Future[bool] {.async.} =
|
||||
if not client.connected:
|
||||
return false
|
||||
let msg = buildMessage(mkPing, client.nextId(), @[])
|
||||
let msgStr = toString(msg)
|
||||
await client.socket.send(msgStr)
|
||||
|
||||
let headerData = await client.socket.recv(12)
|
||||
if headerData.len < 12:
|
||||
return false
|
||||
|
||||
var pos = 0
|
||||
let hdrData = toBytes(headerData)
|
||||
let kind = MsgKind(readUint32(hdrData, pos))
|
||||
return kind == mkPong
|
||||
|
||||
# === Fluent Query Builder ===
|
||||
|
||||
type
|
||||
QueryBuilder* = ref object
|
||||
client: BaraClient
|
||||
selectCols: seq[string]
|
||||
fromTable: string
|
||||
whereClauses: seq[string]
|
||||
joinClauses: seq[string]
|
||||
groupByCols: seq[string]
|
||||
havingClause: string
|
||||
orderCols: seq[string]
|
||||
orderDirs: seq[string]
|
||||
limitVal: int
|
||||
offsetVal: int
|
||||
|
||||
proc newQueryBuilder*(client: BaraClient): QueryBuilder =
|
||||
QueryBuilder(client: client, limitVal: 0, offsetVal: 0)
|
||||
|
||||
proc select*(qb: QueryBuilder, cols: varargs[string]): QueryBuilder =
|
||||
for c in cols: qb.selectCols.add(c)
|
||||
return qb
|
||||
|
||||
proc `from`*(qb: QueryBuilder, table: string): QueryBuilder =
|
||||
qb.fromTable = table
|
||||
return qb
|
||||
|
||||
proc where*(qb: QueryBuilder, clause: string): QueryBuilder =
|
||||
qb.whereClauses.add(clause)
|
||||
return qb
|
||||
|
||||
proc join*(qb: QueryBuilder, table: string, on: string): QueryBuilder =
|
||||
qb.joinClauses.add("JOIN " & table & " ON " & on)
|
||||
return qb
|
||||
|
||||
proc leftJoin*(qb: QueryBuilder, table: string, on: string): QueryBuilder =
|
||||
qb.joinClauses.add("LEFT JOIN " & table & " ON " & on)
|
||||
return qb
|
||||
|
||||
proc groupBy*(qb: QueryBuilder, cols: varargs[string]): QueryBuilder =
|
||||
for c in cols: qb.groupByCols.add(c)
|
||||
return qb
|
||||
|
||||
proc having*(qb: QueryBuilder, clause: string): QueryBuilder =
|
||||
qb.havingClause = clause
|
||||
return qb
|
||||
|
||||
proc orderBy*(qb: QueryBuilder, col: string, dir: string = "ASC"): QueryBuilder =
|
||||
qb.orderCols.add(col)
|
||||
qb.orderDirs.add(dir)
|
||||
return qb
|
||||
|
||||
proc limit*(qb: QueryBuilder, n: int): QueryBuilder =
|
||||
qb.limitVal = n
|
||||
return qb
|
||||
|
||||
proc offset*(qb: QueryBuilder, n: int): QueryBuilder =
|
||||
qb.offsetVal = n
|
||||
return qb
|
||||
|
||||
proc build*(qb: QueryBuilder): string =
|
||||
result = "SELECT " & (if qb.selectCols.len > 0: qb.selectCols.join(", ") else: "*")
|
||||
result &= " FROM " & qb.fromTable
|
||||
for j in qb.joinClauses: result &= " " & j
|
||||
if qb.whereClauses.len > 0: result &= " WHERE " & qb.whereClauses.join(" AND ")
|
||||
if qb.groupByCols.len > 0: result &= " GROUP BY " & qb.groupByCols.join(", ")
|
||||
if qb.havingClause.len > 0: result &= " HAVING " & qb.havingClause
|
||||
if qb.orderCols.len > 0:
|
||||
result &= " ORDER BY "
|
||||
for i, col in qb.orderCols:
|
||||
if i > 0: result &= ", "
|
||||
result &= col & " " & qb.orderDirs[i]
|
||||
if qb.limitVal > 0: result &= " LIMIT " & $qb.limitVal
|
||||
if qb.offsetVal > 0: result &= " OFFSET " & $qb.offsetVal
|
||||
|
||||
proc exec*(qb: QueryBuilder): Future[QueryResult] {.async.} =
|
||||
return await qb.client.query(qb.build())
|
||||
|
||||
# === Blocking Sync Client (production-grade, no waitFor) ===
|
||||
|
||||
type
|
||||
SyncClient* = ref object
|
||||
config: ClientConfig
|
||||
socket: netmod.Socket
|
||||
connected: bool
|
||||
requestId: uint32
|
||||
lock: Lock
|
||||
|
||||
proc newSyncClient*(config: ClientConfig = defaultConfig()): SyncClient =
|
||||
result = SyncClient(config: config, connected: false, requestId: 0)
|
||||
result.socket = netmod.newSocket()
|
||||
initLock(result.lock)
|
||||
|
||||
proc recvExact(sock: netmod.Socket, size: int): string =
|
||||
result = ""
|
||||
while result.len < size:
|
||||
let chunk = sock.recv(size - result.len)
|
||||
if chunk.len == 0:
|
||||
raise newException(IOError, "Connection closed")
|
||||
result.add(chunk)
|
||||
|
||||
proc readQueryResponseBlocking(client: SyncClient): QueryResult =
|
||||
let headerData = client.socket.recvExact(12)
|
||||
var pos = 0
|
||||
let hdrData = toBytes(headerData)
|
||||
let kind = MsgKind(readUint32(hdrData, pos))
|
||||
let payloadLen = int(readUint32(hdrData, pos))
|
||||
discard readUint32(hdrData, pos)
|
||||
|
||||
let payloadStr = client.socket.recvExact(payloadLen)
|
||||
var payload = toBytes(payloadStr)
|
||||
|
||||
result = QueryResult(columns: @[], rows: @[], rowCount: 0, affectedRows: 0)
|
||||
|
||||
if kind == mkReady:
|
||||
return
|
||||
if kind == mkError and payload.len >= 8:
|
||||
var epos = 0
|
||||
let code = readUint32(payload, epos)
|
||||
let emsg = readString(payload, epos)
|
||||
raise newException(IOError, "Error " & $code & ": " & emsg)
|
||||
if kind == mkData:
|
||||
var dpos = 0
|
||||
let colCount = int(readUint32(payload, dpos))
|
||||
var cols: seq[string] = @[]
|
||||
for i in 0..<colCount:
|
||||
cols.add(readString(payload, dpos))
|
||||
result.columns = cols
|
||||
var colTypes: seq[string] = @[]
|
||||
for i in 0..<colCount:
|
||||
colTypes.add($FieldKind(payload[dpos]))
|
||||
inc dpos
|
||||
result.columnTypes = colTypes
|
||||
let rowCount = int(readUint32(payload, dpos))
|
||||
for r in 0..<rowCount:
|
||||
var row: seq[string] = @[]
|
||||
for c in 0..<colCount:
|
||||
let wv = deserializeValue(payload, dpos)
|
||||
row.add(wireValueToString(wv))
|
||||
result.rows.add(row)
|
||||
result.rowCount = rowCount
|
||||
# Read following mkComplete message
|
||||
let compHeader = client.socket.recvExact(12)
|
||||
var chPos = 0
|
||||
let chData = toBytes(compHeader)
|
||||
let compKind = MsgKind(readUint32(chData, chPos))
|
||||
let compLen = int(readUint32(chData, chPos))
|
||||
discard readUint32(chData, chPos)
|
||||
let compPayloadStr = client.socket.recvExact(compLen)
|
||||
if compKind == mkComplete:
|
||||
var cpPos = 0
|
||||
result.affectedRows = int(readUint32(toBytes(compPayloadStr), cpPos))
|
||||
return
|
||||
if kind == mkComplete:
|
||||
var rpos = 0
|
||||
result.affectedRows = int(readUint32(payload, rpos))
|
||||
return
|
||||
|
||||
proc connect*(client: SyncClient) =
|
||||
netmod.connect(client.socket, client.config.host, Port(client.config.port))
|
||||
client.connected = true
|
||||
|
||||
proc close*(client: SyncClient) =
|
||||
if client.connected:
|
||||
try:
|
||||
let msg = buildMessage(mkClose, 0, @[])
|
||||
netmod.send(client.socket, toString(msg))
|
||||
except: discard
|
||||
netmod.close(client.socket)
|
||||
client.connected = false
|
||||
deinitLock(client.lock)
|
||||
|
||||
proc query*(client: SyncClient, sql: string): QueryResult =
|
||||
acquire(client.lock)
|
||||
try:
|
||||
if not client.connected:
|
||||
raise newException(IOError, "Not connected")
|
||||
let msg = makeQueryMessage(0, sql)
|
||||
netmod.send(client.socket, toString(msg))
|
||||
return readQueryResponseBlocking(client)
|
||||
finally:
|
||||
release(client.lock)
|
||||
|
||||
proc query*(client: SyncClient, sql: string, params: seq[WireValue]): QueryResult =
|
||||
acquire(client.lock)
|
||||
try:
|
||||
if not client.connected:
|
||||
raise newException(IOError, "Not connected")
|
||||
let msg = makeQueryParamsMessage(0, sql, params)
|
||||
netmod.send(client.socket, toString(msg))
|
||||
return readQueryResponseBlocking(client)
|
||||
finally:
|
||||
release(client.lock)
|
||||
|
||||
proc exec*(client: SyncClient, sql: string): int =
|
||||
let qr = client.query(sql)
|
||||
return qr.affectedRows
|
||||
|
||||
proc auth*(client: SyncClient, token: string) =
|
||||
acquire(client.lock)
|
||||
try:
|
||||
if not client.connected:
|
||||
raise newException(IOError, "Not connected")
|
||||
let msg = makeAuthMessage(0, token)
|
||||
netmod.send(client.socket, toString(msg))
|
||||
let headerData = client.socket.recvExact(12)
|
||||
var pos = 0
|
||||
let hdrData = toBytes(headerData)
|
||||
let kind = MsgKind(readUint32(hdrData, pos))
|
||||
let payloadLen = int(readUint32(hdrData, pos))
|
||||
discard readUint32(hdrData, pos)
|
||||
if kind == mkAuthOk:
|
||||
return
|
||||
elif kind == mkError:
|
||||
let payloadStr = client.socket.recvExact(payloadLen)
|
||||
var epos = 0
|
||||
let emsg = readString(toBytes(payloadStr), epos)
|
||||
raise newException(IOError, "Auth failed: " & emsg)
|
||||
else:
|
||||
raise newException(IOError, "Unexpected auth response")
|
||||
finally:
|
||||
release(client.lock)
|
||||
|
||||
proc ping*(client: SyncClient): bool =
|
||||
acquire(client.lock)
|
||||
try:
|
||||
if not client.connected:
|
||||
return false
|
||||
let msg = buildMessage(mkPing, 0, @[])
|
||||
netmod.send(client.socket, toString(msg))
|
||||
let headerData = client.socket.recvExact(12)
|
||||
var pos = 0
|
||||
let hdrData = toBytes(headerData)
|
||||
let kind = MsgKind(readUint32(hdrData, pos))
|
||||
return kind == mkPong
|
||||
except:
|
||||
return false
|
||||
finally:
|
||||
release(client.lock)
|
||||
|
||||
proc `$`*(qr: QueryResult): string =
|
||||
if qr.columns.len == 0: return "(no results)"
|
||||
result = ""
|
||||
for i, col in qr.columns:
|
||||
result &= col
|
||||
if i < qr.columns.len - 1: result &= ", "
|
||||
result &= "\n"
|
||||
for row in qr.rows:
|
||||
result &= row.join(", ") & "\n"
|
||||
result &= "(" & $qr.rowCount & " rows)"
|
||||
@@ -0,0 +1,101 @@
|
||||
import std/strutils
|
||||
import std/uri
|
||||
|
||||
|
||||
type DatabaseUrl* = distinct string
|
||||
|
||||
type DatabaseUrlQuery* = tuple[key, value: string]
|
||||
|
||||
type ParsedDatabaseUrl* = object
|
||||
raw*: string
|
||||
scheme*: string
|
||||
username*: string
|
||||
password*: string
|
||||
hostname*: string
|
||||
port*: int
|
||||
hasPort*: bool
|
||||
path*: string
|
||||
query*: seq[DatabaseUrlQuery]
|
||||
|
||||
|
||||
proc asDatabaseUrl*(value: string): DatabaseUrl =
|
||||
DatabaseUrl(value)
|
||||
|
||||
|
||||
proc `$`*(value: DatabaseUrl): string =
|
||||
string(value)
|
||||
|
||||
|
||||
proc parseQueryPairs(query: string): seq[DatabaseUrlQuery] =
|
||||
if query.len == 0:
|
||||
return
|
||||
|
||||
for item in query.split('&'):
|
||||
if item.len == 0:
|
||||
continue
|
||||
|
||||
let eqIndex = item.find('=')
|
||||
if eqIndex < 0:
|
||||
result.add((decodeUrl(item), ""))
|
||||
else:
|
||||
result.add((decodeUrl(item[0..<eqIndex]), decodeUrl(item[eqIndex + 1 .. ^1])))
|
||||
|
||||
|
||||
proc parseDatabaseUrl*(value: string): ParsedDatabaseUrl =
|
||||
let parsed = parseUri(value)
|
||||
|
||||
result.raw = value
|
||||
result.scheme = parsed.scheme.toLowerAscii()
|
||||
result.username = decodeUrl(parsed.username)
|
||||
result.password = decodeUrl(parsed.password)
|
||||
result.hostname = decodeUrl(parsed.hostname)
|
||||
result.path = decodeUrl(parsed.path)
|
||||
result.query = parseQueryPairs(parsed.query)
|
||||
|
||||
if parsed.port.len > 0:
|
||||
result.port = parsed.port.parseInt()
|
||||
result.hasPort = true
|
||||
|
||||
|
||||
proc parseDatabaseUrl*(value: DatabaseUrl): ParsedDatabaseUrl =
|
||||
parseDatabaseUrl(string(value))
|
||||
|
||||
|
||||
proc requireDatabaseUrlScheme*(value: ParsedDatabaseUrl; expectedSchemes: openArray[string];
|
||||
driverName: string) =
|
||||
if value.scheme.len == 0 or value.scheme notin expectedSchemes:
|
||||
raise newException(
|
||||
ValueError,
|
||||
"Invalid URL format. Expected a " & driverName & " URL starting with '" &
|
||||
expectedSchemes[0] & "://'."
|
||||
)
|
||||
|
||||
|
||||
proc stripLeadingSlash(value: string): string =
|
||||
if value.len > 0 and value[0] == '/':
|
||||
if value.len == 1:
|
||||
return ""
|
||||
return value[1..^1]
|
||||
value
|
||||
|
||||
|
||||
proc databaseName*(value: ParsedDatabaseUrl): string =
|
||||
stripLeadingSlash(value.path)
|
||||
|
||||
|
||||
proc sqliteDatabasePath*(value: ParsedDatabaseUrl): string =
|
||||
if value.hostname.len == 0:
|
||||
return value.path
|
||||
|
||||
let tail = stripLeadingSlash(value.path)
|
||||
if tail.len == 0:
|
||||
return value.hostname
|
||||
|
||||
result = value.hostname & "/" & tail
|
||||
|
||||
|
||||
proc portOrDefault*(value: ParsedDatabaseUrl; defaultPort: int): int =
|
||||
if value.hasPort:
|
||||
value.port
|
||||
else:
|
||||
defaultPort
|
||||
@@ -0,0 +1,558 @@
|
||||
import std/asyncdispatch
|
||||
import std/monotimes
|
||||
import std/times
|
||||
import std/json
|
||||
import ../../error
|
||||
import ../../models/database_types
|
||||
import ../../models/mariadb/mariadb_types
|
||||
import ../../prepared_param
|
||||
import ./mariadb_rdb
|
||||
import ./mariadb_lib
|
||||
|
||||
|
||||
type
|
||||
MariadbWaitState = object
|
||||
cancelled: bool
|
||||
|
||||
proc mariadbTimeoutError(): ref DbError =
|
||||
newException(DbError, "MariaDB query timeout")
|
||||
|
||||
proc makeDeadline(timeout: int): MonoTime =
|
||||
if timeout > 0:
|
||||
result = getMonoTime() + initDuration(seconds = timeout)
|
||||
else:
|
||||
result = getMonoTime()
|
||||
|
||||
proc remainingMs(deadline: MonoTime): int =
|
||||
let left = (deadline - getMonoTime()).inMilliseconds
|
||||
if left <= 0:
|
||||
return 0
|
||||
if left > int64(high(int)):
|
||||
return high(int)
|
||||
result = int(left)
|
||||
if result < 1:
|
||||
result = 1
|
||||
|
||||
proc ensureMariadbSocketRegistered(conn: PMySQL): AsyncFD =
|
||||
let sock = mariadb_rdb.get_socket(conn)
|
||||
if sock < 0:
|
||||
dbError(conn)
|
||||
result = AsyncFD(cint(sock))
|
||||
let disp = getGlobalDispatcher()
|
||||
if not disp.contains(result):
|
||||
register(result)
|
||||
|
||||
proc waitMariadb(conn: PMySQL, waitStatus: cint, deadline: MonoTime): Future[cint] {.async.} =
|
||||
if waitStatus == 0:
|
||||
return 0
|
||||
|
||||
let totalRemaining = remainingMs(deadline)
|
||||
if totalRemaining <= 0:
|
||||
raise mariadbTimeoutError()
|
||||
|
||||
var stepTimeout = totalRemaining
|
||||
if (waitStatus and MYSQL_WAIT_TIMEOUT) != 0:
|
||||
let connectorTimeout = int(mariadb_rdb.get_timeout_value_ms(conn))
|
||||
if connectorTimeout > 0 and connectorTimeout < stepTimeout:
|
||||
stepTimeout = connectorTimeout
|
||||
if stepTimeout < 1:
|
||||
stepTimeout = 1
|
||||
|
||||
let shouldWaitRead = (waitStatus and (MYSQL_WAIT_READ or MYSQL_WAIT_EXCEPT)) != 0
|
||||
let shouldWaitWrite = (waitStatus and MYSQL_WAIT_WRITE) != 0
|
||||
|
||||
if not shouldWaitRead and not shouldWaitWrite:
|
||||
await sleepAsync(stepTimeout)
|
||||
return cint(MYSQL_WAIT_TIMEOUT)
|
||||
|
||||
let fd = ensureMariadbSocketRegistered(conn)
|
||||
var state = MariadbWaitState(cancelled: false)
|
||||
var ioReady = newFuture[void]("waitMariadb")
|
||||
var readyStatus = 0.cint
|
||||
|
||||
if shouldWaitRead:
|
||||
proc readCb(f: AsyncFD): bool =
|
||||
if state.cancelled:
|
||||
return true
|
||||
readyStatus = readyStatus or cint(MYSQL_WAIT_READ)
|
||||
if (waitStatus and MYSQL_WAIT_EXCEPT) != 0:
|
||||
readyStatus = readyStatus or cint(MYSQL_WAIT_EXCEPT)
|
||||
if not ioReady.finished:
|
||||
ioReady.complete()
|
||||
return true
|
||||
addRead(fd, readCb)
|
||||
|
||||
if shouldWaitWrite:
|
||||
proc writeCb(f: AsyncFD): bool =
|
||||
if state.cancelled:
|
||||
return true
|
||||
readyStatus = readyStatus or cint(MYSQL_WAIT_WRITE)
|
||||
if not ioReady.finished:
|
||||
ioReady.complete()
|
||||
return true
|
||||
addWrite(fd, writeCb)
|
||||
|
||||
let ok = await withTimeout(ioReady, stepTimeout)
|
||||
if not ok:
|
||||
state.cancelled = true
|
||||
unregister(fd)
|
||||
if (waitStatus and MYSQL_WAIT_TIMEOUT) != 0:
|
||||
return cint(MYSQL_WAIT_TIMEOUT)
|
||||
raise mariadbTimeoutError()
|
||||
|
||||
if readyStatus == 0:
|
||||
if (waitStatus and MYSQL_WAIT_TIMEOUT) != 0:
|
||||
return cint(MYSQL_WAIT_TIMEOUT)
|
||||
return waitStatus and cint(MYSQL_WAIT_READ or MYSQL_WAIT_WRITE or MYSQL_WAIT_EXCEPT)
|
||||
|
||||
return readyStatus
|
||||
|
||||
proc runRealQuery(conn: PMySQL, q: string, deadline: MonoTime): Future[void] {.async.} =
|
||||
var queryStatus = 0'i32
|
||||
var waitStatus = mariadb_rdb.real_query_start(addr queryStatus, conn, q.cstring, culong(q.len))
|
||||
while waitStatus != 0:
|
||||
let ready = await waitMariadb(conn, waitStatus, deadline)
|
||||
waitStatus = mariadb_rdb.real_query_cont(addr queryStatus, conn, ready)
|
||||
if queryStatus != 0'i32:
|
||||
dbError(conn)
|
||||
|
||||
proc runFetchRow(conn: PMySQL, sqlres: PRES, deadline: MonoTime): Future[mariadb_rdb.Row] {.async.} =
|
||||
var row: mariadb_rdb.Row = nil
|
||||
var waitStatus = mariadb_rdb.fetch_row_start(addr row, sqlres)
|
||||
while waitStatus != 0:
|
||||
let ready = await waitMariadb(conn, waitStatus, deadline)
|
||||
waitStatus = mariadb_rdb.fetch_row_cont(addr row, sqlres, ready)
|
||||
return row
|
||||
|
||||
proc runFreeResult(conn: PMySQL, sqlres: PRES, deadline: MonoTime): Future[void] {.async.} =
|
||||
if sqlres.isNil:
|
||||
return
|
||||
var waitStatus = mariadb_rdb.free_result_start(sqlres)
|
||||
while waitStatus != 0:
|
||||
let ready = await waitMariadb(conn, waitStatus, deadline)
|
||||
waitStatus = mariadb_rdb.free_result_cont(sqlres, ready)
|
||||
|
||||
proc rawExec(conn: PMySQL, query: string, args: MariadbParams, timeout: int) {.async.} =
|
||||
let q = dbFormat(conn, query, args)
|
||||
let deadline = makeDeadline(timeout)
|
||||
await runRealQuery(conn, q, deadline)
|
||||
|
||||
|
||||
proc runStmtPrepare(conn: PMySQL, stmt: PSTMT, sql: string, deadline: MonoTime): Future[void] {.async.} =
|
||||
var ret = 0.cint
|
||||
var waitStatus = stmt_prepare_start(addr ret, stmt, sql.cstring, culong(sql.len))
|
||||
while waitStatus != 0:
|
||||
let ready = await waitMariadb(conn, waitStatus, deadline)
|
||||
waitStatus = stmt_prepare_cont(addr ret, stmt, ready)
|
||||
if ret != 0:
|
||||
raise newException(DbError, $stmt_error(stmt))
|
||||
|
||||
|
||||
proc runStmtReset(conn: PMySQL, stmt: PSTMT, deadline: MonoTime): Future[void] {.async.} =
|
||||
var ret = false
|
||||
var waitStatus = stmt_reset_start(addr ret, stmt)
|
||||
while waitStatus != 0:
|
||||
let ready = await waitMariadb(conn, waitStatus, deadline)
|
||||
waitStatus = stmt_reset_cont(addr ret, stmt, ready)
|
||||
if ret:
|
||||
raise newException(DbError, $stmt_error(stmt))
|
||||
|
||||
|
||||
proc runStmtFreeResult(conn: PMySQL, stmt: PSTMT, deadline: MonoTime): Future[void] {.async.} =
|
||||
var ret = false
|
||||
var waitStatus = stmt_free_result_start(addr ret, stmt)
|
||||
while waitStatus != 0:
|
||||
let ready = await waitMariadb(conn, waitStatus, deadline)
|
||||
waitStatus = stmt_free_result_cont(addr ret, stmt, ready)
|
||||
if ret:
|
||||
raise newException(DbError, $stmt_error(stmt))
|
||||
|
||||
|
||||
proc runStmtExecute(conn: PMySQL, stmt: PSTMT, deadline: MonoTime): Future[void] {.async.} =
|
||||
var ret = 0.cint
|
||||
var waitStatus = stmt_execute_start(addr ret, stmt)
|
||||
while waitStatus != 0:
|
||||
let ready = await waitMariadb(conn, waitStatus, deadline)
|
||||
waitStatus = stmt_execute_cont(addr ret, stmt, ready)
|
||||
if ret != 0:
|
||||
raise newException(DbError, $stmt_error(stmt))
|
||||
|
||||
|
||||
proc runStmtStoreResult(conn: PMySQL, stmt: PSTMT, deadline: MonoTime): Future[void] {.async.} =
|
||||
var ret = 0.cint
|
||||
var waitStatus = stmt_store_result_start(addr ret, stmt)
|
||||
while waitStatus != 0:
|
||||
let ready = await waitMariadb(conn, waitStatus, deadline)
|
||||
waitStatus = stmt_store_result_cont(addr ret, stmt, ready)
|
||||
if ret != 0:
|
||||
raise newException(DbError, $stmt_error(stmt))
|
||||
|
||||
|
||||
proc runStmtFetch(conn: PMySQL, stmt: PSTMT, deadline: MonoTime): Future[cint] {.async.} =
|
||||
var ret = 0.cint
|
||||
var waitStatus = stmt_fetch_start(addr ret, stmt)
|
||||
while waitStatus != 0:
|
||||
let ready = await waitMariadb(conn, waitStatus, deadline)
|
||||
waitStatus = stmt_fetch_cont(addr ret, stmt, ready)
|
||||
return ret
|
||||
|
||||
|
||||
proc prepareStmt*(conn: PMySQL, sql: string, timeout: int): Future[PSTMT] {.async.} =
|
||||
assert(not conn.isNil, "Database not connected.")
|
||||
result = stmt_init(conn)
|
||||
if result.isNil:
|
||||
dbError(conn)
|
||||
let deadline = makeDeadline(timeout)
|
||||
try:
|
||||
await runStmtPrepare(conn, result, sql, deadline)
|
||||
except CatchableError:
|
||||
discard stmt_close(result)
|
||||
raise
|
||||
|
||||
|
||||
proc bindStmtParams(stmt: PSTMT, args: seq[PreparedParam]) =
|
||||
if stmt_param_count(stmt) != args.len:
|
||||
raise newException(DbError, "Prepared statement parameter count mismatch.")
|
||||
|
||||
if args.len == 0:
|
||||
return
|
||||
|
||||
var binds = newSeq[BIND](args.len)
|
||||
var values = newSeq[string](args.len)
|
||||
var lengths = newSeq[culong](args.len)
|
||||
var nullFlags = newSeq[my_bool](args.len)
|
||||
var errorFlags = newSeq[my_bool](args.len)
|
||||
|
||||
for i, arg in args:
|
||||
if arg.isNull:
|
||||
nullFlags[i] = true
|
||||
binds[i].buffer_type = TYPE_NULL
|
||||
binds[i].is_null = addr nullFlags[i]
|
||||
binds[i].error = addr errorFlags[i]
|
||||
continue
|
||||
|
||||
values[i] = arg.value
|
||||
lengths[i] = values[i].len.culong
|
||||
binds[i].buffer_type = TYPE_STRING
|
||||
if values[i].len > 0:
|
||||
binds[i].buffer = cast[pointer](values[i].cstring)
|
||||
else:
|
||||
binds[i].buffer = nil
|
||||
binds[i].buffer_length = values[i].len.culong
|
||||
binds[i].length = addr lengths[i]
|
||||
binds[i].is_null = addr nullFlags[i]
|
||||
binds[i].error = addr errorFlags[i]
|
||||
|
||||
if stmt_bind_param(stmt, binds[0].addr):
|
||||
raise newException(DbError, $stmt_error(stmt))
|
||||
|
||||
|
||||
proc bindStmtResults(
|
||||
stmt: PSTMT,
|
||||
metadata: PRES,
|
||||
resultBinds: MariadbResultBindCache
|
||||
) =
|
||||
let cols = int(num_fields(metadata))
|
||||
if resultBinds.binds.len != cols:
|
||||
resultBinds.binds = newSeq[BIND](cols)
|
||||
resultBinds.buffers = newSeq[string](cols)
|
||||
resultBinds.lengths = newSeq[culong](cols)
|
||||
resultBinds.nullFlags = newSeq[my_bool](cols)
|
||||
resultBinds.errorFlags = newSeq[my_bool](cols)
|
||||
for i in 0 ..< cols:
|
||||
let field = fetch_field_direct(metadata, cast[mariadb_rdb.cuint](i))
|
||||
var bufferLen = int(field.len)
|
||||
if bufferLen < 4096:
|
||||
bufferLen = 4096
|
||||
resultBinds.buffers[i] = newString(bufferLen)
|
||||
else:
|
||||
for i in 0 ..< cols:
|
||||
resultBinds.lengths[i] = 0
|
||||
resultBinds.nullFlags[i] = false
|
||||
resultBinds.errorFlags[i] = false
|
||||
|
||||
for i in 0 ..< cols:
|
||||
resultBinds.binds[i].buffer_type = TYPE_STRING
|
||||
resultBinds.binds[i].buffer = if resultBinds.buffers[i].len > 0: cast[pointer](resultBinds.buffers[i].cstring) else: nil
|
||||
resultBinds.binds[i].buffer_length = resultBinds.buffers[i].len.culong
|
||||
resultBinds.binds[i].length = addr resultBinds.lengths[i]
|
||||
resultBinds.binds[i].is_null = addr resultBinds.nullFlags[i]
|
||||
resultBinds.binds[i].error = addr resultBinds.errorFlags[i]
|
||||
|
||||
if cols > 0 and stmt_bind_result(stmt, resultBinds.binds[0].addr):
|
||||
raise newException(DbError, $stmt_error(stmt))
|
||||
|
||||
|
||||
proc refetchTruncatedColumns(
|
||||
stmt: PSTMT,
|
||||
resultBinds: MariadbResultBindCache
|
||||
) =
|
||||
for i in 0 ..< resultBinds.binds.len:
|
||||
if not resultBinds.errorFlags[i]:
|
||||
continue
|
||||
let needed = max(int(resultBinds.lengths[i]), resultBinds.buffers[i].len)
|
||||
if needed <= 0:
|
||||
continue
|
||||
resultBinds.buffers[i] = newString(needed)
|
||||
resultBinds.binds[i].buffer = cast[pointer](resultBinds.buffers[i].cstring)
|
||||
resultBinds.binds[i].buffer_length = needed.culong
|
||||
if stmt_fetch_column(stmt, resultBinds.binds[i].addr, cast[mariadb_rdb.cuint](i), 0) != 0:
|
||||
raise newException(DbError, $stmt_error(stmt))
|
||||
|
||||
|
||||
proc execPreparedStmt*(conn: PMySQL, stmt: PSTMT, args: seq[PreparedParam], timeout: int) {.async.} =
|
||||
assert(not conn.isNil, "Database not connected.")
|
||||
let deadline = makeDeadline(timeout)
|
||||
await runStmtReset(conn, stmt, deadline)
|
||||
await runStmtFreeResult(conn, stmt, deadline)
|
||||
bindStmtParams(stmt, args)
|
||||
await runStmtExecute(conn, stmt, deadline)
|
||||
await runStmtFreeResult(conn, stmt, deadline)
|
||||
|
||||
|
||||
proc queryPreparedStmt*(
|
||||
conn: PMySQL,
|
||||
stmt: PSTMT,
|
||||
args: seq[PreparedParam],
|
||||
timeout: int,
|
||||
resultBinds: MariadbResultBindCache
|
||||
): Future[(seq[database_types.Row], DbRows)] {.async.} =
|
||||
assert(not conn.isNil, "Database not connected.")
|
||||
let deadline = makeDeadline(timeout)
|
||||
await runStmtReset(conn, stmt, deadline)
|
||||
await runStmtFreeResult(conn, stmt, deadline)
|
||||
bindStmtParams(stmt, args)
|
||||
await runStmtExecute(conn, stmt, deadline)
|
||||
|
||||
var dbRows: DbRows
|
||||
var rows = newSeq[seq[string]]()
|
||||
let metadata = stmt_result_metadata(stmt)
|
||||
if metadata.isNil:
|
||||
await runStmtFreeResult(conn, stmt, deadline)
|
||||
return (rows, dbRows)
|
||||
|
||||
defer:
|
||||
free_result(metadata)
|
||||
await runStmtFreeResult(conn, stmt, deadline)
|
||||
|
||||
await runStmtStoreResult(conn, stmt, deadline)
|
||||
|
||||
let cols = int(num_fields(metadata))
|
||||
var baseColumns: DbColumns
|
||||
setColumnInfo(baseColumns, metadata, cols)
|
||||
bindStmtResults(stmt, metadata, resultBinds)
|
||||
|
||||
while true:
|
||||
let fetchRes = await runStmtFetch(conn, stmt, deadline)
|
||||
if fetchRes == 100:
|
||||
break
|
||||
if fetchRes notin {0, 101}:
|
||||
raise newException(DbError, $stmt_error(stmt))
|
||||
if fetchRes == 101:
|
||||
refetchTruncatedColumns(stmt, resultBinds)
|
||||
|
||||
var rowColumns = baseColumns
|
||||
var row = newSeq[string](cols)
|
||||
for i in 0 ..< cols:
|
||||
if resultBinds.nullFlags[i]:
|
||||
rowColumns[i].typ.kind = dbNull
|
||||
row[i] = ""
|
||||
else:
|
||||
let length = min(int(resultBinds.lengths[i]), resultBinds.buffers[i].len)
|
||||
if length <= 0:
|
||||
row[i] = ""
|
||||
else:
|
||||
row[i] = resultBinds.buffers[i][0 ..< length]
|
||||
rows.add(row)
|
||||
dbRows.add(rowColumns)
|
||||
|
||||
return (rows, dbRows)
|
||||
|
||||
|
||||
proc closePreparedStmt*(stmt: PSTMT): void =
|
||||
if stmt.isNil:
|
||||
return
|
||||
discard stmt_close(stmt)
|
||||
|
||||
|
||||
proc jsonObjValuesToStrSeq(args: JsonNode): seq[string] =
|
||||
result = newSeq[string](args.len)
|
||||
var i = 0
|
||||
for arg in args.items:
|
||||
case arg["value"].kind
|
||||
of JBool:
|
||||
result[i] = if arg["value"].getBool: "1" else: "0"
|
||||
of JInt:
|
||||
result[i] = $arg["value"].getInt
|
||||
of JFloat:
|
||||
result[i] = $arg["value"].getFloat
|
||||
of JArray, JObject:
|
||||
result[i] = arg["value"].pretty()
|
||||
of JNull:
|
||||
result[i] = "null"
|
||||
else:
|
||||
result[i] = arg["value"].getStr()
|
||||
inc i
|
||||
|
||||
proc jsonFlatToStrSeq(args: JsonNode): seq[string] =
|
||||
result = newSeq[string](args.len)
|
||||
var i = 0
|
||||
for arg in args.items:
|
||||
case arg.kind
|
||||
of JBool:
|
||||
result[i] = if arg.getBool: "1" else: "0"
|
||||
of JInt:
|
||||
result[i] = $arg.getInt
|
||||
of JFloat:
|
||||
result[i] = $arg.getFloat
|
||||
of JArray, JObject:
|
||||
result[i] = arg.pretty()
|
||||
of JNull:
|
||||
result[i] = "null"
|
||||
else:
|
||||
result[i] = arg.getStr()
|
||||
inc i
|
||||
|
||||
|
||||
proc query*(db: PMySQL, query: string, args: seq[string], timeout: int): Future[(seq[database_types.Row], DbRows)] {.async.} =
|
||||
var dbRows: DbRows
|
||||
var rows = newSeq[seq[string]]()
|
||||
|
||||
let q = dbFormat(query, args)
|
||||
let deadline = makeDeadline(timeout)
|
||||
await runRealQuery(db, q, deadline)
|
||||
|
||||
let sqlres = mariadb_rdb.use_result(db)
|
||||
if sqlres.isNil:
|
||||
return (rows, dbRows)
|
||||
|
||||
try:
|
||||
let cols = int(mariadb_rdb.numFields(sqlres))
|
||||
var baseColumns: DbColumns
|
||||
setColumnInfo(baseColumns, sqlres, cols)
|
||||
while true:
|
||||
let row = await runFetchRow(db, sqlres, deadline)
|
||||
if row == nil:
|
||||
break
|
||||
|
||||
var baseRow = newSeq[string](cols)
|
||||
var rowColumns = baseColumns
|
||||
for i in 0 ..< cols:
|
||||
if row[i].isNil:
|
||||
rowColumns[i].typ.kind = dbNull
|
||||
baseRow[i] = $row[i]
|
||||
rows.add(baseRow)
|
||||
dbRows.add(rowColumns)
|
||||
finally:
|
||||
await runFreeResult(db, sqlres, deadline)
|
||||
|
||||
return (rows, dbRows)
|
||||
|
||||
proc query*(db: PMySQL, query: string, args: JsonNode, timeout: int): Future[(seq[database_types.Row], DbRows)] {.async.} =
|
||||
return query(db, query, jsonObjValuesToStrSeq(args), timeout).await
|
||||
|
||||
proc queryPlain*(db: PMySQL, query: string, args: seq[string], timeout: int): Future[seq[database_types.Row]] {.async.} =
|
||||
let q = dbFormat(query, args)
|
||||
let deadline = makeDeadline(timeout)
|
||||
await runRealQuery(db, q, deadline)
|
||||
|
||||
var rows = newSeq[seq[string]]()
|
||||
let sqlres = mariadb_rdb.use_result(db)
|
||||
if sqlres.isNil:
|
||||
return rows
|
||||
|
||||
try:
|
||||
let cols = int(mariadb_rdb.numFields(sqlres))
|
||||
while true:
|
||||
let row = await runFetchRow(db, sqlres, deadline)
|
||||
if row == nil:
|
||||
break
|
||||
|
||||
var baseRow = newSeq[string](cols)
|
||||
for i in 0 ..< cols:
|
||||
baseRow[i] = $row[i]
|
||||
rows.add(baseRow)
|
||||
finally:
|
||||
await runFreeResult(db, sqlres, deadline)
|
||||
|
||||
return rows
|
||||
|
||||
proc queryPlain*(db: PMySQL, query: string, args: JsonNode, timeout: int): Future[seq[database_types.Row]] {.async.} =
|
||||
return queryPlain(db, query, jsonFlatToStrSeq(args), timeout).await
|
||||
|
||||
proc exec*(db: PMySQL, query: string, args: seq[string], timeout: int) {.async.} =
|
||||
let q = dbFormat(query, args)
|
||||
let deadline = makeDeadline(timeout)
|
||||
await runRealQuery(db, q, deadline)
|
||||
|
||||
proc exec*(db: PMySQL, query: string, args: JsonNode, timeout: int) {.async.} =
|
||||
exec(db, query, jsonFlatToStrSeq(args), timeout).await
|
||||
|
||||
proc exec*(db: PMySQL, query: string, args: JsonNode, columns: seq[seq[string]], timeout: int) {.async.} =
|
||||
let params = MariadbParams.fromObj(args, columns)
|
||||
await rawExec(db, query, params, timeout)
|
||||
|
||||
proc execGetValue*(db: PMySQL, query: string, args: JsonNode, columns: seq[seq[string]], timeout: int): Future[(seq[database_types.Row], DbRows)] {.async.} =
|
||||
var dbRows: DbRows
|
||||
var rows = newSeq[seq[string]]()
|
||||
|
||||
let params = MariadbParams.fromObj(args, columns)
|
||||
let deadline = makeDeadline(timeout)
|
||||
let q = dbFormat(db, query, params)
|
||||
await runRealQuery(db, q, deadline)
|
||||
|
||||
let sqlres = mariadb_rdb.use_result(db)
|
||||
if sqlres.isNil:
|
||||
return (rows, dbRows)
|
||||
|
||||
try:
|
||||
let cols = int(mariadb_rdb.numFields(sqlres))
|
||||
var baseColumns: DbColumns
|
||||
setColumnInfo(baseColumns, sqlres, cols)
|
||||
while true:
|
||||
let row = await runFetchRow(db, sqlres, deadline)
|
||||
if row == nil:
|
||||
break
|
||||
|
||||
var baseRow = newSeq[string](cols)
|
||||
var rowColumns = baseColumns
|
||||
for i in 0 ..< cols:
|
||||
if row[i].isNil:
|
||||
rowColumns[i].typ.kind = dbNull
|
||||
baseRow[i] = $row[i]
|
||||
rows.add(baseRow)
|
||||
dbRows.add(rowColumns)
|
||||
finally:
|
||||
await runFreeResult(db, sqlres, deadline)
|
||||
|
||||
return (rows, dbRows)
|
||||
|
||||
proc rawQuery*(db: PMySQL, query: string, args: JsonNode, timeout: int): Future[(seq[database_types.Row], DbRows)] {.async.} =
|
||||
return query(db, query, jsonFlatToStrSeq(args), timeout).await
|
||||
|
||||
proc getColumns*(db: PMySQL, query: string, args: seq[string], timeout: int): Future[seq[string]] {.async.} =
|
||||
var columns: seq[string]
|
||||
let q = dbFormat(query, args)
|
||||
let deadline = makeDeadline(timeout)
|
||||
await runRealQuery(db, q, deadline)
|
||||
|
||||
let sqlres = mariadb_rdb.use_result(db)
|
||||
if sqlres.isNil:
|
||||
return columns
|
||||
|
||||
try:
|
||||
var dbColumns: DbColumns
|
||||
let cols = int(mariadb_rdb.numFields(sqlres))
|
||||
if cols <= 0:
|
||||
return columns
|
||||
setColumnInfo(dbColumns, sqlres, cols)
|
||||
for column in dbColumns:
|
||||
columns.add(column.name)
|
||||
finally:
|
||||
await runFreeResult(db, sqlres, deadline)
|
||||
|
||||
return columns
|
||||
|
||||
proc getColumnTypes*(db: PMySQL, database, table: string, timeout: int): Future[seq[database_types.Row]] {.async.} =
|
||||
let sql = "SELECT `COLUMN_NAME`, `DATA_TYPE` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?"
|
||||
return queryPlain(db, sql, @[database, table], timeout).await
|
||||
@@ -0,0 +1,232 @@
|
||||
import std/json
|
||||
import ../../error
|
||||
import ../../models/database_types
|
||||
import ./mariadb_rdb
|
||||
|
||||
|
||||
# type InstantRow* = object ## a handle that can be used to get a row's
|
||||
# ## column text on demand
|
||||
# row*: cstringArray
|
||||
# len: int
|
||||
|
||||
proc dbError*(db: PMySQL) {.noreturn.} =
|
||||
## raises a DbError exception.
|
||||
var e: ref DbError
|
||||
new(e)
|
||||
e.msg = $db.error
|
||||
raise e
|
||||
|
||||
# proc checkError*(db: PMySQL) =
|
||||
# ## Raises a DbError exception.
|
||||
# var message = $db.error
|
||||
# if message.len > 0:
|
||||
# raise newException(DbError, $message)
|
||||
|
||||
proc setTypeName(t: var DbType; f: PFIELD) =
|
||||
t.name = $f.name
|
||||
t.maxReprLen = Natural(f.max_length)
|
||||
if (NOT_NULL_FLAG and f.flags) != 0: t.notNull = true
|
||||
case f.ftype
|
||||
of TYPE_DECIMAL:
|
||||
t.kind = dbDecimal
|
||||
of TYPE_TINY:
|
||||
t.kind = dbInt
|
||||
t.size = 1
|
||||
of TYPE_SHORT:
|
||||
t.kind = dbInt
|
||||
t.size = 2
|
||||
of TYPE_LONG:
|
||||
t.kind = dbInt
|
||||
t.size = 4
|
||||
of TYPE_FLOAT:
|
||||
t.kind = dbFloat
|
||||
t.size = 4
|
||||
of TYPE_DOUBLE:
|
||||
t.kind = dbFloat
|
||||
t.size = 8
|
||||
of TYPE_NULL:
|
||||
t.kind = dbNull
|
||||
of TYPE_TIMESTAMP:
|
||||
t.kind = dbTimestamp
|
||||
of TYPE_LONGLONG:
|
||||
t.kind = dbInt
|
||||
t.size = 8
|
||||
of TYPE_INT24:
|
||||
t.kind = dbInt
|
||||
t.size = 3
|
||||
of TYPE_DATE:
|
||||
t.kind = dbDate
|
||||
of TYPE_TIME:
|
||||
t.kind = dbTime
|
||||
of TYPE_DATETIME:
|
||||
t.kind = dbDatetime
|
||||
of TYPE_YEAR:
|
||||
t.kind = dbDate
|
||||
of TYPE_NEWDATE:
|
||||
t.kind = dbDate
|
||||
of TYPE_VARCHAR, TYPE_VAR_STRING, TYPE_STRING:
|
||||
t.kind = dbVarchar
|
||||
of TYPE_BIT:
|
||||
t.kind = dbBit
|
||||
of TYPE_NEWDECIMAL:
|
||||
t.kind = dbDecimal
|
||||
of TYPE_ENUM: t.kind = dbEnum
|
||||
of TYPE_SET: t.kind = dbSet
|
||||
of TYPE_TINY_BLOB, TYPE_MEDIUM_BLOB, TYPE_LONG_BLOB,
|
||||
TYPE_BLOB: t.kind = dbBlob
|
||||
of TYPE_GEOMETRY:
|
||||
t.kind = dbGeometry
|
||||
|
||||
proc setColumnInfo*(columns: var DbColumns; res: PRES; L: int) =
|
||||
setLen(columns, L)
|
||||
for i in 0..<L:
|
||||
let fp = fetch_field_direct(res, cint(i))
|
||||
setTypeName(columns[i].typ, fp)
|
||||
columns[i].name = $fp.name
|
||||
columns[i].tableName = $fp.table
|
||||
columns[i].primaryKey = (fp.flags and PRI_KEY_FLAG) != 0
|
||||
|
||||
# proc newRow*(L: int): database_types.Row =
|
||||
# newSeq(result, L)
|
||||
# for i in 0..L-1: result[i] = ""
|
||||
|
||||
# proc properFreeResult*(sqlres: PRES, row: cstringArray) =
|
||||
# if row != nil:
|
||||
# while fetchRow(sqlres) != nil: discard
|
||||
# freeResult(sqlres)
|
||||
|
||||
proc dbQuote(s: string): string =
|
||||
## DB quotes the string.
|
||||
if s == "null":
|
||||
return "NULL"
|
||||
result = newStringOfCap(s.len + 2)
|
||||
result.add "'"
|
||||
for c in items(s):
|
||||
# see https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html#mysql-escaping
|
||||
case c
|
||||
of '\0': result.add "\\0"
|
||||
of '\b': result.add "\\b"
|
||||
of '\t': result.add "\\t"
|
||||
of '\l': result.add "\\n"
|
||||
of '\r': result.add "\\r"
|
||||
of '\x1a': result.add "\\Z"
|
||||
of '"': result.add "\\\""
|
||||
of '\'': result.add "\\'"
|
||||
of '\\': result.add "\\\\"
|
||||
# of '_': result.add "\\_"
|
||||
else: result.add c
|
||||
add(result, '\'')
|
||||
|
||||
proc dbFormat*(formatstr: string, args: seq[string]): string =
|
||||
result = newStringOfCap(formatstr.len + args.len * 8)
|
||||
var a = 0
|
||||
var segStart = 0
|
||||
for i in 0 ..< formatstr.len:
|
||||
if formatstr[i] == '?':
|
||||
if i > segStart:
|
||||
result.add(formatstr[segStart ..< i])
|
||||
result.add(dbQuote(args[a]))
|
||||
inc(a)
|
||||
segStart = i + 1
|
||||
if segStart < formatstr.len:
|
||||
result.add(formatstr[segStart ..< formatstr.len])
|
||||
|
||||
|
||||
# proc rawExec*(db:PMySQL, query:string, args: seq[string]) =
|
||||
# var q = dbFormat(query, args)
|
||||
# if realQuery(db, q.cstring, q.len) != 0'i32: dbError(db)
|
||||
|
||||
# iterator instantRows*(db: PMySQL; dbRows: var DbRows; query: string;
|
||||
# args: seq[string]): InstantRow =
|
||||
# ## Same as fastRows but returns a handle that can be used to get column text
|
||||
# ## on demand using []. Returned handle is valid only within the iterator body.
|
||||
# rawExec(db, query, args)
|
||||
# var sqlres = mariadb_rdb.useResult(db)
|
||||
# var dbColumns: DbColumns
|
||||
# if sqlres != nil:
|
||||
# let L = int(mariadb_rdb.numFields(sqlres))
|
||||
# var row: cstringArray
|
||||
# while true:
|
||||
# setColumnInfo(dbColumns, sqlres, L)
|
||||
# dbRows.add(dbColumns)
|
||||
# for i in 0..L:
|
||||
# row = mariadb_rdb.fetchRow(sqlres)
|
||||
# if row == nil: break
|
||||
# yield InstantRow(row: row, len: L)
|
||||
# properFreeResult(sqlres, row)
|
||||
|
||||
# proc `[]`*(row: InstantRow, col: int): string {.inline.} =
|
||||
# ## Returns text for given column of the row.
|
||||
# $row.row[col]
|
||||
|
||||
# proc unsafeColumnAt*(row: InstantRow, index: int): cstring {.inline.} =
|
||||
# ## Return cstring of given column of the row
|
||||
# row.row[index]
|
||||
|
||||
# proc len*(row: InstantRow): int {.inline.} =
|
||||
# ## Returns number of columns in the row.
|
||||
# row.len
|
||||
|
||||
|
||||
type MariadbParamValue* = object
|
||||
value*: string
|
||||
isBinary*:bool
|
||||
|
||||
type MariadbParams* = seq[MariadbParamValue]
|
||||
|
||||
proc fromObj*(_:type MariadbParams, args:JsonNode, columns:seq[seq[string]]):MariadbParams =
|
||||
## args is JArray `[{"key":"id", "value": 1}, {"key": "name" "value": "alice"}]`
|
||||
result = newSeq[MariadbParamValue](args.len)
|
||||
var i = 0
|
||||
for arg in args.items:
|
||||
defer: i.inc()
|
||||
case arg["value"].kind
|
||||
of JBool:
|
||||
let value = if arg["value"].getBool: "1" else: "0"
|
||||
result[i] = MariadbParamValue(value:value, isBinary:false)
|
||||
of JInt:
|
||||
let value = $arg["value"].getInt
|
||||
result[i] = MariadbParamValue(value:value, isBinary:false)
|
||||
of JFloat:
|
||||
let value = $arg["value"].getFloat
|
||||
result[i] = MariadbParamValue(value:value, isBinary:false)
|
||||
of JArray, JObject:
|
||||
let value = arg["value"].pretty()
|
||||
result[i] = MariadbParamValue(value:value, isBinary:false)
|
||||
of JNull:
|
||||
let value = "null"
|
||||
result[i] = MariadbParamValue(value:value, isBinary:false)
|
||||
else: # JString
|
||||
let value = arg["value"].getStr
|
||||
result[i] = MariadbParamValue(value:value, isBinary:false)
|
||||
for column in columns:
|
||||
let columnName = column[0]
|
||||
let columnTyp = column[1]
|
||||
if columnName == arg["key"].getStr:
|
||||
defer:break
|
||||
if columnTyp == "blob":
|
||||
result[i].isBinary = true
|
||||
|
||||
|
||||
proc dbFormat*(conn:PMySQL, query: string, args: MariadbParams): string =
|
||||
result = newStringOfCap(query.len + args.len * 8)
|
||||
var a = 0
|
||||
var segStart = 0
|
||||
for i in 0 ..< query.len:
|
||||
if query[i] == '?':
|
||||
if i > segStart:
|
||||
result.add(query[segStart ..< i])
|
||||
if args[a].isBinary:
|
||||
let `from` = args[a].value
|
||||
var to = newString(`from`.len * 2 + 1)
|
||||
let escLen = mariadb_rdb.real_escape_string(conn, to.cstring, `from`.cstring, `from`.len)
|
||||
to.setLen(escLen)
|
||||
result.add("\'")
|
||||
result.add(to)
|
||||
result.add("\'")
|
||||
else:
|
||||
result.add(dbQuote(args[a].value))
|
||||
inc(a)
|
||||
segStart = i + 1
|
||||
if segStart < query.len:
|
||||
result.add(query[segStart ..< query.len])
|
||||
File diff suppressed because it is too large
Load Diff
+1158
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,445 @@
|
||||
import std/asyncdispatch
|
||||
import std/times
|
||||
import std/strutils
|
||||
import std/strformat
|
||||
import std/json
|
||||
import ../../error
|
||||
import ../../models/database_types
|
||||
import ../../models/mysql/mysql_types
|
||||
import ../../prepared_param
|
||||
import ./mysql_rdb
|
||||
import ./mysql_lib
|
||||
|
||||
|
||||
proc rawExec(conn:PMySQL, query: string, args: seq[string]) =
|
||||
assert conn.ping == 0
|
||||
|
||||
var stmt = mysql_rdb.stmt_init(conn)
|
||||
if stmt.isNil:
|
||||
mysql_rdb.close(conn)
|
||||
dbError("mysql_stmt_init() failed")
|
||||
|
||||
var q = dbFormat(query, args)
|
||||
if realQuery(conn, q.cstring, q.len) != 0'i32: dbError(conn)
|
||||
|
||||
|
||||
proc rawExec(conn:PMySQL, query: string, args: MysqlParams) =
|
||||
assert conn.ping == 0
|
||||
|
||||
var stmt = mysql_rdb.stmt_init(conn)
|
||||
if stmt.isNil:
|
||||
mysql_rdb.close(conn)
|
||||
dbError("mysql_stmt_init() failed")
|
||||
|
||||
var q = dbFormat(conn, query, args)
|
||||
if realQuery(conn, q.cstring, q.len) != 0'i32: dbError(conn)
|
||||
|
||||
|
||||
proc prepareStmt*(conn: PMySQL, sql: string, timeout: int): Future[PSTMT] {.async.} =
|
||||
assert(not conn.isNil, "Database not connected.")
|
||||
await sleepAsync(0)
|
||||
result = mysql_rdb.stmt_init(conn)
|
||||
if result.isNil:
|
||||
dbError(conn)
|
||||
if mysql_rdb.stmt_prepare(result, sql.cstring, sql.len) != 0:
|
||||
let errmsg = $mysql_rdb.stmt_error(result)
|
||||
discard mysql_rdb.stmt_close(result)
|
||||
raise newException(DbError, errmsg)
|
||||
|
||||
|
||||
proc bindStmtParams(stmt: PSTMT, args: seq[PreparedParam]) =
|
||||
if mysql_rdb.stmt_param_count(stmt) != args.len:
|
||||
raise newException(DbError, "Prepared statement parameter count mismatch.")
|
||||
|
||||
if args.len == 0:
|
||||
return
|
||||
|
||||
var binds = newSeq[BIND](args.len)
|
||||
var values = newSeq[string](args.len)
|
||||
var lengths = newSeq[culong](args.len)
|
||||
var nullFlags = newSeq[my_bool](args.len)
|
||||
var errorFlags = newSeq[my_bool](args.len)
|
||||
|
||||
for i, arg in args:
|
||||
if arg.isNull:
|
||||
nullFlags[i] = true
|
||||
binds[i].buffer_type = TYPE_NULL
|
||||
binds[i].is_null = addr nullFlags[i]
|
||||
binds[i].error = addr errorFlags[i]
|
||||
continue
|
||||
|
||||
values[i] = arg.value
|
||||
lengths[i] = values[i].len.culong
|
||||
binds[i].buffer_type = TYPE_STRING
|
||||
if values[i].len > 0:
|
||||
binds[i].buffer = cast[pointer](values[i].cstring)
|
||||
else:
|
||||
binds[i].buffer = nil
|
||||
binds[i].buffer_length = values[i].len.culong
|
||||
binds[i].length = addr lengths[i]
|
||||
binds[i].is_null = addr nullFlags[i]
|
||||
binds[i].error = addr errorFlags[i]
|
||||
|
||||
if mysql_rdb.stmt_bind_param(stmt, binds[0].addr):
|
||||
raise newException(DbError, $mysql_rdb.stmt_error(stmt))
|
||||
|
||||
|
||||
proc bindStmtResults(
|
||||
stmt: PSTMT,
|
||||
metadata: PRES,
|
||||
resultBinds: MysqlResultBindCache
|
||||
) =
|
||||
let cols = int(mysql_rdb.num_fields(metadata))
|
||||
if resultBinds.binds.len != cols:
|
||||
resultBinds.binds = newSeq[BIND](cols)
|
||||
resultBinds.buffers = newSeq[string](cols)
|
||||
resultBinds.lengths = newSeq[culong](cols)
|
||||
resultBinds.nullFlags = newSeq[my_bool](cols)
|
||||
resultBinds.errorFlags = newSeq[my_bool](cols)
|
||||
for i in 0 ..< cols:
|
||||
let field = mysql_rdb.fetch_field_direct(metadata, cast[mysql_rdb.cuint](i))
|
||||
var bufferLen = int(field.len)
|
||||
if bufferLen < 4096:
|
||||
bufferLen = 4096
|
||||
resultBinds.buffers[i] = newString(bufferLen)
|
||||
else:
|
||||
for i in 0 ..< cols:
|
||||
resultBinds.lengths[i] = 0
|
||||
resultBinds.nullFlags[i] = false
|
||||
resultBinds.errorFlags[i] = false
|
||||
|
||||
for i in 0 ..< cols:
|
||||
resultBinds.binds[i].buffer_type = TYPE_STRING
|
||||
resultBinds.binds[i].buffer = if resultBinds.buffers[i].len > 0: cast[pointer](resultBinds.buffers[i].cstring) else: nil
|
||||
resultBinds.binds[i].buffer_length = resultBinds.buffers[i].len.culong
|
||||
resultBinds.binds[i].length = addr resultBinds.lengths[i]
|
||||
resultBinds.binds[i].is_null = addr resultBinds.nullFlags[i]
|
||||
resultBinds.binds[i].error = addr resultBinds.errorFlags[i]
|
||||
|
||||
if cols > 0 and mysql_rdb.stmt_bind_result(stmt, resultBinds.binds[0].addr):
|
||||
raise newException(DbError, $mysql_rdb.stmt_error(stmt))
|
||||
|
||||
|
||||
proc refetchTruncatedColumns(
|
||||
stmt: PSTMT,
|
||||
resultBinds: MysqlResultBindCache
|
||||
) =
|
||||
for i in 0 ..< resultBinds.binds.len:
|
||||
if not resultBinds.errorFlags[i]:
|
||||
continue
|
||||
let needed = max(int(resultBinds.lengths[i]), resultBinds.buffers[i].len)
|
||||
if needed <= 0:
|
||||
continue
|
||||
resultBinds.buffers[i] = newString(needed)
|
||||
resultBinds.binds[i].buffer = cast[pointer](resultBinds.buffers[i].cstring)
|
||||
resultBinds.binds[i].buffer_length = needed.culong
|
||||
if mysql_rdb.stmt_fetch_column(stmt, resultBinds.binds[i].addr, cast[mysql_rdb.cuint](i), 0) != 0:
|
||||
raise newException(DbError, $mysql_rdb.stmt_error(stmt))
|
||||
|
||||
|
||||
proc execPreparedStmt*(conn: PMySQL, stmt: PSTMT, args: seq[PreparedParam], timeout: int) {.async.} =
|
||||
assert(not conn.isNil, "Database not connected.")
|
||||
await sleepAsync(0)
|
||||
if mysql_rdb.stmt_reset(stmt):
|
||||
raise newException(DbError, $mysql_rdb.stmt_error(stmt))
|
||||
if mysql_rdb.stmt_free_result(stmt):
|
||||
raise newException(DbError, $mysql_rdb.stmt_error(stmt))
|
||||
bindStmtParams(stmt, args)
|
||||
if mysql_rdb.stmt_execute(stmt) != 0:
|
||||
raise newException(DbError, $mysql_rdb.stmt_error(stmt))
|
||||
if mysql_rdb.stmt_free_result(stmt):
|
||||
raise newException(DbError, $mysql_rdb.stmt_error(stmt))
|
||||
|
||||
|
||||
proc queryPreparedStmt*(
|
||||
conn: PMySQL,
|
||||
stmt: PSTMT,
|
||||
args: seq[PreparedParam],
|
||||
timeout: int,
|
||||
resultBinds: MysqlResultBindCache
|
||||
): Future[(seq[database_types.Row], DbRows)] {.async.} =
|
||||
assert(not conn.isNil, "Database not connected.")
|
||||
await sleepAsync(0)
|
||||
if mysql_rdb.stmt_reset(stmt):
|
||||
raise newException(DbError, $mysql_rdb.stmt_error(stmt))
|
||||
if mysql_rdb.stmt_free_result(stmt):
|
||||
raise newException(DbError, $mysql_rdb.stmt_error(stmt))
|
||||
bindStmtParams(stmt, args)
|
||||
if mysql_rdb.stmt_execute(stmt) != 0:
|
||||
raise newException(DbError, $mysql_rdb.stmt_error(stmt))
|
||||
|
||||
var dbRows: DbRows
|
||||
var rows = newSeq[seq[string]]()
|
||||
let metadata = mysql_rdb.stmt_result_metadata(stmt)
|
||||
if metadata.isNil:
|
||||
if mysql_rdb.stmt_free_result(stmt):
|
||||
raise newException(DbError, $mysql_rdb.stmt_error(stmt))
|
||||
return (rows, dbRows)
|
||||
|
||||
defer:
|
||||
mysql_rdb.free_result(metadata)
|
||||
if mysql_rdb.stmt_free_result(stmt):
|
||||
raise newException(DbError, $mysql_rdb.stmt_error(stmt))
|
||||
|
||||
if mysql_rdb.stmt_store_result(stmt) != 0:
|
||||
raise newException(DbError, $mysql_rdb.stmt_error(stmt))
|
||||
|
||||
let cols = int(mysql_rdb.num_fields(metadata))
|
||||
var baseColumns: DbColumns
|
||||
setColumnInfo(baseColumns, metadata, cols)
|
||||
bindStmtResults(stmt, metadata, resultBinds)
|
||||
|
||||
while true:
|
||||
let fetchRes = mysql_rdb.stmt_fetch(stmt)
|
||||
if fetchRes == 100:
|
||||
break
|
||||
if fetchRes notin {0, 101}:
|
||||
raise newException(DbError, $mysql_rdb.stmt_error(stmt))
|
||||
if fetchRes == 101:
|
||||
refetchTruncatedColumns(stmt, resultBinds)
|
||||
|
||||
var rowColumns = baseColumns
|
||||
var row = newSeq[string](cols)
|
||||
for i in 0 ..< cols:
|
||||
if resultBinds.nullFlags[i]:
|
||||
rowColumns[i].typ.kind = dbNull
|
||||
row[i] = ""
|
||||
else:
|
||||
let length = min(int(resultBinds.lengths[i]), resultBinds.buffers[i].len)
|
||||
if length <= 0:
|
||||
row[i] = ""
|
||||
else:
|
||||
row[i] = resultBinds.buffers[i][0 ..< length]
|
||||
rows.add(row)
|
||||
dbRows.add(rowColumns)
|
||||
|
||||
return (rows, dbRows)
|
||||
|
||||
|
||||
proc closePreparedStmt*(stmt: PSTMT) =
|
||||
if stmt.isNil:
|
||||
return
|
||||
discard mysql_rdb.stmt_close(stmt)
|
||||
|
||||
|
||||
proc query*(db:PMySQL, query: string, args: seq[string], timeout:int):Future[(seq[database_types.Row], DbRows)] {.async.} =
|
||||
assert db.ping == 0
|
||||
var dbRows: DbRows
|
||||
var rows = newSeq[seq[string]]()
|
||||
var lines = 0
|
||||
|
||||
rawExec(db, query, args)
|
||||
|
||||
var sqlres = mysql_rdb.useResult(db)
|
||||
let calledAt = getTime().toUnix()
|
||||
var dbColumns: DbColumns
|
||||
let cols = int(mysql_rdb.numFields(sqlres))
|
||||
|
||||
while true:
|
||||
if getTime().toUnix() >= calledAt + timeout:
|
||||
return
|
||||
await sleepAsync(0)
|
||||
var row: mysql_rdb.Row
|
||||
var baseRow = newSeq[string](cols)
|
||||
setColumnInfo(dbColumns, sqlres, cols)
|
||||
row = mysql_rdb.fetchRow(sqlres)
|
||||
if row == nil: break
|
||||
for i in 0..<cols:
|
||||
if row[i].isNil:
|
||||
dbColumns[i].typ.kind = dbNull
|
||||
baseRow[i] = $row[i]
|
||||
rows.add(baseRow)
|
||||
dbRows.add(dbColumns)
|
||||
lines.inc()
|
||||
|
||||
free_result(sqlres)
|
||||
return (rows, dbRows)
|
||||
|
||||
|
||||
proc query*(db:PMySQL, query: string, args: JsonNode, timeout:int):Future[(seq[database_types.Row], DbRows)] {.async.} =
|
||||
var strArgs = newSeq[string](args.len)
|
||||
var i = 0
|
||||
for arg in args.items:
|
||||
defer: i.inc()
|
||||
case arg["value"].kind
|
||||
of JBool:
|
||||
strArgs[i] = if arg["value"].getBool: "1" else: "0"
|
||||
of JInt:
|
||||
strArgs[i] = $arg["value"].getInt
|
||||
of JFloat:
|
||||
strArgs[i] = $arg["value"].getFloat
|
||||
of JArray, JObject:
|
||||
strArgs[i] = arg["value"].pretty()
|
||||
of JNull:
|
||||
strArgs[i] = "null"
|
||||
else: # JString
|
||||
strArgs[i] = arg["value"].getStr()
|
||||
|
||||
return query(db, query, strArgs, timeout).await
|
||||
|
||||
|
||||
proc queryPlain*(db:PMySQL, query: string, args: seq[string], timeout:int):Future[seq[database_types.Row]] {.async.} =
|
||||
assert db.ping == 0
|
||||
rawExec(db, query, args)
|
||||
var rows = newSeq[seq[string]]()
|
||||
var sqlres = mysql_rdb.useResult(db)
|
||||
let calledAt = getTime().toUnix()
|
||||
let cols = int(mysql_rdb.numFields(sqlres))
|
||||
while true:
|
||||
if getTime().toUnix() >= calledAt + timeout:
|
||||
return
|
||||
await sleepAsync(0)
|
||||
var row: mysql_rdb.Row
|
||||
var baseRow = newSeq[string](cols)
|
||||
row = mysql_rdb.fetchRow(sqlres)
|
||||
if row == nil: break
|
||||
for i in 0..<cols:
|
||||
baseRow[i] = $row[i]
|
||||
rows.add(baseRow)
|
||||
free_result(sqlres)
|
||||
return rows
|
||||
|
||||
|
||||
proc queryPlain*(db:PMySQL, query: string, args: JsonNode, timeout:int):Future[seq[database_types.Row]] {.async.} =
|
||||
var strArgs = newSeq[string](args.len)
|
||||
var i = 0
|
||||
for arg in args.items:
|
||||
defer: i.inc()
|
||||
case arg.kind
|
||||
of JBool:
|
||||
strArgs[i] = if arg.getBool: "1" else: "0"
|
||||
of JInt:
|
||||
strArgs[i] = $arg.getInt
|
||||
of JFloat:
|
||||
strArgs[i] = $arg.getFloat
|
||||
of JArray, JObject:
|
||||
strArgs[i] = arg.pretty()
|
||||
of JNull:
|
||||
strArgs[i] = "null"
|
||||
else: # JString
|
||||
strArgs[i] = arg.getStr()
|
||||
|
||||
return queryPlain(db, query, strArgs, timeout).await
|
||||
|
||||
|
||||
proc exec*(db:PMySQL, query: string, args: seq[string], timeout:int) {.async.} =
|
||||
var q = dbFormat(query, args)
|
||||
await sleepAsync(0)
|
||||
if realQuery(db, q.cstring, q.len) != 0'i32: dbError(db)
|
||||
|
||||
|
||||
proc exec*(db:PMySQL, query: string, args: JsonNode, timeout:int) {.async.} =
|
||||
var strArgs = newSeq[string](args.len)
|
||||
var i = 0
|
||||
for arg in args.items:
|
||||
defer: i.inc()
|
||||
case arg.kind
|
||||
of JBool:
|
||||
strArgs[i] = if arg.getBool: "1" else: "0"
|
||||
of JInt:
|
||||
strArgs[i] = $arg.getInt
|
||||
of JFloat:
|
||||
strArgs[i] = $arg.getFloat
|
||||
of JArray, JObject:
|
||||
strArgs[i] = arg.pretty()
|
||||
of JNull:
|
||||
strArgs[i] = "null"
|
||||
else: # JString
|
||||
strArgs[i] = arg.getStr()
|
||||
|
||||
exec(db, query, strArgs, timeout).await
|
||||
|
||||
|
||||
proc exec*(db:PMySQL, query: string, args: JsonNode, columns:seq[seq[string]], timeout:int) {.async.} =
|
||||
## args is JArray `[{"key":"id", "value": 1}, {"key": "name" "value": "alice"}]`
|
||||
assert db.ping == 0
|
||||
|
||||
let params = MysqlParams.fromObj(args, columns)
|
||||
rawExec(db, query, params)
|
||||
|
||||
|
||||
proc execGetValue*(db:PMySQL, query: string, args: JsonNode, columns:seq[seq[string]], timeout:int):Future[(seq[database_types.Row], DbRows)] {.async.} =
|
||||
assert db.ping == 0
|
||||
var dbRows: DbRows
|
||||
var rows = newSeq[seq[string]]()
|
||||
var lines = 0
|
||||
|
||||
let params = MysqlParams.fromObj(args, columns)
|
||||
rawExec(db, query, params)
|
||||
|
||||
var sqlres = mysql_rdb.useResult(db)
|
||||
let calledAt = getTime().toUnix()
|
||||
var dbColumns: DbColumns
|
||||
let cols = int(mysql_rdb.numFields(sqlres))
|
||||
|
||||
while true:
|
||||
if getTime().toUnix() >= calledAt + timeout:
|
||||
return
|
||||
await sleepAsync(0)
|
||||
var row: mysql_rdb.Row
|
||||
var baseRow = newSeq[string](cols)
|
||||
setColumnInfo(dbColumns, sqlres, cols)
|
||||
row = mysql_rdb.fetchRow(sqlres)
|
||||
if row == nil: break
|
||||
for i in 0..<cols:
|
||||
if row[i].isNil:
|
||||
dbColumns[i].typ.kind = dbNull
|
||||
baseRow[i] = $row[i]
|
||||
rows.add(baseRow)
|
||||
dbRows.add(dbColumns)
|
||||
lines.inc()
|
||||
|
||||
free_result(sqlres)
|
||||
return (rows, dbRows)
|
||||
|
||||
|
||||
proc rawQuery*(db:PMySQL, query: string, args: JsonNode, timeout:int):Future[(seq[database_types.Row], DbRows)] {.async.} =
|
||||
var strArgs = newSeq[string](args.len)
|
||||
var i = 0
|
||||
for arg in args.items:
|
||||
defer: i.inc()
|
||||
case arg.kind
|
||||
of JBool:
|
||||
strArgs[i] = if arg.getBool: "1" else: "0"
|
||||
of JInt:
|
||||
strArgs[i] = $arg.getInt
|
||||
of JFloat:
|
||||
strArgs[i] = $arg.getFloat
|
||||
of JArray, JObject:
|
||||
strArgs[i] = arg.pretty()
|
||||
of JNull:
|
||||
strArgs[i] = "null"
|
||||
else: # JString
|
||||
strArgs[i] = arg.getStr()
|
||||
|
||||
return query(db, query, strArgs, timeout).await
|
||||
|
||||
|
||||
proc getColumns*(db:PMySQL, query: string, args: seq[string], timeout:int):Future[seq[string]] {.async.} =
|
||||
assert db.ping == 0
|
||||
var columns:seq[string]
|
||||
|
||||
rawExec(db, query, args)
|
||||
var sqlres = mysql_rdb.useResult(db)
|
||||
let calledAt = getTime().toUnix()
|
||||
var dbColumns: DbColumns
|
||||
let cols = int(mysql_rdb.numFields(sqlres))
|
||||
while true:
|
||||
if getTime().toUnix() >= calledAt + timeout:
|
||||
return
|
||||
await sleepAsync(0)
|
||||
var row: mysql_rdb.Row
|
||||
setColumnInfo(dbColumns, sqlres, cols)
|
||||
for column in dbColumns:
|
||||
columns.add(column.name)
|
||||
row = mysql_rdb.fetchRow(sqlres)
|
||||
break
|
||||
free_result(sqlres)
|
||||
return columns
|
||||
|
||||
|
||||
proc getColumnTypes*(db:PMySQL, database, table:string, timeout:int):Future[seq[database_types.Row]] {.async.} =
|
||||
assert db.ping == 0
|
||||
|
||||
let sql = &"SELECT `COLUMN_NAME`, `DATA_TYPE` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE TABLE_SCHEMA = '{database}' AND TABLE_NAME = '{table}'"
|
||||
return queryPlain(db, sql, @[], timeout).await
|
||||
@@ -0,0 +1,224 @@
|
||||
import std/json
|
||||
import ../../error
|
||||
import ../../models/database_types
|
||||
import ./mysql_rdb
|
||||
|
||||
|
||||
# type InstantRow* = object ## a handle that can be used to get a row's
|
||||
# ## column text on demand
|
||||
# row*: cstringArray
|
||||
# len: int
|
||||
|
||||
proc dbError*(db: PMySQL) {.noreturn.} =
|
||||
## raises a DbError exception.
|
||||
var e: ref DbError
|
||||
new(e)
|
||||
e.msg = $db.error
|
||||
raise e
|
||||
|
||||
# proc checkError*(db: PMySQL) =
|
||||
# ## Raises a DbError exception.
|
||||
# var message = $db.error
|
||||
# if message.len > 0:
|
||||
# raise newException(DbError, $message)
|
||||
|
||||
proc setTypeName(t: var DbType; f: PFIELD) =
|
||||
t.name = $f.name
|
||||
t.maxReprLen = Natural(f.max_length)
|
||||
if (NOT_NULL_FLAG and f.flags) != 0: t.notNull = true
|
||||
case f.ftype
|
||||
of TYPE_DECIMAL:
|
||||
t.kind = dbDecimal
|
||||
of TYPE_TINY:
|
||||
t.kind = dbInt
|
||||
t.size = 1
|
||||
of TYPE_SHORT:
|
||||
t.kind = dbInt
|
||||
t.size = 2
|
||||
of TYPE_LONG:
|
||||
t.kind = dbInt
|
||||
t.size = 4
|
||||
of TYPE_FLOAT:
|
||||
t.kind = dbFloat
|
||||
t.size = 4
|
||||
of TYPE_DOUBLE:
|
||||
t.kind = dbFloat
|
||||
t.size = 8
|
||||
of TYPE_NULL:
|
||||
t.kind = dbNull
|
||||
of TYPE_TIMESTAMP:
|
||||
t.kind = dbTimestamp
|
||||
of TYPE_LONGLONG:
|
||||
t.kind = dbInt
|
||||
t.size = 8
|
||||
of TYPE_INT24:
|
||||
t.kind = dbInt
|
||||
t.size = 3
|
||||
of TYPE_DATE:
|
||||
t.kind = dbDate
|
||||
of TYPE_TIME:
|
||||
t.kind = dbTime
|
||||
of TYPE_DATETIME:
|
||||
t.kind = dbDatetime
|
||||
of TYPE_YEAR:
|
||||
t.kind = dbDate
|
||||
of TYPE_NEWDATE:
|
||||
t.kind = dbDate
|
||||
of TYPE_VARCHAR, TYPE_VAR_STRING, TYPE_STRING:
|
||||
t.kind = dbVarchar
|
||||
of TYPE_BIT:
|
||||
t.kind = dbBit
|
||||
of TYPE_NEWDECIMAL:
|
||||
t.kind = dbDecimal
|
||||
of TYPE_ENUM: t.kind = dbEnum
|
||||
of TYPE_SET: t.kind = dbSet
|
||||
of TYPE_TINY_BLOB, TYPE_MEDIUM_BLOB, TYPE_LONG_BLOB,
|
||||
TYPE_BLOB: t.kind = dbBlob
|
||||
of TYPE_GEOMETRY:
|
||||
t.kind = dbGeometry
|
||||
|
||||
proc setColumnInfo*(columns: var DbColumns; res: PRES; L: int) =
|
||||
setLen(columns, L)
|
||||
for i in 0..<L:
|
||||
let fp = fetch_field_direct(res, cint(i))
|
||||
setTypeName(columns[i].typ, fp)
|
||||
columns[i].name = $fp.name
|
||||
columns[i].tableName = $fp.table
|
||||
columns[i].primaryKey = (fp.flags and PRI_KEY_FLAG) != 0
|
||||
|
||||
# proc newRow*(L: int): database_types.Row =
|
||||
# newSeq(result, L)
|
||||
# for i in 0..L-1: result[i] = ""
|
||||
|
||||
# proc properFreeResult*(sqlres: PRES, row: cstringArray) =
|
||||
# if row != nil:
|
||||
# while fetchRow(sqlres) != nil: discard
|
||||
# freeResult(sqlres)
|
||||
|
||||
proc dbQuote(s: string): string =
|
||||
## DB quotes the string.
|
||||
if s == "null":
|
||||
return "NULL"
|
||||
result = newStringOfCap(s.len + 2)
|
||||
result.add "'"
|
||||
for c in items(s):
|
||||
# see https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html#mysql-escaping
|
||||
case c
|
||||
of '\0': result.add "\\0"
|
||||
of '\b': result.add "\\b"
|
||||
of '\t': result.add "\\t"
|
||||
of '\l': result.add "\\n"
|
||||
of '\r': result.add "\\r"
|
||||
of '\x1a': result.add "\\Z"
|
||||
of '"': result.add "\\\""
|
||||
of '\'': result.add "\\'"
|
||||
of '\\': result.add "\\\\"
|
||||
# of '_': result.add "\\_"
|
||||
else: result.add c
|
||||
add(result, '\'')
|
||||
|
||||
proc dbFormat*(formatstr: string, args: seq[string]): string =
|
||||
result = ""
|
||||
var a = 0
|
||||
for c in items(formatstr):
|
||||
if c == '?':
|
||||
add(result, dbQuote(args[a]))
|
||||
inc(a)
|
||||
else:
|
||||
add(result, c)
|
||||
|
||||
|
||||
# proc rawExec*(db:PMySQL, query:string, args: seq[string]) =
|
||||
# var q = dbFormat(query, args)
|
||||
# if realQuery(db, q.cstring, q.len) != 0'i32: dbError(db)
|
||||
|
||||
# iterator instantRows*(db: PMySQL; dbRows: var DbRows; query: string;
|
||||
# args: seq[string]): InstantRow =
|
||||
# ## Same as fastRows but returns a handle that can be used to get column text
|
||||
# ## on demand using []. Returned handle is valid only within the iterator body.
|
||||
# rawExec(db, query, args)
|
||||
# var sqlres = mysql_rdb.useResult(db)
|
||||
# var dbColumns: DbColumns
|
||||
# if sqlres != nil:
|
||||
# let L = int(mysql_rdb.numFields(sqlres))
|
||||
# var row: cstringArray
|
||||
# while true:
|
||||
# setColumnInfo(dbColumns, sqlres, L)
|
||||
# dbRows.add(dbColumns)
|
||||
# for i in 0..L:
|
||||
# row = mysql_rdb.fetchRow(sqlres)
|
||||
# if row == nil: break
|
||||
# yield InstantRow(row: row, len: L)
|
||||
# properFreeResult(sqlres, row)
|
||||
|
||||
# proc `[]`*(row: InstantRow, col: int): string {.inline.} =
|
||||
# ## Returns text for given column of the row.
|
||||
# $row.row[col]
|
||||
|
||||
# proc unsafeColumnAt*(row: InstantRow, index: int): cstring {.inline.} =
|
||||
# ## Return cstring of given column of the row
|
||||
# row.row[index]
|
||||
|
||||
# proc len*(row: InstantRow): int {.inline.} =
|
||||
# ## Returns number of columns in the row.
|
||||
# row.len
|
||||
|
||||
|
||||
type MysqlParamValue* = object
|
||||
value*: string
|
||||
isBinary*:bool
|
||||
|
||||
type MysqlParams* = seq[MysqlParamValue]
|
||||
|
||||
proc fromObj*(_:type MysqlParams, args:JsonNode, columns:seq[seq[string]]):MysqlParams =
|
||||
## args is JArray `[{"key":"id", "value": 1}, {"key": "name" "value": "alice"}]`
|
||||
result = newSeq[MysqlParamValue](args.len)
|
||||
var i = 0
|
||||
for arg in args.items:
|
||||
defer: i.inc()
|
||||
case arg["value"].kind
|
||||
of JBool:
|
||||
let value = if arg["value"].getBool: "1" else: "0"
|
||||
result[i] = MysqlParamValue(value:value, isBinary:false)
|
||||
of JInt:
|
||||
let value = $arg["value"].getInt
|
||||
result[i] = MysqlParamValue(value:value, isBinary:false)
|
||||
of JFloat:
|
||||
let value = $arg["value"].getFloat
|
||||
result[i] = MysqlParamValue(value:value, isBinary:false)
|
||||
of JArray, JObject:
|
||||
let value = arg["value"].pretty()
|
||||
result[i] = MysqlParamValue(value:value, isBinary:false)
|
||||
of JNull:
|
||||
let value = "null"
|
||||
result[i] = MysqlParamValue(value:value, isBinary:false)
|
||||
else: # JString
|
||||
let value = arg["value"].getStr
|
||||
result[i] = MysqlParamValue(value:value, isBinary:false)
|
||||
for column in columns:
|
||||
let columnName = column[0]
|
||||
let columnTyp = column[1]
|
||||
if columnName == arg["key"].getStr:
|
||||
defer:break
|
||||
if columnTyp == "blob":
|
||||
result[i].isBinary = true
|
||||
|
||||
|
||||
proc dbFormat*(conn:PMySQL, query: string, args: MysqlParams): string =
|
||||
result = ""
|
||||
var i = 0
|
||||
for c in query:
|
||||
if c == '?':
|
||||
defer: i.inc()
|
||||
if args[i].isBinary:
|
||||
let `from` = args[i].value
|
||||
var to = newString(`from`.len * 2 + 1)
|
||||
let len = mysql_rdb.real_escape_string(conn, to.cstring, `from`.cstring, `from`.len)
|
||||
to.setLen(len)
|
||||
result.add("\'")
|
||||
result.add(to)
|
||||
result.add("\'")
|
||||
else:
|
||||
result.add(dbQuote(args[i].value))
|
||||
else:
|
||||
result.add(c)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+431
@@ -0,0 +1,431 @@
|
||||
## https://www.postgresql.jp/document/12/html/libpq-async.html
|
||||
|
||||
import std/asyncdispatch
|
||||
import std/json
|
||||
import std/monotimes
|
||||
import std/strutils
|
||||
import std/times
|
||||
import ../../error
|
||||
import ../../models/database_types
|
||||
import ../../prepared_param
|
||||
import ./postgres_rdb
|
||||
import ./postgres_lib
|
||||
|
||||
|
||||
type
|
||||
PgWaitState = object
|
||||
cancelled: bool
|
||||
|
||||
proc cancelQuery(db: PPGconn) {.raises: [DbError].} =
|
||||
let cancel = pqGetCancel(db)
|
||||
if cancel == nil:
|
||||
raise newException(DbError, "PQgetCancel failed")
|
||||
defer:
|
||||
pqFreeCancel(cancel)
|
||||
var errBuf = newStringOfCap(ERROR_MSG_LENGTH)
|
||||
errBuf.setLen(ERROR_MSG_LENGTH)
|
||||
if pqCancel(cancel, errBuf.cstring, int32(errBuf.len)) == 0:
|
||||
raise newException(DbError, "PQcancel failed: " & $errBuf.cstring)
|
||||
|
||||
proc ensurePgSocketRegistered(db: PPGconn): AsyncFD =
|
||||
let sock = pqsocket(db)
|
||||
if sock < 0:
|
||||
dbError(db)
|
||||
result = AsyncFD(cint(sock))
|
||||
let disp = getGlobalDispatcher()
|
||||
if not disp.contains(result):
|
||||
register(result)
|
||||
|
||||
proc waitPgIo(db: PPGconn, timeoutMs: int; forRead: bool): Future[bool] {.async.} =
|
||||
if timeoutMs <= 0:
|
||||
return false
|
||||
let fd = ensurePgSocketRegistered(db)
|
||||
var state = PgWaitState(cancelled: false)
|
||||
var ioFut = newFuture[void]("waitPgIo")
|
||||
if forRead:
|
||||
proc readCb(f: AsyncFD): bool =
|
||||
if state.cancelled:
|
||||
return true
|
||||
if not ioFut.finished:
|
||||
ioFut.complete()
|
||||
return true
|
||||
addRead(fd, readCb)
|
||||
else:
|
||||
proc writeCb(f: AsyncFD): bool =
|
||||
if state.cancelled:
|
||||
return true
|
||||
if not ioFut.finished:
|
||||
ioFut.complete()
|
||||
return true
|
||||
addWrite(fd, writeCb)
|
||||
let ok = await withTimeout(ioFut, timeoutMs)
|
||||
if not ok:
|
||||
state.cancelled = true
|
||||
unregister(fd)
|
||||
return ok
|
||||
|
||||
proc makePgDeadline(timeout: int): MonoTime =
|
||||
let sec = if timeout > 0: timeout else: 0
|
||||
getMonoTime() + initDuration(seconds = sec)
|
||||
|
||||
proc pgRemainingMs(deadline: MonoTime): int =
|
||||
let left = (deadline - getMonoTime()).inMilliseconds
|
||||
if left <= 0:
|
||||
return 0
|
||||
if left > int64(high(int)):
|
||||
return high(int)
|
||||
result = int(left)
|
||||
if result < 1:
|
||||
result = 1
|
||||
|
||||
proc pgSendQueryParams(db: PPGconn, query: string, pgParams: PGParams) {.raises: [DbError].} =
|
||||
let status =
|
||||
if pgParams.nParams > 0:
|
||||
pqsendQueryParams(db, query.cstring, pgParams.nParams, nil, pgParams.values, pgParams.lengths[0].unsafeAddr, pgParams.formats[0].unsafeAddr, 0)
|
||||
else:
|
||||
pqsendQueryParams(db, query.cstring, pgParams.nParams, nil, nil, nil, nil, 0)
|
||||
if status != 1:
|
||||
dbError(db)
|
||||
|
||||
proc pgFlushOutgoing(db: PPGconn, deadline: MonoTime): Future[void] {.async.} =
|
||||
while true:
|
||||
let flushRes = pqflush(db)
|
||||
if flushRes == 0:
|
||||
return
|
||||
if flushRes < 0:
|
||||
dbError(db)
|
||||
let ms = pgRemainingMs(deadline)
|
||||
if ms <= 0:
|
||||
cancelQuery(db)
|
||||
raise newException(DbError, "PostgreSQL query timeout")
|
||||
if not await waitPgIo(db, ms, false):
|
||||
cancelQuery(db)
|
||||
raise newException(DbError, "PostgreSQL query timeout")
|
||||
|
||||
proc pgAwaitReadyForGetResult(db: PPGconn, deadline: MonoTime): Future[void] {.async.} =
|
||||
while true:
|
||||
if pqconsumeInput(db) != 1:
|
||||
dbError(db)
|
||||
if pqisBusy(db) != 1:
|
||||
return
|
||||
let ms = pgRemainingMs(deadline)
|
||||
if ms <= 0:
|
||||
cancelQuery(db)
|
||||
raise newException(DbError, "PostgreSQL query timeout")
|
||||
if not await waitPgIo(db, ms, true):
|
||||
cancelQuery(db)
|
||||
raise newException(DbError, "PostgreSQL query timeout")
|
||||
|
||||
proc pgNextResult(db: PPGconn, deadline: MonoTime): Future[PPGresult] {.async.} =
|
||||
await pgAwaitReadyForGetResult(db, deadline)
|
||||
result = pqgetResult(db)
|
||||
|
||||
proc pgEnsureIdle(db: PPGconn, deadline: MonoTime): Future[void] {.async.} =
|
||||
while true:
|
||||
await pgAwaitReadyForGetResult(db, deadline)
|
||||
let r = pqgetResult(db)
|
||||
if r == nil:
|
||||
db.checkError()
|
||||
return
|
||||
pqclear(r)
|
||||
|
||||
|
||||
proc query*(db: PPGconn, query: string, args: JsonNode, timeout: int): Future[(seq[Row], DbRows)] {.async.} =
|
||||
assert db.status == CONNECTION_OK
|
||||
let pgParams = PGParams.fromObjArray(args)
|
||||
defer:
|
||||
if pgParams.nParams > 0: pgParams.values.deallocCStringArray()
|
||||
pgSendQueryParams(db, query, pgParams)
|
||||
var dbRows: DbRows
|
||||
var rows = newSeq[Row]()
|
||||
let deadline = makePgDeadline(timeout)
|
||||
await pgFlushOutgoing(db, deadline)
|
||||
while true:
|
||||
let pqresult = await pgNextResult(db, deadline)
|
||||
if pqresult == nil:
|
||||
db.checkError()
|
||||
break
|
||||
|
||||
let cols = pqnfields(pqresult)
|
||||
var row = newRow(cols)
|
||||
let base = buildBaseDbColumns(pqresult, cols)
|
||||
for i in 0'i32 .. pqNtuples(pqresult) - 1:
|
||||
setRow(pqresult, row, i, cols)
|
||||
appendDbRowWithBaseColumns(pqresult, dbRows, i, cols, base)
|
||||
rows.add(row)
|
||||
pqclear(pqresult)
|
||||
|
||||
return (rows, dbRows)
|
||||
|
||||
|
||||
proc exec*(db: PPGconn, query: string, args: JsonNode, columns: seq[Row], timeout: int) {.async.} =
|
||||
assert db.status == CONNECTION_OK
|
||||
let pgParams = PGParams.fromObjArray(args, columns)
|
||||
defer:
|
||||
if pgParams.nParams > 0: pgParams.values.deallocCStringArray()
|
||||
pgSendQueryParams(db, query, pgParams)
|
||||
let deadline = makePgDeadline(timeout)
|
||||
await pgFlushOutgoing(db, deadline)
|
||||
while true:
|
||||
let pqresult = await pgNextResult(db, deadline)
|
||||
if pqresult == nil:
|
||||
db.checkError()
|
||||
break
|
||||
pqclear(pqresult)
|
||||
|
||||
|
||||
proc execGetValue*(db: PPGconn, query: string, args: JsonNode, columns: seq[Row], timeout: int): Future[(seq[Row], DbRows)] {.async.} =
|
||||
assert db.status == CONNECTION_OK
|
||||
let pgParams = PGParams.fromObjArray(args, columns)
|
||||
defer:
|
||||
if pgParams.nParams > 0: pgParams.values.deallocCStringArray()
|
||||
pgSendQueryParams(db, query, pgParams)
|
||||
var dbRows: DbRows
|
||||
var rows = newSeq[Row]()
|
||||
let deadline = makePgDeadline(timeout)
|
||||
await pgFlushOutgoing(db, deadline)
|
||||
while true:
|
||||
let pqresult = await pgNextResult(db, deadline)
|
||||
if pqresult == nil:
|
||||
db.checkError()
|
||||
break
|
||||
|
||||
let cols = pqnfields(pqresult)
|
||||
var row = newRow(cols)
|
||||
let base = buildBaseDbColumns(pqresult, cols)
|
||||
for i in 0'i32 .. pqNtuples(pqresult) - 1:
|
||||
setRow(pqresult, row, i, cols)
|
||||
appendDbRowWithBaseColumns(pqresult, dbRows, i, cols, base)
|
||||
rows.add(row)
|
||||
pqclear(pqresult)
|
||||
|
||||
return (rows, dbRows)
|
||||
|
||||
|
||||
proc rawQuery*(db: PPGconn, query: string, args: JsonNode, timeout: int): Future[(seq[Row], DbRows)] {.async.} =
|
||||
assert db.status == CONNECTION_OK
|
||||
let pgParams = PGParams.fromArray(args)
|
||||
defer:
|
||||
if pgParams.nParams > 0: pgParams.values.deallocCStringArray()
|
||||
pgSendQueryParams(db, query, pgParams)
|
||||
var dbRows: DbRows
|
||||
var rows = newSeq[Row]()
|
||||
let deadline = makePgDeadline(timeout)
|
||||
await pgFlushOutgoing(db, deadline)
|
||||
while true:
|
||||
let pqresult = await pgNextResult(db, deadline)
|
||||
if pqresult == nil:
|
||||
db.checkError()
|
||||
break
|
||||
|
||||
let cols = pqnfields(pqresult)
|
||||
var row = newRow(cols)
|
||||
let base = buildBaseDbColumns(pqresult, cols)
|
||||
for i in 0'i32 .. pqNtuples(pqresult) - 1:
|
||||
setRow(pqresult, row, i, cols)
|
||||
appendDbRowWithBaseColumns(pqresult, dbRows, i, cols, base)
|
||||
rows.add(row)
|
||||
pqclear(pqresult)
|
||||
|
||||
return (rows, dbRows)
|
||||
|
||||
|
||||
proc rawExec*(db: PPGconn, query: string, args: JsonNode, timeout: int) {.async.} =
|
||||
assert db.status == CONNECTION_OK
|
||||
let pgParams = PGParams.fromArray(args)
|
||||
defer:
|
||||
if pgParams.nParams > 0: pgParams.values.deallocCStringArray()
|
||||
pgSendQueryParams(db, query, pgParams)
|
||||
let deadline = makePgDeadline(timeout)
|
||||
await pgFlushOutgoing(db, deadline)
|
||||
while true:
|
||||
let pqresult = await pgNextResult(db, deadline)
|
||||
if pqresult == nil:
|
||||
db.checkError()
|
||||
break
|
||||
pqclear(pqresult)
|
||||
|
||||
|
||||
# ==================================================
|
||||
# Old functions
|
||||
# ==================================================
|
||||
|
||||
proc query*(db: PPGconn, query: string, args: seq[string], timeout: int): Future[(seq[Row], DbRows)] {.async.} =
|
||||
assert db.status == CONNECTION_OK
|
||||
let status = pqsendQuery(db, dbFormat(query, args).cstring)
|
||||
if status != 1: dbError(db)
|
||||
var dbRows: DbRows
|
||||
var rows = newSeq[Row]()
|
||||
let deadline = makePgDeadline(timeout)
|
||||
await pgFlushOutgoing(db, deadline)
|
||||
while true:
|
||||
let pqresult = await pgNextResult(db, deadline)
|
||||
if pqresult == nil:
|
||||
db.checkError()
|
||||
break
|
||||
|
||||
let cols = pqnfields(pqresult)
|
||||
var row = newRow(cols)
|
||||
let base = buildBaseDbColumns(pqresult, cols)
|
||||
for i in 0'i32 .. pqNtuples(pqresult) - 1:
|
||||
setRow(pqresult, row, i, cols)
|
||||
appendDbRowWithBaseColumns(pqresult, dbRows, i, cols, base)
|
||||
rows.add(row)
|
||||
pqclear(pqresult)
|
||||
|
||||
return (rows, dbRows)
|
||||
|
||||
proc queryPlain*(db: PPGconn, query: string, args: seq[string], timeout: int): Future[seq[Row]] {.async.} =
|
||||
assert db.status == CONNECTION_OK
|
||||
let status = pqsendQuery(db, dbFormat(query, args).cstring)
|
||||
if status != 1: dbError(db)
|
||||
var rows = newSeq[Row]()
|
||||
let deadline = makePgDeadline(timeout)
|
||||
await pgFlushOutgoing(db, deadline)
|
||||
while true:
|
||||
let pqresult = await pgNextResult(db, deadline)
|
||||
if pqresult == nil:
|
||||
db.checkError()
|
||||
break
|
||||
|
||||
var cols = pqnfields(pqresult)
|
||||
var row = newRow(cols)
|
||||
for i in 0'i32 .. pqNtuples(pqresult) - 1:
|
||||
setRow(pqresult, row, i, cols)
|
||||
rows.add(row)
|
||||
pqclear(pqresult)
|
||||
|
||||
return rows
|
||||
|
||||
|
||||
proc exec*(db: PPGconn, query: string, args: seq[string], timeout: int) {.async.} =
|
||||
assert db.status == CONNECTION_OK
|
||||
let success = pqsendQuery(db, dbFormat(query, args).cstring)
|
||||
if success != 1: dbError(db)
|
||||
let deadline = makePgDeadline(timeout)
|
||||
await pgFlushOutgoing(db, deadline)
|
||||
while true:
|
||||
let pqresult = await pgNextResult(db, deadline)
|
||||
if pqresult == nil:
|
||||
db.checkError()
|
||||
break
|
||||
pqclear(pqresult)
|
||||
|
||||
|
||||
proc getColumns*(db: PPGconn, query: string, args: seq[string], timeout: int): Future[seq[string]] {.async.} =
|
||||
assert db.status == CONNECTION_OK
|
||||
let status = pqsendQuery(db, dbFormat(query, args).cstring)
|
||||
if status != 1: dbError(db)
|
||||
var dbRows: DbRows
|
||||
let deadline = makePgDeadline(timeout)
|
||||
await pgFlushOutgoing(db, deadline)
|
||||
while true:
|
||||
let pqresult = await pgNextResult(db, deadline)
|
||||
if pqresult == nil:
|
||||
db.checkError()
|
||||
break
|
||||
|
||||
let cols = pqnfields(pqresult)
|
||||
let base = buildBaseDbColumns(pqresult, cols)
|
||||
appendDbRowWithBaseColumns(pqresult, dbRows, 0, cols, base)
|
||||
pqclear(pqresult)
|
||||
|
||||
for column in dbRows[0]:
|
||||
result.add(column.name)
|
||||
|
||||
|
||||
proc prepare*(db: PPGconn, query: string, timeout: int, stmtName: string, nArgs: int): Future[void] {.async.} =
|
||||
assert db.status == CONNECTION_OK
|
||||
let success = pqsendPrepare(db, stmtName, questionToDaller(query).cstring, int32(nArgs), nil)
|
||||
if success != 1: dbError(db)
|
||||
let deadline = makePgDeadline(timeout)
|
||||
await pgFlushOutgoing(db, deadline)
|
||||
while true:
|
||||
let pqresult = await pgNextResult(db, deadline)
|
||||
if pqresult == nil:
|
||||
db.checkError()
|
||||
break
|
||||
pqclear(pqresult)
|
||||
|
||||
|
||||
proc deallocate*(db: PPGconn, stmtName: string, timeout: int): Future[void] {.async.} =
|
||||
assert db.status == CONNECTION_OK
|
||||
if stmtName.len == 0:
|
||||
return
|
||||
let success = pqsendQuery(db, ("DEALLOCATE " & stmtName).cstring)
|
||||
if success != 1:
|
||||
dbError(db)
|
||||
let deadline = makePgDeadline(timeout)
|
||||
await pgFlushOutgoing(db, deadline)
|
||||
while true:
|
||||
let pqresult = await pgNextResult(db, deadline)
|
||||
if pqresult == nil:
|
||||
db.checkError()
|
||||
break
|
||||
pqclear(pqresult)
|
||||
|
||||
proc allocPreparedCStringArray(args: seq[PreparedParam]): cstringArray =
|
||||
result = cast[cstringArray](alloc0((args.len + 1) * sizeof(cstring)))
|
||||
for i, arg in args:
|
||||
if arg.isNull:
|
||||
continue
|
||||
let cstrLen = arg.value.len + 1
|
||||
let cstr = cast[cstring](alloc0(cstrLen))
|
||||
copyMem(cstr, arg.value.cstring, arg.value.len)
|
||||
result[i] = cstr
|
||||
|
||||
|
||||
proc freePreparedCStringArray(values: cstringArray, n: int) =
|
||||
if values.isNil:
|
||||
return
|
||||
for i in 0 ..< n:
|
||||
if values[i] != nil:
|
||||
dealloc(values[i])
|
||||
dealloc(values)
|
||||
|
||||
|
||||
proc preparedQuery*(db: PPGconn, args: seq[PreparedParam], nArgs: int, timeout: int, stmtName: string): Future[(seq[Row], DbRows)] {.async.} =
|
||||
assert db.status == CONNECTION_OK
|
||||
let deadline = makePgDeadline(timeout)
|
||||
await pgEnsureIdle(db, deadline)
|
||||
let values = allocPreparedCStringArray(args)
|
||||
defer:
|
||||
freePreparedCStringArray(values, args.len)
|
||||
let status = pqsendQueryPrepared(db, stmtName, int32(nArgs), values, nil, nil, 0)
|
||||
if status != 1: dbError(db)
|
||||
var dbRows: DbRows
|
||||
var rows = newSeq[Row]()
|
||||
await pgFlushOutgoing(db, deadline)
|
||||
while true:
|
||||
let pqresult = await pgNextResult(db, deadline)
|
||||
if pqresult == nil:
|
||||
db.checkError()
|
||||
break
|
||||
|
||||
let cols = pqnfields(pqresult)
|
||||
var row = newRow(cols)
|
||||
let base = buildBaseDbColumns(pqresult, cols)
|
||||
for i in 0'i32 .. pqNtuples(pqresult) - 1:
|
||||
setRow(pqresult, row, i, cols)
|
||||
appendDbRowWithBaseColumns(pqresult, dbRows, i, cols, base)
|
||||
rows.add(row)
|
||||
pqclear(pqresult)
|
||||
|
||||
return (rows, dbRows)
|
||||
|
||||
proc preparedExec*(db: PPGconn, args: seq[PreparedParam], nArgs: int, timeout: int, stmtName: string) {.async.} =
|
||||
assert db.status == CONNECTION_OK
|
||||
let deadline = makePgDeadline(timeout)
|
||||
await pgEnsureIdle(db, deadline)
|
||||
let values = allocPreparedCStringArray(args)
|
||||
defer:
|
||||
freePreparedCStringArray(values, args.len)
|
||||
let status = pqsendQueryPrepared(db, stmtName, int32(nArgs), values, nil, nil, 0)
|
||||
if status != 1: dbError(db)
|
||||
await pgFlushOutgoing(db, deadline)
|
||||
while true:
|
||||
let pqresult = await pgNextResult(db, deadline)
|
||||
if pqresult == nil:
|
||||
db.checkError()
|
||||
break
|
||||
pqclear(pqresult)
|
||||
@@ -0,0 +1,364 @@
|
||||
import std/strutils
|
||||
import std/json
|
||||
import ../../error
|
||||
import ../../models/database_types
|
||||
import ./postgres_rdb
|
||||
|
||||
|
||||
proc dbError*(db: PPGconn) {.noreturn.} =
|
||||
## raises a DbError exception.
|
||||
var e: ref DbError
|
||||
new(e)
|
||||
e.msg = $pqErrorMessage(db)
|
||||
raise e
|
||||
|
||||
proc checkError*(db: PPGconn) =
|
||||
## Raises a DbError exception.
|
||||
var message = pqErrorMessage(db)
|
||||
if message.len > 0:
|
||||
raise newException(DbError, $message)
|
||||
|
||||
proc getBaseColumnType(res: PPGresult, col: int32): DbType =
|
||||
## OID → DbType(行に依存しない)。NULL セルは行ごとに `pqgetisnull` で上書きする。
|
||||
let oid = pqftype(res, col)
|
||||
case oid
|
||||
of 16: return DbType(kind: DbTypeKind.dbBool, name: "bool")
|
||||
of 17: return DbType(kind: DbTypeKind.dbBlob, name: "bytea")
|
||||
|
||||
of 21: return DbType(kind: DbTypeKind.dbInt, name: "int2", size: 2)
|
||||
of 23: return DbType(kind: DbTypeKind.dbInt, name: "int4", size: 4)
|
||||
of 20: return DbType(kind: DbTypeKind.dbInt, name: "int8", size: 8)
|
||||
of 1560: return DbType(kind: DbTypeKind.dbBit, name: "bit")
|
||||
of 1562: return DbType(kind: DbTypeKind.dbInt, name: "varbit")
|
||||
|
||||
of 18: return DbType(kind: DbTypeKind.dbFixedChar, name: "char")
|
||||
of 19: return DbType(kind: DbTypeKind.dbFixedChar, name: "name")
|
||||
of 1042: return DbType(kind: DbTypeKind.dbFixedChar, name: "bpchar")
|
||||
|
||||
of 25: return DbType(kind: DbTypeKind.dbVarchar, name: "text")
|
||||
of 1043: return DbType(kind: DbTypeKind.dbVarChar, name: "varchar")
|
||||
of 2275: return DbType(kind: DbTypeKind.dbVarchar, name: "cstring")
|
||||
|
||||
of 700: return DbType(kind: DbTypeKind.dbFloat, name: "float4")
|
||||
of 701: return DbType(kind: DbTypeKind.dbFloat, name: "float8")
|
||||
|
||||
of 790: return DbType(kind: DbTypeKind.dbDecimal, name: "money")
|
||||
of 1700: return DbType(kind: DbTypeKind.dbDecimal, name: "numeric")
|
||||
|
||||
of 704: return DbType(kind: DbTypeKind.dbTimeInterval, name: "tinterval")
|
||||
of 702: return DbType(kind: DbTypeKind.dbTimestamp, name: "abstime")
|
||||
of 703: return DbType(kind: DbTypeKind.dbTimeInterval, name: "reltime")
|
||||
of 1082: return DbType(kind: DbTypeKind.dbDate, name: "date")
|
||||
of 1083: return DbType(kind: DbTypeKind.dbTime, name: "time")
|
||||
of 1114: return DbType(kind: DbTypeKind.dbTimestamp, name: "timestamp")
|
||||
of 1184: return DbType(kind: DbTypeKind.dbTimestamp, name: "timestamptz")
|
||||
of 1186: return DbType(kind: DbTypeKind.dbTimeInterval, name: "interval")
|
||||
of 1266: return DbType(kind: DbTypeKind.dbTime, name: "timetz")
|
||||
|
||||
of 114: return DbType(kind: DbTypeKind.dbJson, name: "json")
|
||||
of 142: return DbType(kind: DbTypeKind.dbXml, name: "xml")
|
||||
of 3802: return DbType(kind: DbTypeKind.dbJson, name: "jsonb")
|
||||
|
||||
of 600: return DbType(kind: DbTypeKind.dbPoint, name: "point")
|
||||
of 601: return DbType(kind: DbTypeKind.dbLseg, name: "lseg")
|
||||
of 602: return DbType(kind: DbTypeKind.dbPath, name: "path")
|
||||
of 603: return DbType(kind: DbTypeKind.dbBox, name: "box")
|
||||
of 604: return DbType(kind: DbTypeKind.dbPolygon, name: "polygon")
|
||||
of 628: return DbType(kind: DbTypeKind.dbLine, name: "line")
|
||||
of 718: return DbType(kind: DbTypeKind.dbCircle, name: "circle")
|
||||
|
||||
of 650: return DbType(kind: DbTypeKind.dbInet, name: "cidr")
|
||||
of 829: return DbType(kind: DbTypeKind.dbMacAddress, name: "macaddr")
|
||||
of 869: return DbType(kind: DbTypeKind.dbInet, name: "inet")
|
||||
|
||||
of 2950: return DbType(kind: DbTypeKind.dbVarchar, name: "uuid")
|
||||
of 3614: return DbType(kind: DbTypeKind.dbVarchar, name: "tsvector")
|
||||
of 3615: return DbType(kind: DbTypeKind.dbVarchar, name: "tsquery")
|
||||
of 2970: return DbType(kind: DbTypeKind.dbVarchar, name: "txid_snapshot")
|
||||
|
||||
of 27: return DbType(kind: DbTypeKind.dbComposite, name: "tid")
|
||||
of 1790: return DbType(kind: DbTypeKind.dbComposite, name: "refcursor")
|
||||
of 2249: return DbType(kind: DbTypeKind.dbComposite, name: "record")
|
||||
of 3904: return DbType(kind: DbTypeKind.dbComposite, name: "int4range")
|
||||
of 3906: return DbType(kind: DbTypeKind.dbComposite, name: "numrange")
|
||||
of 3908: return DbType(kind: DbTypeKind.dbComposite, name: "tsrange")
|
||||
of 3910: return DbType(kind: DbTypeKind.dbComposite, name: "tstzrange")
|
||||
of 3912: return DbType(kind: DbTypeKind.dbComposite, name: "daterange")
|
||||
of 3926: return DbType(kind: DbTypeKind.dbComposite, name: "int8range")
|
||||
|
||||
of 22: return DbType(kind: DbTypeKind.dbArray, name: "int2vector")
|
||||
of 30: return DbType(kind: DbTypeKind.dbArray, name: "oidvector")
|
||||
of 143: return DbType(kind: DbTypeKind.dbArray, name: "xml[]")
|
||||
of 199: return DbType(kind: DbTypeKind.dbArray, name: "json[]")
|
||||
of 629: return DbType(kind: DbTypeKind.dbArray, name: "line[]")
|
||||
of 651: return DbType(kind: DbTypeKind.dbArray, name: "cidr[]")
|
||||
of 719: return DbType(kind: DbTypeKind.dbArray, name: "circle[]")
|
||||
of 791: return DbType(kind: DbTypeKind.dbArray, name: "money[]")
|
||||
of 1000: return DbType(kind: DbTypeKind.dbArray, name: "bool[]")
|
||||
of 1001: return DbType(kind: DbTypeKind.dbArray, name: "bytea[]")
|
||||
of 1002: return DbType(kind: DbTypeKind.dbArray, name: "char[]")
|
||||
of 1003: return DbType(kind: DbTypeKind.dbArray, name: "name[]")
|
||||
of 1005: return DbType(kind: DbTypeKind.dbArray, name: "int2[]")
|
||||
of 1006: return DbType(kind: DbTypeKind.dbArray, name: "int2vector[]")
|
||||
of 1007: return DbType(kind: DbTypeKind.dbArray, name: "int4[]")
|
||||
of 1008: return DbType(kind: DbTypeKind.dbArray, name: "regproc[]")
|
||||
of 1009: return DbType(kind: DbTypeKind.dbArray, name: "text[]")
|
||||
of 1028: return DbType(kind: DbTypeKind.dbArray, name: "oid[]")
|
||||
of 1010: return DbType(kind: DbTypeKind.dbArray, name: "tid[]")
|
||||
of 1011: return DbType(kind: DbTypeKind.dbArray, name: "xid[]")
|
||||
of 1012: return DbType(kind: DbTypeKind.dbArray, name: "cid[]")
|
||||
of 1013: return DbType(kind: DbTypeKind.dbArray, name: "oidvector[]")
|
||||
of 1014: return DbType(kind: DbTypeKind.dbArray, name: "bpchar[]")
|
||||
of 1015: return DbType(kind: DbTypeKind.dbArray, name: "varchar[]")
|
||||
of 1016: return DbType(kind: DbTypeKind.dbArray, name: "int8[]")
|
||||
of 1017: return DbType(kind: DbTypeKind.dbArray, name: "point[]")
|
||||
of 1018: return DbType(kind: DbTypeKind.dbArray, name: "lseg[]")
|
||||
of 1019: return DbType(kind: DbTypeKind.dbArray, name: "path[]")
|
||||
of 1020: return DbType(kind: DbTypeKind.dbArray, name: "box[]")
|
||||
of 1021: return DbType(kind: DbTypeKind.dbArray, name: "float4[]")
|
||||
of 1022: return DbType(kind: DbTypeKind.dbArray, name: "float8[]")
|
||||
of 1023: return DbType(kind: DbTypeKind.dbArray, name: "abstime[]")
|
||||
of 1024: return DbType(kind: DbTypeKind.dbArray, name: "reltime[]")
|
||||
of 1025: return DbType(kind: DbTypeKind.dbArray, name: "tinterval[]")
|
||||
of 1027: return DbType(kind: DbTypeKind.dbArray, name: "polygon[]")
|
||||
of 1040: return DbType(kind: DbTypeKind.dbArray, name: "macaddr[]")
|
||||
of 1041: return DbType(kind: DbTypeKind.dbArray, name: "inet[]")
|
||||
of 1263: return DbType(kind: DbTypeKind.dbArray, name: "cstring[]")
|
||||
of 1115: return DbType(kind: DbTypeKind.dbArray, name: "timestamp[]")
|
||||
of 1182: return DbType(kind: DbTypeKind.dbArray, name: "date[]")
|
||||
of 1183: return DbType(kind: DbTypeKind.dbArray, name: "time[]")
|
||||
of 1185: return DbType(kind: DbTypeKind.dbArray, name: "timestamptz[]")
|
||||
of 1187: return DbType(kind: DbTypeKind.dbArray, name: "interval[]")
|
||||
of 1231: return DbType(kind: DbTypeKind.dbArray, name: "numeric[]")
|
||||
of 1270: return DbType(kind: DbTypeKind.dbArray, name: "timetz[]")
|
||||
of 1561: return DbType(kind: DbTypeKind.dbArray, name: "bit[]")
|
||||
of 1563: return DbType(kind: DbTypeKind.dbArray, name: "varbit[]")
|
||||
of 2201: return DbType(kind: DbTypeKind.dbArray, name: "refcursor[]")
|
||||
of 2951: return DbType(kind: DbTypeKind.dbArray, name: "uuid[]")
|
||||
of 3643: return DbType(kind: DbTypeKind.dbArray, name: "tsvector[]")
|
||||
of 3645: return DbType(kind: DbTypeKind.dbArray, name: "tsquery[]")
|
||||
of 3807: return DbType(kind: DbTypeKind.dbArray, name: "jsonb[]")
|
||||
of 2949: return DbType(kind: DbTypeKind.dbArray, name: "txid_snapshot[]")
|
||||
of 3905: return DbType(kind: DbTypeKind.dbArray, name: "int4range[]")
|
||||
of 3907: return DbType(kind: DbTypeKind.dbArray, name: "numrange[]")
|
||||
of 3909: return DbType(kind: DbTypeKind.dbArray, name: "tsrange[]")
|
||||
of 3911: return DbType(kind: DbTypeKind.dbArray, name: "tstzrange[]")
|
||||
of 3913: return DbType(kind: DbTypeKind.dbArray, name: "daterange[]")
|
||||
of 3927: return DbType(kind: DbTypeKind.dbArray, name: "int8range[]")
|
||||
of 2287: return DbType(kind: DbTypeKind.dbArray, name: "record[]")
|
||||
|
||||
of 705: return DbType(kind: DbTypeKind.dbUnknown, name: "unknown")
|
||||
else: return DbType(kind: DbTypeKind.dbUnknown, name: $oid) ## Query the system table pg_type to determine exactly which type is referenced.
|
||||
|
||||
proc buildBaseDbColumns*(res: PPGresult; cols: int32): DbColumns =
|
||||
result = newSeqOfCap[DbColumn](cols.int)
|
||||
setLen(result, cols)
|
||||
for col in 0'i32 ..< cols:
|
||||
result[col].name = $pqfname(res, col)
|
||||
result[col].typ = getBaseColumnType(res, col)
|
||||
result[col].tableName = $(pqftable(res, col))
|
||||
|
||||
proc appendDbRowWithBaseColumns*(res: PPGresult; dbRows: var DbRows; line, cols: int32; base: DbColumns) =
|
||||
var columns = base
|
||||
for col in 0'i32 ..< cols:
|
||||
if pqgetisnull(res, line, col) == 1:
|
||||
columns[col].typ = DbType(kind: dbNull, name: "null")
|
||||
dbRows.add(columns)
|
||||
|
||||
proc setColumnInfo*(res: PPGresult; dbRows: var DbRows; line, cols: int32) =
|
||||
let base = buildBaseDbColumns(res, cols)
|
||||
appendDbRowWithBaseColumns(res, dbRows, line, cols, base)
|
||||
|
||||
proc newRow*(L: int): Row =
|
||||
newSeq(result, L)
|
||||
for i in 0..L-1: result[i] = ""
|
||||
|
||||
proc setRow*(res: PPGresult, r: var Row, line, cols: int32) =
|
||||
for col in 0'i32..cols-1:
|
||||
setLen(r[col], 0)
|
||||
let x = pqgetvalue(res, line, col)
|
||||
if x.isNil:
|
||||
r[col] = ""
|
||||
else:
|
||||
add(r[col], x)
|
||||
|
||||
proc dbQuote(s: string): string =
|
||||
# ## DB quotes the string.
|
||||
# if s == "null":
|
||||
# return "NULL"
|
||||
# result = "'"
|
||||
# for c in items(s):
|
||||
# if c == '\'': add(result, "''")
|
||||
# else: add(result, c)
|
||||
# add(result, '\'')
|
||||
|
||||
## DB quotes the string.
|
||||
if s == "null":
|
||||
return "NULL"
|
||||
result = newStringOfCap(s.len * 2 + 2)
|
||||
result.add('\'')
|
||||
for c in items(s):
|
||||
case c
|
||||
of '\'': add(result, "''")
|
||||
of '\0': add(result, "\\0")
|
||||
else: add(result, c)
|
||||
add(result, '\'')
|
||||
|
||||
proc dbFormat*(formatstr: string, args: varargs[string]): string =
|
||||
var a = 0
|
||||
if args.len > 0 and not formatstr.contains("?"):
|
||||
dbError("""parameter substitution expects "?" """)
|
||||
if args.len == 0:
|
||||
return formatstr
|
||||
result = newStringOfCap(formatstr.len + args.len * 8)
|
||||
var segStart = 0
|
||||
for j in 0 ..< formatstr.len:
|
||||
if formatstr[j] == '?':
|
||||
if j > segStart:
|
||||
result.add(formatstr[segStart ..< j])
|
||||
result.add(dbQuote(args[a]))
|
||||
inc(a)
|
||||
segStart = j + 1
|
||||
if segStart < formatstr.len:
|
||||
result.add(formatstr[segStart ..< formatstr.len])
|
||||
|
||||
|
||||
proc questionToDaller*(s:string):string =
|
||||
## from `UPDATE user SET name = ?, email = ? WHERE id = ?`
|
||||
##
|
||||
## to `UPDATE user SET name = $1, email = $2 WHERE id = $3`
|
||||
var i = 1
|
||||
var segStart = 0
|
||||
result = newStringOfCap(s.len + 8)
|
||||
for j in 0 ..< s.len:
|
||||
if s[j] == '?':
|
||||
if j > segStart:
|
||||
result.add(s[segStart ..< j])
|
||||
result.add('$')
|
||||
result.add($i)
|
||||
inc(i)
|
||||
segStart = j + 1
|
||||
if segStart < s.len:
|
||||
result.add(s[segStart ..< s.len])
|
||||
|
||||
|
||||
type PGParams* = object
|
||||
nParams*: int32
|
||||
values*: cstringArray
|
||||
lengths*: seq[int32]
|
||||
formats*: seq[int32] # 0:text,1:binary
|
||||
|
||||
|
||||
proc objArrayParamSeqs(args: JsonNode; columns: seq[Row]): tuple[values: seq[string], lengths: seq[int32], formats: seq[int32]] =
|
||||
result.values = newSeq[string](args.len)
|
||||
result.lengths = newSeq[int32](args.len)
|
||||
result.formats = newSeq[int32](args.len)
|
||||
var i = 0
|
||||
for arg in args.items:
|
||||
defer: i.inc()
|
||||
case arg["value"].kind
|
||||
of JBool:
|
||||
result.values[i] = if arg["value"].getBool: "t" else: "f"
|
||||
result.lengths[i] = 0
|
||||
result.formats[i] = 0
|
||||
of JInt:
|
||||
result.values[i] = $arg["value"].getInt
|
||||
result.lengths[i] = 0
|
||||
result.formats[i] = 0
|
||||
of JFloat:
|
||||
result.values[i] = $arg["value"].getFloat
|
||||
result.lengths[i] = 0
|
||||
result.formats[i] = 0
|
||||
of JNull:
|
||||
result.values[i] = "NULL"
|
||||
result.lengths[i] = 0
|
||||
result.formats[i] = 0
|
||||
of JObject, JArray:
|
||||
result.values[i] = arg["value"].pretty
|
||||
result.lengths[i] = 0
|
||||
result.formats[i] = 0
|
||||
of JString:
|
||||
if columns.len > 0:
|
||||
for column in columns:
|
||||
if column[0] == arg["key"].getStr:
|
||||
defer: break
|
||||
let value = arg["value"].getStr
|
||||
result.values[i] = value
|
||||
result.lengths[i] = value.len.int32
|
||||
if column[1] == "bytea":
|
||||
result.formats[i] = 1
|
||||
else:
|
||||
result.formats[i] = 0
|
||||
else:
|
||||
let value = arg["value"].getStr
|
||||
result.values[i] = value
|
||||
result.lengths[i] = value.len.int32
|
||||
result.formats[i] = 0
|
||||
|
||||
proc allocPgParamsFromSeqs(values: seq[string]; lengths, formats: seq[int32]; n: int): PGParams =
|
||||
result.nParams = n.int32
|
||||
result.lengths = lengths
|
||||
result.formats = formats
|
||||
result.values = allocCStringArray(values)
|
||||
for j, row in values:
|
||||
if row == "NULL":
|
||||
result.values[j] = nil
|
||||
|
||||
proc fromObjArray*(_: type PGParams, args: JsonNode, columns: seq[Row]): PGParams =
|
||||
if args.len == 0:
|
||||
return
|
||||
let t = objArrayParamSeqs(args, columns)
|
||||
result = allocPgParamsFromSeqs(t.values, t.lengths, t.formats, args.len)
|
||||
|
||||
proc fromObjArray*(_: type PGParams, args: JsonNode): PGParams =
|
||||
## `args` is JArray `[{"key": "bool", "value": true},{"key": "int", "value": 1}]`
|
||||
if args.len == 0:
|
||||
return
|
||||
let t = objArrayParamSeqs(args, @[])
|
||||
result = allocPgParamsFromSeqs(t.values, t.lengths, t.formats, args.len)
|
||||
|
||||
|
||||
proc fromArray*(_:type PGParams, args: JsonNode):PGParams =
|
||||
## `args` is JArray `{true, 1, 1.1, "alice"}`
|
||||
if args.len == 0:
|
||||
return
|
||||
result.nParams = args.len.int32
|
||||
|
||||
var values = newSeq[string](args.len)
|
||||
result.formats = newSeq[int32](args.len)
|
||||
result.lengths = newSeq[int32](args.len)
|
||||
|
||||
var i = 0
|
||||
for arg in args.items:
|
||||
defer: i.inc()
|
||||
case arg.kind
|
||||
of JBool:
|
||||
values[i] = if arg.getBool: "t" else: "f"
|
||||
result.lengths[i] = 0
|
||||
result.formats[i] = 0
|
||||
of JInt:
|
||||
values[i] = $arg.getInt
|
||||
result.lengths[i] = 0
|
||||
result.formats[i] = 0
|
||||
of JFloat:
|
||||
values[i] = $arg.getFloat
|
||||
result.lengths[i] = 0
|
||||
result.formats[i] = 0
|
||||
of JNull:
|
||||
values[i] = "NULL"
|
||||
result.lengths[i] = 0
|
||||
result.formats[i] = 0
|
||||
of JObject, JArray:
|
||||
values[i] = arg.pretty
|
||||
result.lengths[i] = 0
|
||||
result.formats[i] = 0
|
||||
of JString:
|
||||
let value = arg.getStr
|
||||
values[i] = value
|
||||
result.lengths[i] = value.len.int32
|
||||
result.formats[i] = 0
|
||||
|
||||
result.values = allocCStringArray(values)
|
||||
for i, row in values:
|
||||
if row == "NULL":
|
||||
result.values[i] = nil
|
||||
@@ -0,0 +1,374 @@
|
||||
when defined(nimHasStyleChecks):
|
||||
{.push styleChecks: off.}
|
||||
|
||||
when defined(windows):
|
||||
const
|
||||
dllName = "libpq.dll"
|
||||
elif defined(macosx):
|
||||
const
|
||||
dllName = "libpq.dylib"
|
||||
else:
|
||||
const
|
||||
dllName = "libpq.so(.5|)"
|
||||
type
|
||||
POid* = ptr Oid
|
||||
Oid* = int32
|
||||
|
||||
const
|
||||
ERROR_MSG_LENGTH* = 4096
|
||||
CMDSTATUS_LEN* = 40
|
||||
|
||||
type
|
||||
SockAddr* = array[1..112, int8]
|
||||
PGresAttDesc*{.pure, final.} = object
|
||||
name*: cstring
|
||||
adtid*: Oid
|
||||
adtsize*: int
|
||||
|
||||
PPGresAttDesc* = ptr PGresAttDesc
|
||||
PPPGresAttDesc* = ptr PPGresAttDesc
|
||||
PGresAttValue*{.pure, final.} = object
|
||||
length*: int32
|
||||
value*: cstring
|
||||
|
||||
PPGresAttValue* = ptr PGresAttValue
|
||||
PPPGresAttValue* = ptr PPGresAttValue
|
||||
PExecStatusType* = ptr ExecStatusType
|
||||
ExecStatusType* = enum
|
||||
PGRES_EMPTY_QUERY = 0, PGRES_COMMAND_OK, PGRES_TUPLES_OK, PGRES_COPY_OUT,
|
||||
PGRES_COPY_IN, PGRES_BAD_RESPONSE, PGRES_NONFATAL_ERROR, PGRES_FATAL_ERROR,
|
||||
PGRES_COPY_BOTH, PGRES_SINGLE_TUPLE
|
||||
PGlobjfuncs*{.pure, final.} = object
|
||||
fn_lo_open*: Oid
|
||||
fn_lo_close*: Oid
|
||||
fn_lo_creat*: Oid
|
||||
fn_lo_unlink*: Oid
|
||||
fn_lo_lseek*: Oid
|
||||
fn_lo_tell*: Oid
|
||||
fn_lo_read*: Oid
|
||||
fn_lo_write*: Oid
|
||||
|
||||
PPGlobjfuncs* = ptr PGlobjfuncs
|
||||
PConnStatusType* = ptr ConnStatusType
|
||||
ConnStatusType* = enum
|
||||
CONNECTION_OK, CONNECTION_BAD, CONNECTION_STARTED, CONNECTION_MADE,
|
||||
CONNECTION_AWAITING_RESPONSE, CONNECTION_AUTH_OK, CONNECTION_SETENV,
|
||||
CONNECTION_SSL_STARTUP, CONNECTION_NEEDED, CONNECTION_CHECK_WRITABLE,
|
||||
CONNECTION_CONSUME, CONNECTION_GSS_STARTUP, CONNECTION_CHECK_TARGET
|
||||
PGconn*{.pure, final.} = object
|
||||
pghost*: cstring
|
||||
pgtty*: cstring
|
||||
pgport*: cstring
|
||||
pgoptions*: cstring
|
||||
dbName*: cstring
|
||||
status*: ConnStatusType
|
||||
errorMessage*: array[0..(ERROR_MSG_LENGTH) - 1, char]
|
||||
Pfin*: File
|
||||
Pfout*: File
|
||||
Pfdebug*: File
|
||||
sock*: int32
|
||||
laddr*: SockAddr
|
||||
raddr*: SockAddr
|
||||
salt*: array[0..(2) - 1, char]
|
||||
asyncNotifyWaiting*: int32
|
||||
notifyList*: pointer
|
||||
pguser*: cstring
|
||||
pgpass*: cstring
|
||||
lobjfuncs*: PPGlobjfuncs
|
||||
|
||||
PPGconn* = ptr PGconn
|
||||
PGresult*{.pure, final.} = object
|
||||
ntups*: int32
|
||||
numAttributes*: int32
|
||||
attDescs*: PPGresAttDesc
|
||||
tuples*: PPPGresAttValue
|
||||
tupArrSize*: int32
|
||||
resultStatus*: ExecStatusType
|
||||
cmdStatus*: array[0..(CMDSTATUS_LEN) - 1, char]
|
||||
binary*: int32
|
||||
conn*: PPGconn
|
||||
|
||||
PGCancel*{.pure, final.} = object
|
||||
PPGcancel* = ptr PGCancel
|
||||
|
||||
PPGresult* = ptr PGresult
|
||||
PPostgresPollingStatusType* = ptr PostgresPollingStatusType
|
||||
PostgresPollingStatusType* = enum
|
||||
PGRES_POLLING_FAILED = 0, PGRES_POLLING_READING, PGRES_POLLING_WRITING,
|
||||
PGRES_POLLING_OK, PGRES_POLLING_ACTIVE
|
||||
PPGTransactionStatusType* = ptr PGTransactionStatusType
|
||||
PGTransactionStatusType* = enum
|
||||
PQTRANS_IDLE, PQTRANS_ACTIVE, PQTRANS_INTRANS, PQTRANS_INERROR,
|
||||
PQTRANS_UNKNOWN
|
||||
PPGVerbosity* = ptr PGVerbosity
|
||||
PGVerbosity* = enum
|
||||
PQERRORS_TERSE, PQERRORS_DEFAULT, PQERRORS_VERBOSE, PQERRORS_SQLSTATE
|
||||
PPGNotify* = ptr pgNotify
|
||||
pgNotify*{.pure, final.} = object
|
||||
relname*: cstring
|
||||
be_pid*: int32
|
||||
extra*: cstring
|
||||
|
||||
PQnoticeReceiver* = proc (arg: pointer, res: PPGresult){.cdecl.}
|
||||
PQnoticeProcessor* = proc (arg: pointer, message: cstring){.cdecl.}
|
||||
Ppqbool* = ptr pqbool
|
||||
pqbool* = char
|
||||
PPQprintOpt* = ptr PQprintOpt
|
||||
PQprintOpt*{.pure, final.} = object
|
||||
header*: pqbool
|
||||
align*: pqbool
|
||||
standard*: pqbool
|
||||
html3*: pqbool
|
||||
expanded*: pqbool
|
||||
pager*: pqbool
|
||||
fieldSep*: cstring
|
||||
tableOpt*: cstring
|
||||
caption*: cstring
|
||||
fieldName*: ptr cstring
|
||||
|
||||
PPQconninfoOption* = ptr PQconninfoOption
|
||||
PQconninfoOption*{.pure, final.} = object
|
||||
keyword*: cstring
|
||||
envvar*: cstring
|
||||
compiled*: cstring
|
||||
val*: cstring
|
||||
label*: cstring
|
||||
dispchar*: cstring
|
||||
dispsize*: int32
|
||||
|
||||
PPQArgBlock* = ptr PQArgBlock
|
||||
PQArgBlock*{.pure, final.} = object
|
||||
length*: int32
|
||||
isint*: int32
|
||||
p*: pointer
|
||||
|
||||
proc pqinitOpenSSL*(do_ssl: int32, do_crypto: int32) {.cdecl, dynlib: dllName,
|
||||
importc: "PQinitOpenSSL".}
|
||||
proc pqconnectStart*(conninfo: cstring): PPGconn{.cdecl, dynlib: dllName,
|
||||
importc: "PQconnectStart".}
|
||||
proc pqconnectPoll*(conn: PPGconn): PostgresPollingStatusType{.cdecl,
|
||||
dynlib: dllName, importc: "PQconnectPoll".}
|
||||
proc pqconnectdb*(conninfo: cstring): PPGconn{.cdecl, dynlib: dllName,
|
||||
importc: "PQconnectdb".}
|
||||
proc pqsetdbLogin*(pghost: cstring, pgport: cstring, pgoptions: cstring,
|
||||
pgtty: cstring, dbName: cstring, login: cstring, pwd: cstring): PPGconn{.
|
||||
cdecl, dynlib: dllName, importc: "PQsetdbLogin".}
|
||||
proc pqsetdb*(M_PGHOST, M_PGPORT, M_PGOPT, M_PGTTY, M_DBNAME: cstring): PPGconn
|
||||
proc pqfinish*(conn: PPGconn){.cdecl, dynlib: dllName, importc: "PQfinish".}
|
||||
proc pqconndefaults*(): PPQconninfoOption{.cdecl, dynlib: dllName,
|
||||
importc: "PQconndefaults".}
|
||||
proc pqconninfoFree*(connOptions: PPQconninfoOption){.cdecl, dynlib: dllName,
|
||||
importc: "PQconninfoFree".}
|
||||
proc pqresetStart*(conn: PPGconn): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQresetStart".}
|
||||
proc pqresetPoll*(conn: PPGconn): PostgresPollingStatusType{.cdecl,
|
||||
dynlib: dllName, importc: "PQresetPoll".}
|
||||
proc pqreset*(conn: PPGconn){.cdecl, dynlib: dllName, importc: "PQreset".}
|
||||
|
||||
# add these procs pqGetCancel, pqFreeCancel and pqCancel.
|
||||
# https://www.postgresql.org/docs/12/libpq-cancel.html
|
||||
proc pqGetCancel*(conn: PPGconn): PPGcancel{.cdecl, dynlib: dllName, importc: "PQgetCancel".}
|
||||
proc pqFreeCancel*(cancel: PPGcancel){.cdecl, dynlib: dllName, importc: "PQfreeCancel".}
|
||||
proc pqCancel*(cancel: PPGcancel, errbuf: cstring, errbufsize: int32):int32{.cdecl, dynlib: dllName, importc: "PQcancel".}
|
||||
|
||||
proc pqrequestCancel*(conn: PPGconn): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQrequestCancel".}
|
||||
proc pqdb*(conn: PPGconn): cstring{.cdecl, dynlib: dllName, importc: "PQdb".}
|
||||
proc pquser*(conn: PPGconn): cstring{.cdecl, dynlib: dllName, importc: "PQuser".}
|
||||
proc pqpass*(conn: PPGconn): cstring{.cdecl, dynlib: dllName, importc: "PQpass".}
|
||||
proc pqhost*(conn: PPGconn): cstring{.cdecl, dynlib: dllName, importc: "PQhost".}
|
||||
proc pqport*(conn: PPGconn): cstring{.cdecl, dynlib: dllName, importc: "PQport".}
|
||||
proc pqtty*(conn: PPGconn): cstring{.cdecl, dynlib: dllName, importc: "PQtty".}
|
||||
proc pqoptions*(conn: PPGconn): cstring{.cdecl, dynlib: dllName,
|
||||
importc: "PQoptions".}
|
||||
proc pqstatus*(conn: PPGconn): ConnStatusType{.cdecl, dynlib: dllName,
|
||||
importc: "PQstatus".}
|
||||
proc pqtransactionStatus*(conn: PPGconn): PGTransactionStatusType{.cdecl,
|
||||
dynlib: dllName, importc: "PQtransactionStatus".}
|
||||
proc pqparameterStatus*(conn: PPGconn, paramName: cstring): cstring{.cdecl,
|
||||
dynlib: dllName, importc: "PQparameterStatus".}
|
||||
proc pqserverVersion*(conn: PPGconn): int32{.cdecl,
|
||||
dynlib: dllName, importc: "PQserverVersion".}
|
||||
proc pqprotocolVersion*(conn: PPGconn): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQprotocolVersion".}
|
||||
proc pqerrorMessage*(conn: PPGconn): cstring{.cdecl, dynlib: dllName,
|
||||
importc: "PQerrorMessage".}
|
||||
proc pqsocket*(conn: PPGconn): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQsocket".}
|
||||
proc pqbackendPID*(conn: PPGconn): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQbackendPID".}
|
||||
proc pqconnectionNeedsPassword*(conn: PPGconn): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQconnectionNeedsPassword".}
|
||||
proc pqconnectionUsedPassword*(conn: PPGconn): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQconnectionUsedPassword".}
|
||||
proc pqclientEncoding*(conn: PPGconn): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQclientEncoding".}
|
||||
proc pqsetClientEncoding*(conn: PPGconn, encoding: cstring): int32{.cdecl,
|
||||
dynlib: dllName, importc: "PQsetClientEncoding".}
|
||||
when defined(USE_SSL):
|
||||
# Get the SSL structure associated with a connection
|
||||
proc pqgetssl*(conn: PPGconn): PSSL{.cdecl, dynlib: dllName,
|
||||
importc: "PQgetssl".}
|
||||
proc pqsetErrorVerbosity*(conn: PPGconn, verbosity: PGVerbosity): PGVerbosity{.
|
||||
cdecl, dynlib: dllName, importc: "PQsetErrorVerbosity".}
|
||||
proc pqtrace*(conn: PPGconn, debug_port: File){.cdecl, dynlib: dllName,
|
||||
importc: "PQtrace".}
|
||||
proc pquntrace*(conn: PPGconn){.cdecl, dynlib: dllName, importc: "PQuntrace".}
|
||||
proc pqsetNoticeReceiver*(conn: PPGconn, theProc: PQnoticeReceiver, arg: pointer): PQnoticeReceiver{.
|
||||
cdecl, dynlib: dllName, importc: "PQsetNoticeReceiver".}
|
||||
proc pqsetNoticeProcessor*(conn: PPGconn, theProc: PQnoticeProcessor,
|
||||
arg: pointer): PQnoticeProcessor{.cdecl,
|
||||
dynlib: dllName, importc: "PQsetNoticeProcessor".}
|
||||
proc pqexec*(conn: PPGconn, query: cstring): PPGresult{.cdecl, dynlib: dllName,
|
||||
importc: "PQexec".}
|
||||
proc pqexecParams*(conn: PPGconn, command: cstring, nParams: int32,
|
||||
paramTypes: POid, paramValues: cstringArray,
|
||||
paramLengths, paramFormats: ptr int32, resultFormat: int32): PPGresult{.
|
||||
cdecl, dynlib: dllName, importc: "PQexecParams".}
|
||||
proc pqprepare*(conn: PPGconn, stmtName, query: cstring, nParams: int32,
|
||||
paramTypes: POid): PPGresult{.cdecl, dynlib: dllName, importc: "PQprepare".}
|
||||
proc pqexecPrepared*(conn: PPGconn, stmtName: cstring, nParams: int32,
|
||||
paramValues: cstringArray,
|
||||
paramLengths, paramFormats: ptr int32, resultFormat: int32): PPGresult{.
|
||||
cdecl, dynlib: dllName, importc: "PQexecPrepared".}
|
||||
proc pqsendQuery*(conn: PPGconn, query: cstring): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQsendQuery".}
|
||||
## See also https://www.postgresql.org/docs/current/libpq-async.html
|
||||
proc pqsendQueryParams*(conn: PPGconn, command: cstring, nParams: int32,
|
||||
paramTypes: POid, paramValues: cstringArray,
|
||||
paramLengths, paramFormats: ptr int32,
|
||||
resultFormat: int32): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQsendQueryParams".}
|
||||
proc pqsendPrepare*(conn: PPGconn, stmtName: cstring, query: cstring,
|
||||
nParams: int32, paramTypes: POid): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQsendPrepare".}
|
||||
proc pqsendQueryPrepared*(conn: PPGconn, stmtName: cstring, nParams: int32,
|
||||
paramValues: cstringArray,
|
||||
paramLengths, paramFormats: ptr int32,
|
||||
resultFormat: int32): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQsendQueryPrepared".}
|
||||
proc pqSetSingleRowMode*(conn: PPGconn): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQsetSingleRowMode".}
|
||||
## See also https://www.postgresql.org/docs/current/libpq-single-row-mode.html
|
||||
proc pqgetResult*(conn: PPGconn): PPGresult{.cdecl, dynlib: dllName,
|
||||
importc: "PQgetResult".}
|
||||
proc pqisBusy*(conn: PPGconn): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQisBusy".}
|
||||
proc pqconsumeInput*(conn: PPGconn): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQconsumeInput".}
|
||||
proc pqnotifies*(conn: PPGconn): PPGNotify{.cdecl, dynlib: dllName,
|
||||
importc: "PQnotifies".}
|
||||
proc pqputCopyData*(conn: PPGconn, buffer: cstring, nbytes: int32): int32{.
|
||||
cdecl, dynlib: dllName, importc: "PQputCopyData".}
|
||||
proc pqputCopyEnd*(conn: PPGconn, errormsg: cstring): int32{.cdecl,
|
||||
dynlib: dllName, importc: "PQputCopyEnd".}
|
||||
proc pqgetCopyData*(conn: PPGconn, buffer: cstringArray, async: int32): int32{.
|
||||
cdecl, dynlib: dllName, importc: "PQgetCopyData".}
|
||||
proc pqgetline*(conn: PPGconn, str: cstring, len: int32): int32{.cdecl,
|
||||
dynlib: dllName, importc: "PQgetline".}
|
||||
proc pqputline*(conn: PPGconn, str: cstring): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQputline".}
|
||||
proc pqgetlineAsync*(conn: PPGconn, buffer: cstring, bufsize: int32): int32{.
|
||||
cdecl, dynlib: dllName, importc: "PQgetlineAsync".}
|
||||
proc pqputnbytes*(conn: PPGconn, buffer: cstring, nbytes: int32): int32{.cdecl,
|
||||
dynlib: dllName, importc: "PQputnbytes".}
|
||||
proc pqendcopy*(conn: PPGconn): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQendcopy".}
|
||||
proc pqsetnonblocking*(conn: PPGconn, arg: int32): int32{.cdecl,
|
||||
dynlib: dllName, importc: "PQsetnonblocking".}
|
||||
proc pqisnonblocking*(conn: PPGconn): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQisnonblocking".}
|
||||
proc pqflush*(conn: PPGconn): int32{.cdecl, dynlib: dllName, importc: "PQflush".}
|
||||
proc pqfn*(conn: PPGconn, fnid: int32, result_buf, result_len: ptr int32,
|
||||
result_is_int: int32, args: PPQArgBlock, nargs: int32): PPGresult{.
|
||||
cdecl, dynlib: dllName, importc: "PQfn".}
|
||||
proc pqresultStatus*(res: PPGresult): ExecStatusType{.cdecl, dynlib: dllName,
|
||||
importc: "PQresultStatus".}
|
||||
proc pqresStatus*(status: ExecStatusType): cstring{.cdecl, dynlib: dllName,
|
||||
importc: "PQresStatus".}
|
||||
proc pqresultErrorMessage*(res: PPGresult): cstring{.cdecl, dynlib: dllName,
|
||||
importc: "PQresultErrorMessage".}
|
||||
proc pqresultErrorField*(res: PPGresult, fieldcode: int32): cstring{.cdecl,
|
||||
dynlib: dllName, importc: "PQresultErrorField".}
|
||||
proc pqntuples*(res: PPGresult): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQntuples".}
|
||||
proc pqnfields*(res: PPGresult): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQnfields".}
|
||||
proc pqbinaryTuples*(res: PPGresult): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQbinaryTuples".}
|
||||
proc pqfname*(res: PPGresult, field_num: int32): cstring{.cdecl,
|
||||
dynlib: dllName, importc: "PQfname".}
|
||||
proc pqfnumber*(res: PPGresult, field_name: cstring): int32{.cdecl,
|
||||
dynlib: dllName, importc: "PQfnumber".}
|
||||
proc pqftable*(res: PPGresult, field_num: int32): Oid{.cdecl, dynlib: dllName,
|
||||
importc: "PQftable".}
|
||||
proc pqftablecol*(res: PPGresult, field_num: int32): int32{.cdecl,
|
||||
dynlib: dllName, importc: "PQftablecol".}
|
||||
proc pqfformat*(res: PPGresult, field_num: int32): int32{.cdecl,
|
||||
dynlib: dllName, importc: "PQfformat".}
|
||||
proc pqftype*(res: PPGresult, field_num: int32): Oid{.cdecl, dynlib: dllName,
|
||||
importc: "PQftype".}
|
||||
proc pqfsize*(res: PPGresult, field_num: int32): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQfsize".}
|
||||
proc pqfmod*(res: PPGresult, field_num: int32): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQfmod".}
|
||||
proc pqcmdStatus*(res: PPGresult): cstring{.cdecl, dynlib: dllName,
|
||||
importc: "PQcmdStatus".}
|
||||
proc pqoidStatus*(res: PPGresult): cstring{.cdecl, dynlib: dllName,
|
||||
importc: "PQoidStatus".}
|
||||
proc pqoidValue*(res: PPGresult): Oid{.cdecl, dynlib: dllName,
|
||||
importc: "PQoidValue".}
|
||||
proc pqcmdTuples*(res: PPGresult): cstring{.cdecl, dynlib: dllName,
|
||||
importc: "PQcmdTuples".}
|
||||
proc pqgetvalue*(res: PPGresult, tup_num: int32, field_num: int32): cstring{.
|
||||
cdecl, dynlib: dllName, importc: "PQgetvalue".}
|
||||
proc pqgetlength*(res: PPGresult, tup_num: int32, field_num: int32): int32{.
|
||||
cdecl, dynlib: dllName, importc: "PQgetlength".}
|
||||
proc pqgetisnull*(res: PPGresult, tup_num: int32, field_num: int32): int32{.
|
||||
cdecl, dynlib: dllName, importc: "PQgetisnull".}
|
||||
proc pqclear*(res: PPGresult){.cdecl, dynlib: dllName, importc: "PQclear".}
|
||||
proc pqfreemem*(p: pointer){.cdecl, dynlib: dllName, importc: "PQfreemem".}
|
||||
proc pqmakeEmptyPGresult*(conn: PPGconn, status: ExecStatusType): PPGresult{.
|
||||
cdecl, dynlib: dllName, importc: "PQmakeEmptyPGresult".}
|
||||
proc pqescapeString*(till, `from`: cstring, len: int): int{.cdecl,
|
||||
dynlib: dllName, importc: "PQescapeString".}
|
||||
proc pqescapeBytea*(bintext: cstring, binlen: int, bytealen: var int): cstring{.
|
||||
cdecl, dynlib: dllName, importc: "PQescapeBytea".}
|
||||
proc pqunescapeBytea*(strtext: cstring, retbuflen: var int): cstring{.cdecl,
|
||||
dynlib: dllName, importc: "PQunescapeBytea".}
|
||||
proc pqprint*(fout: File, res: PPGresult, ps: PPQprintOpt){.cdecl,
|
||||
dynlib: dllName, importc: "PQprint".}
|
||||
proc pqdisplayTuples*(res: PPGresult, fp: File, fillAlign: int32,
|
||||
fieldSep: cstring, printHeader: int32, quiet: int32){.
|
||||
cdecl, dynlib: dllName, importc: "PQdisplayTuples".}
|
||||
proc pqprintTuples*(res: PPGresult, fout: File, printAttName: int32,
|
||||
terseOutput: int32, width: int32){.cdecl, dynlib: dllName,
|
||||
importc: "PQprintTuples".}
|
||||
proc lo_open*(conn: PPGconn, lobjId: Oid, mode: int32): int32{.cdecl,
|
||||
dynlib: dllName, importc: "lo_open".}
|
||||
proc lo_close*(conn: PPGconn, fd: int32): int32{.cdecl, dynlib: dllName,
|
||||
importc: "lo_close".}
|
||||
proc lo_read*(conn: PPGconn, fd: int32, buf: cstring, length: int): int32{.
|
||||
cdecl, dynlib: dllName, importc: "lo_read".}
|
||||
proc lo_write*(conn: PPGconn, fd: int32, buf: cstring, length: int): int32{.
|
||||
cdecl, dynlib: dllName, importc: "lo_write".}
|
||||
proc lo_lseek*(conn: PPGconn, fd: int32, offset: int32, whence: int32): int32{.
|
||||
cdecl, dynlib: dllName, importc: "lo_lseek".}
|
||||
proc lo_creat*(conn: PPGconn, mode: int32): Oid{.cdecl, dynlib: dllName,
|
||||
importc: "lo_creat".}
|
||||
proc lo_tell*(conn: PPGconn, fd: int32): int32{.cdecl, dynlib: dllName,
|
||||
importc: "lo_tell".}
|
||||
proc lo_unlink*(conn: PPGconn, lobjId: Oid): int32{.cdecl, dynlib: dllName,
|
||||
importc: "lo_unlink".}
|
||||
proc lo_import*(conn: PPGconn, filename: cstring): Oid{.cdecl, dynlib: dllName,
|
||||
importc: "lo_import".}
|
||||
proc lo_export*(conn: PPGconn, lobjId: Oid, filename: cstring): int32{.cdecl,
|
||||
dynlib: dllName, importc: "lo_export".}
|
||||
proc pqmblen*(s: cstring, encoding: int32): int32{.cdecl, dynlib: dllName,
|
||||
importc: "PQmblen".}
|
||||
proc pqenv2encoding*(): int32{.cdecl, dynlib: dllName, importc: "PQenv2encoding".}
|
||||
proc pqsetdb(M_PGHOST, M_PGPORT, M_PGOPT, M_PGTTY, M_DBNAME: cstring): PPGconn =
|
||||
result = pqsetdbLogin(M_PGHOST, M_PGPORT, M_PGOPT, M_PGTTY, M_DBNAME, "", "")
|
||||
|
||||
when defined(nimHasStyleChecks):
|
||||
{.pop.}
|
||||
@@ -0,0 +1,263 @@
|
||||
import std/asyncdispatch
|
||||
import std/strutils
|
||||
import std/json
|
||||
import ../../models/database_types
|
||||
import ../../prepared_param
|
||||
import ./sqlite_rdb
|
||||
import ./sqlite_lib
|
||||
|
||||
|
||||
proc query*(db:PSqlite3, query:string, args:seq[string], timeout:int):Future[(seq[Row], DbRows)] {.async.} =
|
||||
assert(not db.isNil, "Database not connected.")
|
||||
sleepAsync(0).await
|
||||
var dbRows: DbRows
|
||||
var rows = newSeq[seq[string]]()
|
||||
for row in db.instantRows(dbRows, query, args):
|
||||
var columns = newSeq[string](row.len)
|
||||
for i in 0..row.len()-1:
|
||||
columns[i] = row[i]
|
||||
rows.add(columns)
|
||||
return (rows, dbRows)
|
||||
|
||||
|
||||
proc queryPlain*(db:PSqlite3, query:string, args:seq[string], timeout:int):Future[seq[Row]] {.async.} =
|
||||
assert(not db.isNil, "Database not connected.")
|
||||
sleepAsync(0).await
|
||||
var rows = newSeq[seq[string]]()
|
||||
for row in db.instantRowsPlain(query, args):
|
||||
var columns = newSeq[string](row.len)
|
||||
for i in 0..row.len()-1:
|
||||
columns[i] = row[i]
|
||||
rows.add(columns)
|
||||
return rows
|
||||
|
||||
|
||||
proc getColumnTypes*(db:PSqlite3, query: string):Future[seq[(string, string)]] {.async.} =
|
||||
sleepAsync(0).await
|
||||
var dbRows: DbRows
|
||||
var columns = newSeq[(string, string)]()
|
||||
for row in db.instantRows(dbRows, query, newSeq[string]()):
|
||||
columns.add((row[1], row[2]))
|
||||
return columns
|
||||
|
||||
|
||||
proc exec*(db:PSqlite3, query: string, args: JsonNode, columns:seq[(string, string)], timeout:int) {.async.} =
|
||||
## args is `JArray`
|
||||
assert(not db.isNil, "Database not connected.")
|
||||
sleepAsync(0).await
|
||||
# var q = dbFormat(query, strArges)
|
||||
var stmt: PStmt
|
||||
var res:bool
|
||||
if prepare_v2(db, query.cstring, query.len.cint, stmt, nil) == SQLITE_OK:
|
||||
var argCount = 1
|
||||
for arg in args.items:
|
||||
defer: argCount.inc()
|
||||
case arg["value"].kind
|
||||
of JBool:
|
||||
let value = arg["value"].getBool
|
||||
let insertVal = if value:1 else: 0
|
||||
discard bind_int64(stmt, argCount.int32, insertVal)
|
||||
of JInt:
|
||||
let value = arg["value"].getInt
|
||||
discard bind_int64(stmt, argCount.int32, value)
|
||||
of JFloat:
|
||||
let value = arg["value"].getFloat
|
||||
discard bind_double(stmt, argCount.int32, value.float64)
|
||||
of JNull:
|
||||
discard bind_null(stmt, argCount.int32)
|
||||
of JObject, JArray:
|
||||
let value = arg["value"].pretty
|
||||
discard bind_text(stmt, argCount.int32, value.cstring, value.len.int32, SQLITE_TRANSIENT)
|
||||
of JString:
|
||||
for column in columns:
|
||||
let columnName = column[0]
|
||||
let columnTyp = column[1]
|
||||
if columnName == arg["key"].getStr:
|
||||
defer: break
|
||||
if columnTyp == "BLOB":
|
||||
let value = arg["value"].getStr
|
||||
var blobVal:seq[byte]
|
||||
for c in value:
|
||||
blobVal.add(c.byte)
|
||||
discard bind_blob(stmt, argCount.int32, blobVal[0].unsafeAddr, blobVal.len.int32, SQLITE_TRANSIENT)
|
||||
else:
|
||||
let value = arg["value"].getStr
|
||||
discard bind_text(stmt, argCount.int32, value.cstring, value.len.int32, SQLITE_TRANSIENT)
|
||||
|
||||
let x = step(stmt)
|
||||
if x in {SQLITE_DONE, SQLITE_ROW}:
|
||||
res = finalize(stmt) == SQLITE_OK
|
||||
else:
|
||||
discard finalize(stmt)
|
||||
res = false
|
||||
if not res:
|
||||
dbError(db)
|
||||
|
||||
|
||||
proc exec*(db:PSqlite3, query: string, args: JsonNode, timeout:int) {.async.} =
|
||||
## used for rdb.raw().exec()
|
||||
## args are `JArray`
|
||||
assert(not db.isNil, "Database not connected.")
|
||||
sleepAsync(0).await
|
||||
var stmt: PStmt
|
||||
var res:bool
|
||||
if prepare_v2(db, query.cstring, query.len.cint, stmt, nil) == SQLITE_OK:
|
||||
var argCount = 1
|
||||
for arg in args.items:
|
||||
defer: argCount.inc()
|
||||
case arg.kind
|
||||
of JBool:
|
||||
let value = arg.getBool
|
||||
let insertVal = if value:1 else: 0
|
||||
discard bind_int64(stmt, argCount.int32, insertVal)
|
||||
of JInt:
|
||||
let value = arg.getInt
|
||||
discard bind_int64(stmt, argCount.int32, value)
|
||||
of JFloat:
|
||||
let value = arg.getFloat
|
||||
discard bind_double(stmt, argCount.int32, value.float64)
|
||||
of JNull:
|
||||
discard bind_null(stmt, argCount.int32)
|
||||
of JObject, JArray:
|
||||
let value = arg.pretty
|
||||
discard bind_text(stmt, argCount.int32, value.cstring, value.len.int32, SQLITE_TRANSIENT)
|
||||
of JString:
|
||||
let value = arg.getStr
|
||||
discard bind_text(stmt, argCount.int32, value.cstring, value.len.int32, SQLITE_TRANSIENT)
|
||||
|
||||
let x = step(stmt)
|
||||
if x in {SQLITE_DONE, SQLITE_ROW}:
|
||||
res = finalize(stmt) == SQLITE_OK
|
||||
else:
|
||||
discard finalize(stmt)
|
||||
res = false
|
||||
if not res:
|
||||
dbError(db)
|
||||
|
||||
|
||||
proc exec*(db:PSqlite3, query: string, args: seq[string], timeout:int) {.async.} =
|
||||
## Not used anymore
|
||||
assert(not db.isNil, "Database not connected.")
|
||||
sleepAsync(0).await
|
||||
var q = dbFormat(query, args)
|
||||
var stmt: PStmt
|
||||
var res:bool
|
||||
if prepare_v2(db, q.cstring, q.len.cint, stmt, nil) == SQLITE_OK:
|
||||
let x = step(stmt)
|
||||
if x in {SQLITE_DONE, SQLITE_ROW}:
|
||||
res = finalize(stmt) == SQLITE_OK
|
||||
else:
|
||||
discard finalize(stmt)
|
||||
res = false
|
||||
if not res:
|
||||
dbError(db)
|
||||
|
||||
|
||||
proc getColumns*(db:PSqlite3, query:string, args:seq[string], timeout:int):Future[seq[string]] {.async.} =
|
||||
assert(not db.isNil, "Database not connected.")
|
||||
var dbRows: DbRows
|
||||
return db.getColumns(dbRows, query, args)
|
||||
|
||||
|
||||
proc prepare*(db:PSqlite3, query:string, timeout:int):Future[PStmt] {.async.} =
|
||||
if prepare_v2(db, query, query.len.cint, result, nil) != SQLITE_OK:
|
||||
discard finalize(result)
|
||||
dbError(db)
|
||||
|
||||
|
||||
proc bindPreparedParams(db: PSqlite3, stmt: PStmt, args: seq[PreparedParam]) =
|
||||
if reset(stmt) != SQLITE_OK:
|
||||
dbError(db)
|
||||
if clear_bindings(stmt) != SQLITE_OK:
|
||||
dbError(db)
|
||||
|
||||
for i, arg in args:
|
||||
let paramIdx = i.int32 + 1
|
||||
if arg.isNull:
|
||||
if bind_null(stmt, paramIdx) != SQLITE_OK:
|
||||
dbError(db)
|
||||
else:
|
||||
if bind_text(stmt, paramIdx, arg.value.cstring, arg.value.len.int32, SQLITE_TRANSIENT) != SQLITE_OK:
|
||||
dbError(db)
|
||||
|
||||
|
||||
proc preparedQueryReuse*(db: PSqlite3, stmt: PStmt, args: seq[PreparedParam], timeout: int,
|
||||
cachedColumns: DbColumns): Future[(seq[Row], DbRows)] {.async.} =
|
||||
assert(not db.isNil, "Database not connected.")
|
||||
sleepAsync(0).await
|
||||
bindPreparedParams(db, stmt, args)
|
||||
defer:
|
||||
discard clear_bindings(stmt)
|
||||
|
||||
var dbRows: DbRows
|
||||
var rows = newSeq[seq[string]]()
|
||||
|
||||
while true:
|
||||
let stepRes = step(stmt)
|
||||
if stepRes == SQLITE_ROW:
|
||||
var columns = cachedColumns
|
||||
setColumnsRuntimeTypes(columns, stmt)
|
||||
dbRows.add(columns)
|
||||
var row = newSeq[string](int(column_count(stmt)))
|
||||
for i in 0 ..< row.len:
|
||||
let text = column_text(stmt, i.int32)
|
||||
if text.isNil:
|
||||
row[i] = ""
|
||||
else:
|
||||
row[i] = $text
|
||||
rows.add(row)
|
||||
continue
|
||||
if stepRes == SQLITE_DONE:
|
||||
break
|
||||
dbError(db)
|
||||
|
||||
return (rows, dbRows)
|
||||
|
||||
|
||||
proc preparedExecReuse*(db: PSqlite3, stmt: PStmt, args: seq[PreparedParam], timeout: int) {.async.} =
|
||||
assert(not db.isNil, "Database not connected.")
|
||||
sleepAsync(0).await
|
||||
bindPreparedParams(db, stmt, args)
|
||||
defer:
|
||||
discard clear_bindings(stmt)
|
||||
|
||||
var stepRes = step(stmt)
|
||||
while stepRes == SQLITE_ROW:
|
||||
stepRes = step(stmt)
|
||||
if stepRes != SQLITE_DONE:
|
||||
dbError(db)
|
||||
|
||||
|
||||
proc preparedQuery*(db:PSqlite3, args:seq[string] = @[], sqliteStmt:PStmt):Future[(seq[Row], DbRows)] {.async.} =
|
||||
# bind params
|
||||
for i, row in args:
|
||||
sqliteStmt.bindParam(i+1, row)
|
||||
# run query
|
||||
assert(not db.isNil, "Database not connected.")
|
||||
sleepAsync(0).await
|
||||
var dbRows: DbRows
|
||||
var rows = newSeq[seq[string]]()
|
||||
for row in db.instantRows(dbRows, sqliteStmt):
|
||||
await sleepAsync(0)
|
||||
var columns = newSeq[string](row.len)
|
||||
for i in 0..row.len()-1:
|
||||
columns[i] = row[i]
|
||||
rows.add(columns)
|
||||
return (rows, dbRows)
|
||||
|
||||
|
||||
proc preparedExec*(db:PSqlite3, args:seq[string] = @[], sqliteStmt:PStmt) {.async.} =
|
||||
# bind params
|
||||
for i, row in args:
|
||||
sqliteStmt.bindParam(i+1, row)
|
||||
# run query
|
||||
sleepAsync(0).await
|
||||
let x = step(sqliteStmt)
|
||||
var res:bool
|
||||
if x in {SQLITE_DONE, SQLITE_ROW}:
|
||||
res = true
|
||||
else:
|
||||
res = false
|
||||
discard finalize(sqliteStmt)
|
||||
if not res:
|
||||
dbError(db)
|
||||
@@ -0,0 +1,200 @@
|
||||
import std/macros
|
||||
import ../../error
|
||||
import ../../models/database_types
|
||||
import ./sqlite_rdb
|
||||
|
||||
|
||||
proc dbQuote(s: string): string =
|
||||
## Escapes the `'` (single quote) char to `''`.
|
||||
## Because single quote is used for defining `VARCHAR` in SQL.
|
||||
runnableExamples:
|
||||
doAssert dbQuote("'") == "''''"
|
||||
doAssert dbQuote("A Foobar's pen.") == "'A Foobar''s pen.'"
|
||||
|
||||
if s == "null":
|
||||
return "NULL"
|
||||
result = newStringOfCap(s.len * 2 + 2)
|
||||
result.add('\'')
|
||||
for c in items(s):
|
||||
if c == '\'': add(result, "''")
|
||||
else: add(result, c)
|
||||
add(result, '\'')
|
||||
|
||||
proc sqliteQuoteIdent*(name: string): string =
|
||||
## `PRAGMA` 等で使う SQLite の二重引用符識別子(内部の `"` は `""` にエスケープ)。
|
||||
result = newStringOfCap(name.len + 2)
|
||||
result.add('"')
|
||||
for c in name:
|
||||
if c == '"':
|
||||
result.add("\"\"")
|
||||
else:
|
||||
result.add(c)
|
||||
result.add('"')
|
||||
|
||||
proc dbError*(db: PSqlite3) {.noreturn.} =
|
||||
## Raises a `DbError` exception.
|
||||
##
|
||||
## **Examples:**
|
||||
##
|
||||
## .. code-block:: Nim
|
||||
##
|
||||
## let db = open("mytest.db", "", "", "")
|
||||
## if not db.tryExec(sql"SELECT * FROM not_exist_table"):
|
||||
## dbError(db)
|
||||
## db.close()
|
||||
var e: ref DbError
|
||||
new(e)
|
||||
e.msg = $errmsg(db)
|
||||
raise e
|
||||
|
||||
proc dbFormat*(formatstr: string, args: varargs[string]): string =
|
||||
var a = 0
|
||||
result = newStringOfCap(formatstr.len + args.len * 8)
|
||||
var segStart = 0
|
||||
for j in 0 ..< formatstr.len:
|
||||
if formatstr[j] == '?':
|
||||
if j > segStart:
|
||||
result.add(formatstr[segStart ..< j])
|
||||
result.add(dbQuote(args[a]))
|
||||
inc(a)
|
||||
segStart = j + 1
|
||||
if segStart < formatstr.len:
|
||||
result.add(formatstr[segStart ..< formatstr.len])
|
||||
|
||||
proc setupQuery(db: PSqlite3, query: string, args: varargs[string]): PStmt =
|
||||
assert(not db.isNil, "Database not connected.")
|
||||
var q = dbFormat(query, args)
|
||||
if prepare_v2(db, q.cstring, q.len.cint, result, nil) != SQLITE_OK: dbError(db)
|
||||
|
||||
proc toTypeKind(t: var DbType; x: int32) =
|
||||
case x
|
||||
of SQLITE_INTEGER:
|
||||
t.kind = dbInt
|
||||
t.size = 8
|
||||
of SQLITE_FLOAT:
|
||||
t.kind = dbFloat
|
||||
t.size = 8
|
||||
of SQLITE_BLOB: t.kind = dbBlob
|
||||
of SQLITE_NULL: t.kind = dbNull
|
||||
of SQLITE_TEXT: t.kind = dbVarchar
|
||||
else: t.kind = dbUnknown
|
||||
|
||||
proc setColumnsStaticMeta*(columns: var DbColumns; x: PStmt) =
|
||||
## ステップ前でも列名・宣言型・テーブル名は取得できる(行に依存しない)。
|
||||
let L = column_count(x)
|
||||
setLen(columns, L.int)
|
||||
for i in 0'i32 ..< L:
|
||||
columns[i].name = $column_name(x, i)
|
||||
columns[i].typ.name = $column_decltype(x, i)
|
||||
columns[i].tableName = $column_table_name(x, i)
|
||||
|
||||
proc setColumnsRuntimeTypes*(columns: var DbColumns; x: PStmt) =
|
||||
## 行ごとに変わりうるのは `column_type` のみ。
|
||||
let L = column_count(x)
|
||||
for i in 0'i32 ..< L:
|
||||
toTypeKind(columns[i].typ, column_type(x, i))
|
||||
|
||||
iterator instantRows*(db: PSqlite3; dbRows: var DbRows; query: string, args: seq[string]): InstantRow
|
||||
{.tags: [ReadDbEffect].} =
|
||||
## Similar to `instantRows iterator <#instantRows.i,DbConn,SqlQuery,varargs[string,]>`_,
|
||||
## but sets information about columns to `columns`.
|
||||
##
|
||||
## **Examples:**
|
||||
##
|
||||
## .. code-block:: Nim
|
||||
##
|
||||
## let db = open("mytest.db", "", "", "")
|
||||
##
|
||||
## # Records of my_table:
|
||||
## # | id | name |
|
||||
## # |----|----------|
|
||||
## # | 1 | item#1 |
|
||||
## # | 2 | item#2 |
|
||||
##
|
||||
## var columns: DbColumns
|
||||
## for row in db.instantRows(columns, sql"SELECT * FROM my_table"):
|
||||
## discard
|
||||
## echo columns[0]
|
||||
##
|
||||
## # Output:
|
||||
## # (name: "id", tableName: "my_table", typ: (kind: dbNull,
|
||||
## # notNull: false, name: "INTEGER", size: 0, maxReprLen: 0, precision: 0,
|
||||
## # scale: 0, min: 0, max: 0, validValues: @[]), primaryKey: false,
|
||||
## # foreignKey: false)
|
||||
##
|
||||
## db.close()
|
||||
var stmt = setupQuery(db, query, args)
|
||||
try:
|
||||
var columns: DbColumns
|
||||
setColumnsStaticMeta(columns, stmt)
|
||||
while step(stmt) == SQLITE_ROW:
|
||||
setColumnsRuntimeTypes(columns, stmt)
|
||||
dbRows.add(columns)
|
||||
yield stmt
|
||||
finally:
|
||||
if finalize(stmt) != SQLITE_OK: dbError(db)
|
||||
|
||||
iterator instantRowsPlain*(db: PSqlite3; query: string, args: seq[string]): InstantRow
|
||||
{.tags: [ReadDbEffect].} =
|
||||
var stmt = setupQuery(db, query, args)
|
||||
try:
|
||||
while step(stmt) == SQLITE_ROW:
|
||||
yield stmt
|
||||
finally:
|
||||
if finalize(stmt) != SQLITE_OK: dbError(db)
|
||||
|
||||
iterator instantRows*(db: PSqlite3, dbRows: var DbRows, sqliteStmt: PStmt): InstantRow
|
||||
{.tags: [ReadDbEffect,WriteDbEffect].} =
|
||||
var sqliteStmt = sqliteStmt
|
||||
try:
|
||||
var columns: DbColumns
|
||||
setColumnsStaticMeta(columns, sqliteStmt)
|
||||
while step(sqliteStmt) == SQLITE_ROW:
|
||||
setColumnsRuntimeTypes(columns, sqliteStmt)
|
||||
dbRows.add(columns)
|
||||
yield sqliteStmt
|
||||
finally:
|
||||
if finalize(sqliteStmt) != SQLITE_OK: dbError(db)
|
||||
|
||||
proc getColumns*(db: PSqlite3; dbRows: var DbRows; query: string, args: seq[string]):seq[string] =
|
||||
var stmt = setupQuery(db, query, args)
|
||||
try:
|
||||
var i:int32 = 0
|
||||
while true:
|
||||
let name = column_name(stmt, i)
|
||||
if name.len == 0: break
|
||||
result.add($name)
|
||||
i.inc()
|
||||
finally:
|
||||
if finalize(stmt) != SQLITE_OK: dbError(db)
|
||||
|
||||
return result
|
||||
|
||||
proc len*(row: InstantRow): int32 {.inline.} =
|
||||
## Returns number of columns in a row.
|
||||
##
|
||||
## See also:
|
||||
## * `instantRows iterator <#instantRows.i,DbConn,SqlQuery,varargs[string,]>`_
|
||||
## example code
|
||||
column_count(row)
|
||||
|
||||
proc `[]`*(row: InstantRow, col: int32): string {.inline.} =
|
||||
## Returns text for given column of the row.
|
||||
##
|
||||
## See also:
|
||||
## * `instantRows iterator <#instantRows.i,DbConn,SqlQuery,varargs[string,]>`_
|
||||
## example code
|
||||
$column_text(row, col)
|
||||
|
||||
template dbBindParamError*(paramIdx: int, val: varargs[untyped]) =
|
||||
## Raises a `DbError` exception.
|
||||
var e: ref DbError
|
||||
new(e)
|
||||
e.msg = "error binding param in position " & $paramIdx
|
||||
raise e
|
||||
|
||||
proc bindParam*(ps: PStmt, paramIdx: int, val: string, copy = true) =
|
||||
## Binds a string to the specified paramIndex.
|
||||
## if copy is true then SQLite makes its own private copy of the data immediately
|
||||
if bind_text(ps, paramIdx.int32, val.cstring, val.len.int32, if copy: SQLITE_TRANSIENT else: SQLITE_STATIC) != SQLITE_OK:
|
||||
dbBindParamError(paramIdx, val)
|
||||
@@ -0,0 +1,384 @@
|
||||
when defined(nimHasStyleChecks):
|
||||
{.push styleChecks: off.}
|
||||
|
||||
when defined(windows):
|
||||
when defined(nimOldDlls):
|
||||
const Lib = "sqlite3.dll"
|
||||
elif defined(cpu64):
|
||||
const Lib = "sqlite3_64.dll"
|
||||
else:
|
||||
const Lib = "sqlite3_32.dll"
|
||||
elif defined(macosx):
|
||||
const
|
||||
Lib = "libsqlite3(|.0).dylib"
|
||||
else:
|
||||
const
|
||||
Lib = "libsqlite3.so(|.0)"
|
||||
|
||||
when defined(staticSqlite):
|
||||
{.pragma: mylib.}
|
||||
{.compile("sqlite3.c", "-O3").}
|
||||
else:
|
||||
{.pragma: mylib, dynlib: Lib.}
|
||||
|
||||
const
|
||||
SQLITE_INTEGER* = 1
|
||||
SQLITE_FLOAT* = 2
|
||||
SQLITE_BLOB* = 4
|
||||
SQLITE_NULL* = 5
|
||||
SQLITE_TEXT* = 3
|
||||
SQLITE_UTF8* = 1
|
||||
SQLITE_UTF16LE* = 2
|
||||
SQLITE_UTF16BE* = 3 # Use native byte order
|
||||
SQLITE_UTF16* = 4 # sqlite3_create_function only
|
||||
SQLITE_ANY* = 5 #sqlite_exec return values
|
||||
SQLITE_OK* = 0
|
||||
SQLITE_ERROR* = 1 # SQL error or missing database
|
||||
SQLITE_INTERNAL* = 2 # An internal logic error in SQLite
|
||||
SQLITE_PERM* = 3 # Access permission denied
|
||||
SQLITE_ABORT* = 4 # Callback routine requested an abort
|
||||
SQLITE_BUSY* = 5 # The database file is locked
|
||||
SQLITE_LOCKED* = 6 # A table in the database is locked
|
||||
SQLITE_NOMEM* = 7 # A malloc() failed
|
||||
SQLITE_READONLY* = 8 # Attempt to write a readonly database
|
||||
SQLITE_INTERRUPT* = 9 # Operation terminated by sqlite3_interrupt()
|
||||
SQLITE_IOERR* = 10 # Some kind of disk I/O error occurred
|
||||
SQLITE_CORRUPT* = 11 # The database disk image is malformed
|
||||
SQLITE_NOTFOUND* = 12 # (Internal Only) Table or record not found
|
||||
SQLITE_FULL* = 13 # Insertion failed because database is full
|
||||
SQLITE_CANTOPEN* = 14 # Unable to open the database file
|
||||
SQLITE_PROTOCOL* = 15 # Database lock protocol error
|
||||
SQLITE_EMPTY* = 16 # Database is empty
|
||||
SQLITE_SCHEMA* = 17 # The database schema changed
|
||||
SQLITE_TOOBIG* = 18 # Too much data for one row of a table
|
||||
SQLITE_CONSTRAINT* = 19 # Abort due to constraint violation
|
||||
SQLITE_MISMATCH* = 20 # Data type mismatch
|
||||
SQLITE_MISUSE* = 21 # Library used incorrectly
|
||||
SQLITE_NOLFS* = 22 # Uses OS features not supported on host
|
||||
SQLITE_AUTH* = 23 # Authorization denied
|
||||
SQLITE_FORMAT* = 24 # Auxiliary database format error
|
||||
SQLITE_RANGE* = 25 # 2nd parameter to sqlite3_bind out of range
|
||||
SQLITE_NOTADB* = 26 # File opened that is not a database file
|
||||
SQLITE_ROW* = 100 # sqlite3_step() has another row ready
|
||||
SQLITE_DONE* = 101 # sqlite3_step() has finished executing
|
||||
SQLITE_COPY* = 0
|
||||
SQLITE_CREATE_INDEX* = 1
|
||||
SQLITE_CREATE_TABLE* = 2
|
||||
SQLITE_CREATE_TEMP_INDEX* = 3
|
||||
SQLITE_CREATE_TEMP_TABLE* = 4
|
||||
SQLITE_CREATE_TEMP_TRIGGER* = 5
|
||||
SQLITE_CREATE_TEMP_VIEW* = 6
|
||||
SQLITE_CREATE_TRIGGER* = 7
|
||||
SQLITE_CREATE_VIEW* = 8
|
||||
SQLITE_DELETE* = 9
|
||||
SQLITE_DROP_INDEX* = 10
|
||||
SQLITE_DROP_TABLE* = 11
|
||||
SQLITE_DROP_TEMP_INDEX* = 12
|
||||
SQLITE_DROP_TEMP_TABLE* = 13
|
||||
SQLITE_DROP_TEMP_TRIGGER* = 14
|
||||
SQLITE_DROP_TEMP_VIEW* = 15
|
||||
SQLITE_DROP_TRIGGER* = 16
|
||||
SQLITE_DROP_VIEW* = 17
|
||||
SQLITE_INSERT* = 18
|
||||
SQLITE_PRAGMA* = 19
|
||||
SQLITE_READ* = 20
|
||||
SQLITE_SELECT* = 21
|
||||
SQLITE_TRANSACTION* = 22
|
||||
SQLITE_UPDATE* = 23
|
||||
SQLITE_ATTACH* = 24
|
||||
SQLITE_DETACH* = 25
|
||||
SQLITE_ALTER_TABLE* = 26
|
||||
SQLITE_REINDEX* = 27
|
||||
SQLITE_DENY* = 1
|
||||
SQLITE_IGNORE* = 2 # Original from sqlite3.h:
|
||||
#define SQLITE_STATIC ((void(*)(void *))0)
|
||||
#define SQLITE_TRANSIENT ((void(*)(void *))-1)
|
||||
SQLITE_DETERMINISTIC* = 0x800
|
||||
|
||||
type
|
||||
Sqlite3 {.pure, final.} = object
|
||||
PSqlite3* = ptr Sqlite3
|
||||
PPSqlite3* = ptr PSqlite3
|
||||
Sqlite3_Backup {.pure, final.} = object
|
||||
PSqlite3_Backup* = ptr Sqlite3_Backup
|
||||
PPSqlite3_Backup* = ptr PSqlite3_Backup
|
||||
Context{.pure, final.} = object
|
||||
Pcontext* = ptr Context
|
||||
TStmt{.pure, final.} = object
|
||||
PStmt* = ptr TStmt
|
||||
InstantRow* = PStmt ## A handle that can be used to get a row's column
|
||||
Value{.pure, final.} = object
|
||||
PValue* = ptr Value
|
||||
PValueArg* = array[0..127, PValue]
|
||||
|
||||
Callback* = proc (para1: pointer, para2: int32, para3,
|
||||
para4: cstringArray): int32{.cdecl.}
|
||||
Tbind_destructor_func* = proc (para1: pointer){.cdecl, tags: [], gcsafe.}
|
||||
Create_function_step_func* = proc (para1: Pcontext, para2: int32,
|
||||
para3: PValueArg){.cdecl.}
|
||||
Create_function_func_func* = proc (para1: Pcontext, para2: int32,
|
||||
para3: PValueArg){.cdecl.}
|
||||
Create_function_final_func* = proc (para1: Pcontext){.cdecl.}
|
||||
Result_func* = proc (para1: pointer){.cdecl.}
|
||||
Create_collation_func* = proc (para1: pointer, para2: int32, para3: pointer,
|
||||
para4: int32, para5: pointer): int32{.cdecl.}
|
||||
Collation_needed_func* = proc (para1: pointer, para2: PSqlite3, eTextRep: int32,
|
||||
para4: cstring){.cdecl.}
|
||||
|
||||
const
|
||||
SQLITE_STATIC* = nil
|
||||
SQLITE_TRANSIENT* = cast[Tbind_destructor_func](-1)
|
||||
|
||||
proc close*(para1: PSqlite3): int32{.cdecl, mylib, importc: "sqlite3_close".}
|
||||
proc exec*(para1: PSqlite3, sql: cstring, para3: Callback, para4: pointer,
|
||||
errmsg: var cstring): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_exec".}
|
||||
proc last_insert_rowid*(para1: PSqlite3): int64{.cdecl, mylib,
|
||||
importc: "sqlite3_last_insert_rowid".}
|
||||
proc changes*(para1: PSqlite3): int32{.cdecl, mylib, importc: "sqlite3_changes".}
|
||||
proc total_changes*(para1: PSqlite3): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_total_changes".}
|
||||
proc interrupt*(para1: PSqlite3){.cdecl, mylib, importc: "sqlite3_interrupt".}
|
||||
proc complete*(sql: cstring): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_complete".}
|
||||
proc complete16*(sql: pointer): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_complete16".}
|
||||
proc busy_handler*(para1: PSqlite3,
|
||||
para2: proc (para1: pointer, para2: int32): int32{.cdecl.},
|
||||
para3: pointer): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_busy_handler".}
|
||||
proc busy_timeout*(para1: PSqlite3, ms: int32): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_busy_timeout".}
|
||||
proc get_table*(para1: PSqlite3, sql: cstring, resultp: var cstringArray,
|
||||
nrow, ncolumn: var cint, errmsg: ptr cstring): int32{.cdecl,
|
||||
mylib, importc: "sqlite3_get_table".}
|
||||
proc free_table*(result: cstringArray){.cdecl, mylib,
|
||||
importc: "sqlite3_free_table".}
|
||||
# Todo: see how translate sqlite3_mprintf, sqlite3_vmprintf, sqlite3_snprintf
|
||||
# function sqlite3_mprintf(_para1:Pchar; args:array of const):Pchar;cdecl; external Sqlite3Lib name 'sqlite3_mprintf';
|
||||
proc mprintf*(para1: cstring): cstring{.cdecl, varargs, mylib,
|
||||
importc: "sqlite3_mprintf".}
|
||||
#function sqlite3_vmprintf(_para1:Pchar; _para2:va_list):Pchar;cdecl; external Sqlite3Lib name 'sqlite3_vmprintf';
|
||||
proc free*(z: cstring){.cdecl, mylib, importc: "sqlite3_free".}
|
||||
#function sqlite3_snprintf(_para1:longint; _para2:Pchar; _para3:Pchar; args:array of const):Pchar;cdecl; external Sqlite3Lib name 'sqlite3_snprintf';
|
||||
proc snprintf*(para1: int32, para2: cstring, para3: cstring): cstring{.cdecl,
|
||||
mylib, varargs, importc: "sqlite3_snprintf".}
|
||||
proc set_authorizer*(para1: PSqlite3, xAuth: proc (para1: pointer, para2: int32,
|
||||
para3: cstring, para4: cstring, para5: cstring, para6: cstring): int32{.
|
||||
cdecl.}, pUserData: pointer): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_set_authorizer".}
|
||||
proc trace*(para1: PSqlite3, xTrace: proc (para1: pointer, para2: cstring){.cdecl.},
|
||||
para3: pointer): pointer{.cdecl, mylib,
|
||||
importc: "sqlite3_trace".}
|
||||
proc progress_handler*(para1: PSqlite3, para2: int32,
|
||||
para3: proc (para1: pointer): int32{.cdecl.},
|
||||
para4: pointer){.cdecl, mylib,
|
||||
importc: "sqlite3_progress_handler".}
|
||||
proc commit_hook*(para1: PSqlite3, para2: proc (para1: pointer): int32{.cdecl.},
|
||||
para3: pointer): pointer{.cdecl, mylib,
|
||||
importc: "sqlite3_commit_hook".}
|
||||
proc open*(filename: cstring, ppDb: var PSqlite3): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_open".}
|
||||
proc open16*(filename: pointer, ppDb: var PSqlite3): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_open16".}
|
||||
proc errcode*(db: PSqlite3): int32{.cdecl, mylib, importc: "sqlite3_errcode".}
|
||||
proc errmsg*(para1: PSqlite3): cstring{.cdecl, mylib, importc: "sqlite3_errmsg".}
|
||||
proc errmsg16*(para1: PSqlite3): pointer{.cdecl, mylib,
|
||||
importc: "sqlite3_errmsg16".}
|
||||
proc prepare*(db: PSqlite3, zSql: cstring, nBytes: int32, ppStmt: var PStmt,
|
||||
pzTail: ptr cstring): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_prepare".}
|
||||
|
||||
proc prepare_v2*(db: PSqlite3, zSql: cstring, nByte: cint, ppStmt: var PStmt,
|
||||
pzTail: ptr cstring): cint {.
|
||||
importc: "sqlite3_prepare_v2", cdecl, mylib.}
|
||||
|
||||
proc prepare16*(db: PSqlite3, zSql: pointer, nBytes: int32, ppStmt: var PStmt,
|
||||
pzTail: var pointer): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_prepare16".}
|
||||
proc bind_blob*(para1: PStmt, para2: int32, para3: pointer, n: int32,
|
||||
para5: Tbind_destructor_func): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_bind_blob".}
|
||||
proc bind_double*(para1: PStmt, para2: int32, para3: float64): int32{.cdecl,
|
||||
mylib, importc: "sqlite3_bind_double".}
|
||||
proc bind_int*(para1: PStmt, para2: int32, para3: int32): int32{.cdecl,
|
||||
mylib, importc: "sqlite3_bind_int".}
|
||||
proc bind_int64*(para1: PStmt, para2: int32, para3: int64): int32{.cdecl,
|
||||
mylib, importc: "sqlite3_bind_int64".}
|
||||
proc bind_null*(para1: PStmt, para2: int32): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_bind_null".}
|
||||
proc bind_text*(para1: PStmt, para2: int32, para3: cstring, n: int32,
|
||||
para5: Tbind_destructor_func): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_bind_text".}
|
||||
proc bind_text16*(para1: PStmt, para2: int32, para3: pointer, para4: int32,
|
||||
para5: Tbind_destructor_func): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_bind_text16".}
|
||||
#function sqlite3_bind_value(_para1:Psqlite3_stmt; _para2:longint; _para3:Psqlite3_value):longint;cdecl; external Sqlite3Lib name 'sqlite3_bind_value';
|
||||
#These overloaded functions were introduced to allow the use of SQLITE_STATIC and SQLITE_TRANSIENT
|
||||
#It's the c world man ;-)
|
||||
proc bind_blob*(para1: PStmt, para2: int32, para3: pointer, n: int32,
|
||||
para5: int32): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_bind_blob".}
|
||||
proc bind_text*(para1: PStmt, para2: int32, para3: cstring, n: int32,
|
||||
para5: int32): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_bind_text".}
|
||||
proc bind_text16*(para1: PStmt, para2: int32, para3: pointer, para4: int32,
|
||||
para5: int32): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_bind_text16".}
|
||||
proc bind_parameter_count*(para1: PStmt): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_bind_parameter_count".}
|
||||
proc bind_parameter_name*(para1: PStmt, para2: int32): cstring{.cdecl,
|
||||
mylib, importc: "sqlite3_bind_parameter_name".}
|
||||
proc bind_parameter_index*(para1: PStmt, zName: cstring): int32{.cdecl,
|
||||
mylib, importc: "sqlite3_bind_parameter_index".}
|
||||
proc clear_bindings*(para1: PStmt): int32 {.cdecl,
|
||||
mylib, importc: "sqlite3_clear_bindings".}
|
||||
proc column_count*(PStmt: PStmt): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_column_count".}
|
||||
proc column_name*(para1: PStmt, para2: int32): cstring{.cdecl, mylib,
|
||||
importc: "sqlite3_column_name".}
|
||||
proc column_table_name*(para1: PStmt; para2: int32): cstring{.cdecl, mylib,
|
||||
importc: "sqlite3_column_table_name".}
|
||||
proc column_name16*(para1: PStmt, para2: int32): pointer{.cdecl, mylib,
|
||||
importc: "sqlite3_column_name16".}
|
||||
proc column_decltype*(para1: PStmt, i: int32): cstring{.cdecl, mylib,
|
||||
importc: "sqlite3_column_decltype".}
|
||||
proc column_decltype16*(para1: PStmt, para2: int32): pointer{.cdecl,
|
||||
mylib, importc: "sqlite3_column_decltype16".}
|
||||
proc step*(para1: PStmt): int32{.cdecl, mylib, importc: "sqlite3_step".}
|
||||
proc data_count*(PStmt: PStmt): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_data_count".}
|
||||
proc column_blob*(para1: PStmt, iCol: int32): pointer{.cdecl, mylib,
|
||||
importc: "sqlite3_column_blob".}
|
||||
proc column_bytes*(para1: PStmt, iCol: int32): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_column_bytes".}
|
||||
proc column_bytes16*(para1: PStmt, iCol: int32): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_column_bytes16".}
|
||||
proc column_double*(para1: PStmt, iCol: int32): float64{.cdecl, mylib,
|
||||
importc: "sqlite3_column_double".}
|
||||
proc column_int*(para1: PStmt, iCol: int32): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_column_int".}
|
||||
proc column_int64*(para1: PStmt, iCol: int32): int64{.cdecl, mylib,
|
||||
importc: "sqlite3_column_int64".}
|
||||
proc column_text*(para1: PStmt, iCol: int32): cstring{.cdecl, mylib,
|
||||
importc: "sqlite3_column_text".}
|
||||
proc column_text16*(para1: PStmt, iCol: int32): pointer{.cdecl, mylib,
|
||||
importc: "sqlite3_column_text16".}
|
||||
proc column_type*(para1: PStmt, iCol: int32): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_column_type".}
|
||||
proc finalize*(PStmt: PStmt): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_finalize".}
|
||||
proc reset*(PStmt: PStmt): int32{.cdecl, mylib, importc: "sqlite3_reset".}
|
||||
proc create_function*(para1: PSqlite3, zFunctionName: cstring, nArg: int32,
|
||||
eTextRep: int32, para5: pointer,
|
||||
xFunc: Create_function_func_func,
|
||||
xStep: Create_function_step_func,
|
||||
xFinal: Create_function_final_func): int32{.cdecl,
|
||||
mylib, importc: "sqlite3_create_function".}
|
||||
proc create_function16*(para1: PSqlite3, zFunctionName: pointer, nArg: int32,
|
||||
eTextRep: int32, para5: pointer,
|
||||
xFunc: Create_function_func_func,
|
||||
xStep: Create_function_step_func,
|
||||
xFinal: Create_function_final_func): int32{.cdecl,
|
||||
mylib, importc: "sqlite3_create_function16".}
|
||||
proc aggregate_count*(para1: Pcontext): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_aggregate_count".}
|
||||
proc value_blob*(para1: PValue): pointer{.cdecl, mylib,
|
||||
importc: "sqlite3_value_blob".}
|
||||
proc value_bytes*(para1: PValue): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_value_bytes".}
|
||||
proc value_bytes16*(para1: PValue): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_value_bytes16".}
|
||||
proc value_double*(para1: PValue): float64{.cdecl, mylib,
|
||||
importc: "sqlite3_value_double".}
|
||||
proc value_int*(para1: PValue): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_value_int".}
|
||||
proc value_int64*(para1: PValue): int64{.cdecl, mylib,
|
||||
importc: "sqlite3_value_int64".}
|
||||
proc value_text*(para1: PValue): cstring{.cdecl, mylib,
|
||||
importc: "sqlite3_value_text".}
|
||||
proc value_text16*(para1: PValue): pointer{.cdecl, mylib,
|
||||
importc: "sqlite3_value_text16".}
|
||||
proc value_text16le*(para1: PValue): pointer{.cdecl, mylib,
|
||||
importc: "sqlite3_value_text16le".}
|
||||
proc value_text16be*(para1: PValue): pointer{.cdecl, mylib,
|
||||
importc: "sqlite3_value_text16be".}
|
||||
proc value_type*(para1: PValue): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_value_type".}
|
||||
proc aggregate_context*(para1: Pcontext, nBytes: int32): pointer{.cdecl,
|
||||
mylib, importc: "sqlite3_aggregate_context".}
|
||||
proc user_data*(para1: Pcontext): pointer{.cdecl, mylib,
|
||||
importc: "sqlite3_user_data".}
|
||||
proc get_auxdata*(para1: Pcontext, para2: int32): pointer{.cdecl, mylib,
|
||||
importc: "sqlite3_get_auxdata".}
|
||||
proc set_auxdata*(para1: Pcontext, para2: int32, para3: pointer,
|
||||
para4: proc (para1: pointer){.cdecl.}){.cdecl, mylib,
|
||||
importc: "sqlite3_set_auxdata".}
|
||||
proc result_blob*(para1: Pcontext, para2: pointer, para3: int32,
|
||||
para4: Result_func){.cdecl, mylib,
|
||||
importc: "sqlite3_result_blob".}
|
||||
proc result_double*(para1: Pcontext, para2: float64){.cdecl, mylib,
|
||||
importc: "sqlite3_result_double".}
|
||||
proc result_error*(para1: Pcontext, para2: cstring, para3: int32){.cdecl,
|
||||
mylib, importc: "sqlite3_result_error".}
|
||||
proc result_error16*(para1: Pcontext, para2: pointer, para3: int32){.cdecl,
|
||||
mylib, importc: "sqlite3_result_error16".}
|
||||
proc result_int*(para1: Pcontext, para2: int32){.cdecl, mylib,
|
||||
importc: "sqlite3_result_int".}
|
||||
proc result_int64*(para1: Pcontext, para2: int64){.cdecl, mylib,
|
||||
importc: "sqlite3_result_int64".}
|
||||
proc result_null*(para1: Pcontext){.cdecl, mylib,
|
||||
importc: "sqlite3_result_null".}
|
||||
proc result_text*(para1: Pcontext, para2: cstring, para3: int32,
|
||||
para4: Result_func){.cdecl, mylib,
|
||||
importc: "sqlite3_result_text".}
|
||||
proc result_text16*(para1: Pcontext, para2: pointer, para3: int32,
|
||||
para4: Result_func){.cdecl, mylib,
|
||||
importc: "sqlite3_result_text16".}
|
||||
proc result_text16le*(para1: Pcontext, para2: pointer, para3: int32,
|
||||
para4: Result_func){.cdecl, mylib,
|
||||
importc: "sqlite3_result_text16le".}
|
||||
proc result_text16be*(para1: Pcontext, para2: pointer, para3: int32,
|
||||
para4: Result_func){.cdecl, mylib,
|
||||
importc: "sqlite3_result_text16be".}
|
||||
proc result_value*(para1: Pcontext, para2: PValue){.cdecl, mylib,
|
||||
importc: "sqlite3_result_value".}
|
||||
proc create_collation*(para1: PSqlite3, zName: cstring, eTextRep: int32,
|
||||
para4: pointer, xCompare: Create_collation_func): int32{.
|
||||
cdecl, mylib, importc: "sqlite3_create_collation".}
|
||||
proc create_collation16*(para1: PSqlite3, zName: cstring, eTextRep: int32,
|
||||
para4: pointer, xCompare: Create_collation_func): int32{.
|
||||
cdecl, mylib, importc: "sqlite3_create_collation16".}
|
||||
proc collation_needed*(para1: PSqlite3, para2: pointer, para3: Collation_needed_func): int32{.
|
||||
cdecl, mylib, importc: "sqlite3_collation_needed".}
|
||||
proc collation_needed16*(para1: PSqlite3, para2: pointer, para3: Collation_needed_func): int32{.
|
||||
cdecl, mylib, importc: "sqlite3_collation_needed16".}
|
||||
proc libversion*(): cstring{.cdecl, mylib, importc: "sqlite3_libversion".}
|
||||
#Alias for allowing better code portability (win32 is not working with external variables)
|
||||
proc version*(): cstring{.cdecl, mylib, importc: "sqlite3_libversion".}
|
||||
# Not published functions
|
||||
proc libversion_number*(): int32{.cdecl, mylib,
|
||||
importc: "sqlite3_libversion_number".}
|
||||
|
||||
proc backup_init*(pDest: PSqlite3, zDestName: cstring, pSource: PSqlite3, zSourceName: cstring): PSqlite3_Backup {.
|
||||
cdecl, mylib, importc: "sqlite3_backup_init".}
|
||||
|
||||
proc backup_step*(pBackup: PSqlite3_Backup, nPage: int32): int32 {.cdecl, mylib, importc: "sqlite3_backup_step".}
|
||||
|
||||
proc backup_finish*(pBackup: PSqlite3_Backup): int32 {.cdecl, mylib, importc: "sqlite3_backup_finish".}
|
||||
|
||||
proc backup_pagecount*(pBackup: PSqlite3_Backup): int32 {.cdecl, mylib, importc: "sqlite3_backup_pagecount".}
|
||||
|
||||
proc backup_remaining*(pBackup: PSqlite3_Backup): int32 {.cdecl, mylib, importc: "sqlite3_backup_remaining".}
|
||||
|
||||
proc sqlite3_sleep*(t: int64): int64 {.cdecl, mylib, importc: "sqlite3_sleep".}
|
||||
|
||||
#function sqlite3_key(db:Psqlite3; pKey:pointer; nKey:longint):longint;cdecl; external Sqlite3Lib name 'sqlite3_key';
|
||||
#function sqlite3_rekey(db:Psqlite3; pKey:pointer; nKey:longint):longint;cdecl; external Sqlite3Lib name 'sqlite3_rekey';
|
||||
#function sqlite3_sleep(_para1:longint):longint;cdecl; external Sqlite3Lib name 'sqlite3_sleep';
|
||||
#function sqlite3_expired(_para1:Psqlite3_stmt):longint;cdecl; external Sqlite3Lib name 'sqlite3_expired';
|
||||
#function sqlite3_global_recover:longint;cdecl; external Sqlite3Lib name 'sqlite3_global_recover';
|
||||
# implementation
|
||||
|
||||
when defined(nimHasStyleChecks):
|
||||
{.pop.}
|
||||
@@ -0,0 +1,148 @@
|
||||
import std/asyncdispatch
|
||||
import std/httpclient
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
import std/json
|
||||
import ../../error
|
||||
import ./surreal_rdb
|
||||
import ./surreal_lib
|
||||
|
||||
type SurrealImpl* = ref object
|
||||
|
||||
|
||||
proc surrealTimeoutMs(timeout: int): int =
|
||||
if timeout <= 0:
|
||||
return 0
|
||||
if timeout > high(int) div 1000:
|
||||
return high(int)
|
||||
result = timeout * 1000
|
||||
|
||||
|
||||
proc awaitWithTimeout[T](fut: Future[T], timeoutMs: int, timeoutMsg: string): Future[T] {.async.} =
|
||||
if timeoutMs <= 0:
|
||||
return await fut
|
||||
|
||||
let ok = await withTimeout(fut, timeoutMs)
|
||||
if not ok:
|
||||
dbError(timeoutMsg)
|
||||
result = fut.read
|
||||
|
||||
|
||||
proc failSurrealBody(body: JsonNode) =
|
||||
if body.kind == JObject:
|
||||
if body.hasKey("code") and body["code"].kind == JInt and body["code"].getInt == 400:
|
||||
if body.hasKey("information") and body["information"].kind == JString:
|
||||
dbError(body["information"].getStr())
|
||||
if body.hasKey("detail") and not body.hasKey("result") and body["detail"].kind == JString:
|
||||
dbError(body["detail"].getStr())
|
||||
|
||||
if body.kind == JArray:
|
||||
for row in body.items:
|
||||
if row.kind != JObject:
|
||||
continue
|
||||
if row.hasKey("status") and row["status"].kind == JString and row["status"].getStr() == "ERR":
|
||||
if row.hasKey("detail") and row["detail"].kind == JString:
|
||||
dbError(row["detail"].getStr())
|
||||
if row.hasKey("detail") and not row.hasKey("result") and row["detail"].kind == JString:
|
||||
dbError(row["detail"].getStr())
|
||||
|
||||
|
||||
proc runSurrealSql(db: SurrealConn, sql: string, timeout: int): Future[JsonNode] {.async.} =
|
||||
let timeoutMs = surrealTimeoutMs(timeout)
|
||||
let url = &"{db.host}:{db.port}/sql"
|
||||
let response = await awaitWithTimeout(
|
||||
db.client.post(url, sql),
|
||||
timeoutMs,
|
||||
&"SurrealDb request timed out after {timeoutMs} ms"
|
||||
)
|
||||
|
||||
let bodyText = await awaitWithTimeout(response.body(), timeoutMs,
|
||||
&"SurrealDb response body timed out after {timeoutMs} ms")
|
||||
|
||||
var body: JsonNode
|
||||
try:
|
||||
body = bodyText.parseJson()
|
||||
except CatchableError:
|
||||
dbError(&"SurrealDb returned non-JSON response: {bodyText}")
|
||||
|
||||
failSurrealBody(body)
|
||||
if response.status != $Http200:
|
||||
dbError(&"SurrealDb request failed with HTTP status {response.status}")
|
||||
|
||||
return body
|
||||
|
||||
# proc open*(_:type SurrealImpl, namespace="", database="",user="", password="",
|
||||
# host="", port:int32 = 0, maxConnections=1, timeout=30):Future[SurrealConnections] {.async.} =
|
||||
# var pools = newSeq[SurrealConn](maxConnections)
|
||||
# for i in 0..<maxConnections:
|
||||
# let client = newAsyncHttpClient()
|
||||
# var headers = newHttpHeaders(true)
|
||||
# headers["NS"] = namespace
|
||||
# headers["DB"] = database
|
||||
# headers["Accept"] = "application/json"
|
||||
# headers["Authorization"] = "Basic " & base64.encode(user & ":" & password)
|
||||
# client.headers = headers
|
||||
|
||||
# var url = &"{host}:{port}/status"
|
||||
# var resp = client.get(url).await
|
||||
# if(resp.status != $Http200):
|
||||
# dbError(&"Cannot connect to SurrealDb {host}:{port}")
|
||||
|
||||
# url = &"{host}:{port}/sql"
|
||||
# resp = client.post(url, &"DEFINE NAMESPACE `{namespace}`; USE NS `{namespace}`; DEFINE DATABASE `{database}`").await
|
||||
# if(resp.status != $Http200):
|
||||
# dbError(&"Cannot connect to SurrealDb {host}:{port}")
|
||||
|
||||
# pools[i] = SurrealConn(
|
||||
# conn: client,
|
||||
# host:host,
|
||||
# port:port,
|
||||
# isBusy: false,
|
||||
# createdAt: getTime().toUnix(),
|
||||
# )
|
||||
# return SurrealConnections(
|
||||
# pools: pools,
|
||||
# timeout: timeout
|
||||
# )
|
||||
|
||||
|
||||
proc query*(db:SurrealConn, query: string, args: seq[string], timeout:int):Future[JsonNode] {.async.} =
|
||||
## return JArray
|
||||
assert(not db.client.isNil, "Database not connected.")
|
||||
let body = await runSurrealSql(db, dbFormat(query, args), timeout)
|
||||
if body.kind != JArray or body.len == 0:
|
||||
dbError("SurrealDb returned empty result set")
|
||||
return body[^1]["result"]
|
||||
|
||||
|
||||
proc query*(db:SurrealConn, query: string, args: JsonNode, timeout:int):Future[JsonNode] {.async.} =
|
||||
## return JArray
|
||||
assert(not db.client.isNil, "Database not connected.")
|
||||
let body = await runSurrealSql(db, dbFormat(query, args), timeout)
|
||||
if body.kind != JArray or body.len == 0:
|
||||
dbError("SurrealDb returned empty result set")
|
||||
return body[^1]["result"]
|
||||
|
||||
|
||||
proc exec*(db:SurrealConn, query: string, args: seq[string], timeout:int) {.async.} =
|
||||
assert(not db.client.isNil, "Database not connected.")
|
||||
let body = await runSurrealSql(db, dbFormat(query, args), timeout)
|
||||
if body[^1]["status"].getStr() == "ERR":
|
||||
dbError(body[^1]["result"].getStr())
|
||||
|
||||
|
||||
proc exec*(db:SurrealConn, query: string, args: JsonNode, timeout:int) {.async.} =
|
||||
assert(not db.client.isNil, "Database not connected.")
|
||||
let body = await runSurrealSql(db, dbFormat(query, args), timeout)
|
||||
if body[^1]["status"].getStr() == "ERR":
|
||||
dbError(body[^1]["result"].getStr())
|
||||
|
||||
|
||||
proc info*(db:SurrealConn, query: string, args: seq[string], timeout:int):Future[JsonNode] {.async.} =
|
||||
assert(not db.client.isNil, "Database not connected.")
|
||||
return await runSurrealSql(db, dbFormat(query, args), timeout)
|
||||
|
||||
|
||||
proc info*(db:SurrealConn, query: string, args: JsonNode, timeout:int):Future[JsonNode] {.async.} =
|
||||
assert(not db.client.isNil, "Database not connected.")
|
||||
return await runSurrealSql(db, dbFormat(query, args), timeout)
|
||||
@@ -0,0 +1,197 @@
|
||||
import std/strutils
|
||||
import std/json
|
||||
|
||||
|
||||
proc looksLikeRecordId(s: string): bool =
|
||||
let colonPos = s.find(':')
|
||||
if colonPos <= 0 or colonPos >= s.high:
|
||||
return false
|
||||
if s.find(':', colonPos + 1) >= 0:
|
||||
return false
|
||||
if s.contains(' ') or s.contains('"') or s.contains('\''):
|
||||
return false
|
||||
if not (s[0].isAlphaAscii or s[0] == '_'):
|
||||
return false
|
||||
for ch in s[1 ..< colonPos]:
|
||||
if not (ch.isAlphaNumeric or ch == '_'):
|
||||
return false
|
||||
for ch in s[colonPos + 1 .. ^1]:
|
||||
if ch.isSpaceAscii:
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
proc looksLikeDateTimeValue(s: string): bool =
|
||||
if s.len < 10:
|
||||
return false
|
||||
|
||||
for idx in [0, 1, 2, 3, 5, 6, 8, 9]:
|
||||
if idx >= s.len or not s[idx].isDigit:
|
||||
return false
|
||||
|
||||
if s[4] != '-' or s[7] != '-':
|
||||
return false
|
||||
|
||||
if s.len == 10:
|
||||
return true
|
||||
|
||||
if s.len < 19:
|
||||
return false
|
||||
|
||||
if s[10] notin {'T', ' '}:
|
||||
return false
|
||||
|
||||
for idx in [11, 12, 14, 15, 17, 18]:
|
||||
if idx >= s.len or not s[idx].isDigit:
|
||||
return false
|
||||
|
||||
if s[13] != ':' or s[16] != ':':
|
||||
return false
|
||||
|
||||
if s.len == 19:
|
||||
return true
|
||||
|
||||
return s[19] in {'.', 'Z', '+', '-'}
|
||||
|
||||
|
||||
proc dbQuote(s:string):string =
|
||||
## DB quotes the string.
|
||||
if s == "null":
|
||||
return "NULL"
|
||||
if looksLikeRecordId(s):
|
||||
return s
|
||||
result = newStringOfCap(s.len + 2)
|
||||
result.add "'"
|
||||
for c in items(s):
|
||||
# see https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html#mysql-escaping
|
||||
case c
|
||||
of '\0': result.add "\\0"
|
||||
of '\b': result.add "\\b"
|
||||
of '\t': result.add "\\t"
|
||||
of '\l': result.add "\\n"
|
||||
of '\r': result.add "\\r"
|
||||
of '\x1a': result.add "\\Z"
|
||||
of '"': result.add "\\\""
|
||||
of '\'': result.add "\\'"
|
||||
of '\\': result.add "\\\\"
|
||||
of '_': result.add "\\_"
|
||||
else: result.add c
|
||||
add(result, '\'')
|
||||
|
||||
|
||||
proc appendAlphabet(result: var string, n: int) =
|
||||
var n = n
|
||||
if n <= 0:
|
||||
return
|
||||
|
||||
var chars: seq[char] = @[]
|
||||
while n > 0:
|
||||
n.dec()
|
||||
chars.add(chr(ord('a') + (n mod 26)))
|
||||
n = n div 26
|
||||
|
||||
for i in countdown(chars.high, 0):
|
||||
result.add(chars[i])
|
||||
|
||||
|
||||
proc dbFormat*(formatstr: string, args: varargs[string]): string =
|
||||
var a = 0
|
||||
result = newStringOfCap(formatstr.len + args.len * 8)
|
||||
var segStart = 0
|
||||
for j in 0 ..< formatstr.len:
|
||||
if formatstr[j] == '?':
|
||||
if j > segStart:
|
||||
result.add(formatstr[segStart ..< j])
|
||||
result.add(dbQuote(args[a]))
|
||||
inc(a)
|
||||
segStart = j + 1
|
||||
if segStart < formatstr.len:
|
||||
result.add(formatstr[segStart ..< formatstr.len])
|
||||
|
||||
|
||||
proc numToAlphabet*(n:int):string =
|
||||
## 1 => "a", 26 => "z", 27 => "aa", 28 => "ab", 52 => "az", 53 => "ba"
|
||||
result = newStringOfCap(8)
|
||||
appendAlphabet(result, n)
|
||||
|
||||
|
||||
proc questionToDaller*(s: string): string =
|
||||
## from `UPDATE user SET name = ?, email = ? WHERE id = ?`
|
||||
##
|
||||
## to `UPDATE user SET name = $a, email = $b WHERE id = $c`
|
||||
var i = 1
|
||||
var segStart = 0
|
||||
result = newStringOfCap(s.len + 8)
|
||||
for j in 0 ..< s.len:
|
||||
if s[j] == '?':
|
||||
if j > segStart:
|
||||
result.add(s[segStart ..< j])
|
||||
result.add('$')
|
||||
appendAlphabet(result, i)
|
||||
inc(i)
|
||||
segStart = j + 1
|
||||
if segStart < s.len:
|
||||
result.add(s[segStart ..< s.len])
|
||||
|
||||
|
||||
proc appendJsonLetClause(result: var string, idx: int, arg: JsonNode, quoteString: bool) =
|
||||
result.add("LET $")
|
||||
appendAlphabet(result, idx)
|
||||
result.add(" = ")
|
||||
case arg.kind
|
||||
of JBool:
|
||||
result.add($arg.getBool)
|
||||
of JInt:
|
||||
result.add($arg.getInt)
|
||||
of JFloat:
|
||||
result.add($arg.getFloat)
|
||||
of JString:
|
||||
let val = arg.getStr().replace("\"", "\\\"")
|
||||
if looksLikeRecordId(val):
|
||||
result.add(val)
|
||||
elif looksLikeDateTimeValue(val):
|
||||
result.add("<datetime>\"")
|
||||
result.add(val)
|
||||
result.add("\"")
|
||||
elif quoteString:
|
||||
result.add('"')
|
||||
result.add(val)
|
||||
result.add('"')
|
||||
else:
|
||||
result.add(val)
|
||||
of JNull:
|
||||
result.add("NONE")
|
||||
of JArray, JObject:
|
||||
result.add($arg)
|
||||
result.add("; ")
|
||||
|
||||
|
||||
proc dbFormatPrepared*(normalizedQueryString: string, args: JsonNode): string =
|
||||
## `normalizedQueryString` should already have `?` converted to SurrealQL
|
||||
## style placeholders.
|
||||
if args.isNil:
|
||||
result = newStringOfCap(normalizedQueryString.len)
|
||||
result.add(normalizedQueryString)
|
||||
return
|
||||
if args.kind == JNull:
|
||||
result = newStringOfCap(normalizedQueryString.len)
|
||||
result.add(normalizedQueryString)
|
||||
return
|
||||
|
||||
result = newStringOfCap(normalizedQueryString.len + max(args.len, 1) * 24)
|
||||
if args.kind == JArray and args.len > 0:
|
||||
var i = 1
|
||||
for arg in args.items:
|
||||
appendJsonLetClause(result, i, arg, true)
|
||||
inc(i)
|
||||
elif args.kind == JObject and args.len > 0:
|
||||
var i = 1
|
||||
for (_, arg) in args.pairs:
|
||||
appendJsonLetClause(result, i, arg, false)
|
||||
inc(i)
|
||||
|
||||
result.add(normalizedQueryString)
|
||||
|
||||
|
||||
proc dbFormat*(queryString: string, args: JsonNode): string =
|
||||
result = dbFormatPrepared(queryString.questionToDaller(), args)
|
||||
@@ -0,0 +1,6 @@
|
||||
import std/httpclient
|
||||
|
||||
type SurrealConn* = object
|
||||
client*: AsyncHttpClient
|
||||
host*:string
|
||||
port*:int32
|
||||
@@ -0,0 +1,58 @@
|
||||
import std/os
|
||||
import std/parsecfg
|
||||
import std/terminal
|
||||
import std/logging
|
||||
import std/macros
|
||||
import std/strformat
|
||||
|
||||
|
||||
type
|
||||
LogSetting* = ref object
|
||||
shouldDisplayLog*: bool
|
||||
shouldOutputLogFile*: bool
|
||||
logDir*: string
|
||||
|
||||
proc logger*(self:LogSetting, output: auto) =
|
||||
# let msg = $output & " " & $args
|
||||
let msg = $output
|
||||
# console log
|
||||
if self.shouldDisplayLog:
|
||||
let logger = newConsoleLogger()
|
||||
logger.log(lvlDebug, msg)
|
||||
# file log
|
||||
if self.shouldOutputLogFile:
|
||||
# info $output & $args
|
||||
let path = self.logDir & "/log.log"
|
||||
createDir(parentDir(path))
|
||||
let logger = newRollingFileLogger(path, mode=fmAppend, fmtStr=verboseFmtStr)
|
||||
defer: logger.file.close()
|
||||
logger.log(lvlDebug, msg)
|
||||
flushFile(logger.file)
|
||||
|
||||
|
||||
proc echoErrorMsg*(self:LogSetting, msg:string, args:seq[string] = @[]) =
|
||||
let msg = msg & " " & $args
|
||||
# console log
|
||||
if self.shouldDisplayLog:
|
||||
styledWriteLine(stdout, fgRed, bgDefault, msg, resetStyle)
|
||||
# file log
|
||||
if self.shouldOutputLogFile:
|
||||
let path = self.logDir & "/error.log"
|
||||
createDir(parentDir(path))
|
||||
let logger = newRollingFileLogger(path, mode=fmAppend, fmtStr=verboseFmtStr)
|
||||
defer: logger.file.close()
|
||||
logger.log(lvlError, msg)
|
||||
flushFile(logger.file)
|
||||
|
||||
proc echoWarningMsg*(self:LogSetting, msg:string) =
|
||||
# console log
|
||||
if self.shouldDisplayLog:
|
||||
styledWriteLine(stdout, fgYellow, bgDefault, msg, resetStyle)
|
||||
# file log
|
||||
if self.shouldOutputLogFile:
|
||||
let path = self.logDir & "/error.log"
|
||||
createDir(parentDir(path))
|
||||
let logger = newRollingFileLogger(path, mode=fmAppend, fmtStr=verboseFmtStr)
|
||||
defer: logger.file.close()
|
||||
logger.log(lvlError, msg)
|
||||
flushFile(logger.file)
|
||||
@@ -0,0 +1,716 @@
|
||||
import std/asyncdispatch
|
||||
import std/deques
|
||||
import std/json
|
||||
import std/monotimes
|
||||
import std/options
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
import std/sequtils
|
||||
import std/tables
|
||||
import std/times
|
||||
import ../../error
|
||||
import ../../libs/baradb/baradb_client
|
||||
import ../../log
|
||||
import ../database_types
|
||||
import ./query/baradb_builder
|
||||
import ./baradb_types
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# connection pool
|
||||
# ================================================================================
|
||||
|
||||
proc removePoolWaiter(pools: Connections, w: Future[void]) =
|
||||
var kept = initDeque[Future[void]]()
|
||||
while pools.waiters.len > 0:
|
||||
let x = pools.waiters.popFirst()
|
||||
if x != w:
|
||||
kept.addLast(x)
|
||||
pools.waiters = move(kept)
|
||||
|
||||
proc wakeOnePoolWaiter(pools: Connections) =
|
||||
while pools.waiters.len > 0:
|
||||
let w = pools.waiters.popFirst()
|
||||
if w.finished:
|
||||
continue
|
||||
w.complete()
|
||||
break
|
||||
|
||||
proc poolRemainingMs(deadline: MonoTime): int =
|
||||
let left = (deadline - getMonoTime()).inMilliseconds
|
||||
if left <= 0:
|
||||
return 0
|
||||
if left > int64(high(int)):
|
||||
return high(int)
|
||||
result = int(left)
|
||||
if result < 1:
|
||||
result = 1
|
||||
|
||||
proc nowUnix(): int64 =
|
||||
getTime().toUnix()
|
||||
|
||||
proc hasConnExpired(self: Connections, conn: Connection): bool =
|
||||
let now = nowUnix()
|
||||
if self.maxConnectionLifetime > 0 and now - conn.createdAt >= self.maxConnectionLifetime.int64:
|
||||
return true
|
||||
if self.maxConnectionIdleTime > 0 and now - conn.lastUsedAt >= self.maxConnectionIdleTime.int64:
|
||||
return true
|
||||
return false
|
||||
|
||||
proc openBaradbConn(self: Connections): BaraClient =
|
||||
let config = ClientConfig(
|
||||
host: self.host,
|
||||
port: self.port,
|
||||
database: self.database,
|
||||
username: self.user,
|
||||
password: self.password,
|
||||
timeoutMs: self.timeout * 1000,
|
||||
maxRetries: 3,
|
||||
)
|
||||
let client = newClient(config)
|
||||
waitFor client.connect()
|
||||
return client
|
||||
|
||||
proc refreshConn(self: Connections, connI: int): bool =
|
||||
if connI < 0 or connI >= self.conns.len:
|
||||
return false
|
||||
let client = openBaradbConn(self)
|
||||
let oldConn = self.conns[connI].client
|
||||
if not oldConn.isNil:
|
||||
oldConn.close()
|
||||
self.conns[connI].client = client
|
||||
self.conns[connI].createdAt = nowUnix()
|
||||
self.conns[connI].lastUsedAt = self.conns[connI].createdAt
|
||||
return true
|
||||
|
||||
proc getFreeConn(self: BaradbConnections | BaradbQuery | RawBaradbQuery): Future[int] {.async.} =
|
||||
let deadline = getMonoTime() + initDuration(seconds = self.pools.timeout)
|
||||
while true:
|
||||
for i in 0 ..< self.pools.conns.len:
|
||||
if not self.pools.conns[i].isBusy:
|
||||
self.pools.conns[i].isBusy = true
|
||||
if self.pools.hasConnExpired(self.pools.conns[i]):
|
||||
try:
|
||||
discard self.pools.refreshConn(i)
|
||||
except CatchableError:
|
||||
discard
|
||||
when defined(check_pool):
|
||||
echo "=== getFreeConn ", i
|
||||
return i
|
||||
if getMonoTime() >= deadline:
|
||||
return errorConnectionNum
|
||||
let w = newFuture[void]("getFreeConn.poolWait")
|
||||
self.pools.waiters.addLast(w)
|
||||
var ms = poolRemainingMs(deadline)
|
||||
if ms < 1:
|
||||
ms = 1
|
||||
let ok = await withTimeout(w, ms)
|
||||
if not ok:
|
||||
removePoolWaiter(self.pools, w)
|
||||
return errorConnectionNum
|
||||
|
||||
|
||||
proc returnConn(self: BaradbConnections | BaradbQuery | RawBaradbQuery, i: int) {.async.} =
|
||||
if i != errorConnectionNum:
|
||||
self.pools.conns[i].isBusy = false
|
||||
self.pools.conns[i].lastUsedAt = nowUnix()
|
||||
wakeOnePoolWaiter(self.pools)
|
||||
|
||||
|
||||
proc raisePoolTimeout(self: BaradbConnections | BaradbQuery | RawBaradbQuery) {.noreturn.} =
|
||||
raise newException(DbError, "Timed out while waiting for a free Baradb connection")
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# SQL formatting helpers
|
||||
# ================================================================================
|
||||
|
||||
proc escapeSqlValue(val: JsonNode): string =
|
||||
case val.kind
|
||||
of JNull:
|
||||
return "NULL"
|
||||
of JBool:
|
||||
return if val.getBool: "TRUE" else: "FALSE"
|
||||
of JInt:
|
||||
return $val.getInt
|
||||
of JFloat:
|
||||
return $val.getFloat
|
||||
of JString:
|
||||
let s = val.getStr
|
||||
return "'" & s.replace("'", "''") & "'"
|
||||
else:
|
||||
return "'" & val.pretty.replace("'", "''") & "'"
|
||||
|
||||
|
||||
proc formatSql*(sql: string, args: seq[JsonNode]): string =
|
||||
result = sql
|
||||
for arg in args:
|
||||
let pos = result.find("?")
|
||||
if pos < 0:
|
||||
break
|
||||
result = result[0..<pos] & escapeSqlValue(arg) & result[pos+1..^1]
|
||||
|
||||
|
||||
proc formatSql*(sql: string, args: JsonNode): string =
|
||||
var arr: seq[JsonNode]
|
||||
for arg in args.items:
|
||||
arr.add(arg["value"])
|
||||
return formatSql(sql, arr)
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# toJson
|
||||
# ================================================================================
|
||||
|
||||
proc toJson*(resultSet: QueryResult): seq[JsonNode] =
|
||||
var response_table = newSeq[JsonNode](resultSet.rowCount)
|
||||
for r in 0 ..< resultSet.rowCount:
|
||||
var response_row = newJObject()
|
||||
for c in 0 ..< resultSet.columns.len:
|
||||
let key = resultSet.columns[c]
|
||||
let val = resultSet.rows[r][c]
|
||||
if val.len == 0:
|
||||
response_row[key] = newJNull()
|
||||
elif val == "NULL" or val == "null":
|
||||
response_row[key] = newJNull()
|
||||
elif val == "t" or val == "true" or val == "f" or val == "false":
|
||||
response_row[key] = newJBool(val == "t" or val == "true")
|
||||
else:
|
||||
try:
|
||||
response_row[key] = newJInt(val.parseInt)
|
||||
except ValueError:
|
||||
try:
|
||||
response_row[key] = newJFloat(val.parseFloat)
|
||||
except ValueError:
|
||||
response_row[key] = newJString(val)
|
||||
response_table[r] = response_row
|
||||
return response_table
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# private exec
|
||||
# ================================================================================
|
||||
|
||||
proc getAllRows(self: BaradbQuery, queryString: string): Future[seq[JsonNode]] {.async.} =
|
||||
var connI = self.transactionConn
|
||||
if not self.isInTransaction:
|
||||
connI = getFreeConn(self).await
|
||||
defer:
|
||||
if not self.isInTransaction:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
|
||||
let sql = formatSql(queryString, self.placeHolder)
|
||||
let qr = await self.pools.conns[connI].client.query(sql)
|
||||
|
||||
if qr.rowCount == 0:
|
||||
self.log.echoErrorMsg(queryString)
|
||||
return newSeq[JsonNode](0)
|
||||
return toJson(qr)
|
||||
|
||||
|
||||
proc getAllRowsPlain(self: BaradbQuery, queryString: string, args: JsonNode): Future[seq[seq[string]]] {.async.} =
|
||||
var connI = self.transactionConn
|
||||
if not self.isInTransaction:
|
||||
connI = getFreeConn(self).await
|
||||
defer:
|
||||
if not self.isInTransaction:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
|
||||
let sql = formatSql(queryString, args)
|
||||
let qr = await self.pools.conns[connI].client.query(sql)
|
||||
return qr.rows
|
||||
|
||||
|
||||
proc getRow(self: BaradbQuery, queryString: string): Future[Option[JsonNode]] {.async.} =
|
||||
var connI = self.transactionConn
|
||||
if not self.isInTransaction:
|
||||
connI = getFreeConn(self).await
|
||||
defer:
|
||||
if not self.isInTransaction:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
|
||||
let sql = formatSql(queryString, self.placeHolder)
|
||||
let qr = await self.pools.conns[connI].client.query(sql)
|
||||
|
||||
if qr.rowCount == 0:
|
||||
self.log.echoErrorMsg(queryString)
|
||||
return none(JsonNode)
|
||||
return toJson(qr)[0].some()
|
||||
|
||||
|
||||
proc getRowPlain(self: BaradbQuery, queryString: string, args: JsonNode): Future[seq[string]] {.async.} =
|
||||
var connI = self.transactionConn
|
||||
if not self.isInTransaction:
|
||||
connI = getFreeConn(self).await
|
||||
defer:
|
||||
if not self.isInTransaction:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
|
||||
let sql = formatSql(queryString, args)
|
||||
let qr = await self.pools.conns[connI].client.query(sql)
|
||||
return qr.rows[0]
|
||||
|
||||
|
||||
proc getAllRows(self: RawBaradbQuery, queryString: string): Future[seq[JsonNode]] {.async.} =
|
||||
var connI = self.transactionConn
|
||||
if not self.isInTransaction:
|
||||
connI = getFreeConn(self).await
|
||||
defer:
|
||||
if not self.isInTransaction:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
|
||||
var arr: seq[JsonNode]
|
||||
for arg in self.placeHolder.items:
|
||||
arr.add(arg)
|
||||
let sql = formatSql(queryString, arr)
|
||||
let qr = await self.pools.conns[connI].client.query(sql)
|
||||
|
||||
if qr.rowCount == 0:
|
||||
self.log.echoErrorMsg(queryString)
|
||||
return newSeq[JsonNode](0)
|
||||
return toJson(qr)
|
||||
|
||||
|
||||
proc getAllRowsPlain(self: RawBaradbQuery, queryString: string, args: JsonNode): Future[seq[seq[string]]] {.async.} =
|
||||
var connI = self.transactionConn
|
||||
if not self.isInTransaction:
|
||||
connI = getFreeConn(self).await
|
||||
defer:
|
||||
if not self.isInTransaction:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
|
||||
var arr: seq[JsonNode]
|
||||
for arg in args.items:
|
||||
arr.add(arg)
|
||||
let sql = formatSql(queryString, arr)
|
||||
let qr = await self.pools.conns[connI].client.query(sql)
|
||||
return qr.rows
|
||||
|
||||
|
||||
proc getRow(self: RawBaradbQuery, queryString: string): Future[Option[JsonNode]] {.async.} =
|
||||
var connI = self.transactionConn
|
||||
if not self.isInTransaction:
|
||||
connI = getFreeConn(self).await
|
||||
defer:
|
||||
if not self.isInTransaction:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
|
||||
var arr: seq[JsonNode]
|
||||
for arg in self.placeHolder.items:
|
||||
arr.add(arg)
|
||||
let sql = formatSql(queryString, arr)
|
||||
let qr = await self.pools.conns[connI].client.query(sql)
|
||||
|
||||
if qr.rowCount == 0:
|
||||
self.log.echoErrorMsg(queryString)
|
||||
return none(JsonNode)
|
||||
return toJson(qr)[0].some()
|
||||
|
||||
|
||||
proc getRowPlain(self: RawBaradbQuery, queryString: string, args: JsonNode): Future[seq[string]] {.async.} =
|
||||
var connI = self.transactionConn
|
||||
if not self.isInTransaction:
|
||||
connI = getFreeConn(self).await
|
||||
defer:
|
||||
if not self.isInTransaction:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
|
||||
var arr: seq[JsonNode]
|
||||
for arg in args.items:
|
||||
arr.add(arg)
|
||||
let sql = formatSql(queryString, arr)
|
||||
let qr = await self.pools.conns[connI].client.query(sql)
|
||||
return qr.rows[0]
|
||||
|
||||
|
||||
proc exec(self: BaradbQuery, queryString: string) {.async.} =
|
||||
var connI = self.transactionConn
|
||||
if not self.isInTransaction:
|
||||
connI = getFreeConn(self).await
|
||||
defer:
|
||||
if not self.isInTransaction:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
|
||||
let sql = formatSql(queryString, self.placeHolder)
|
||||
discard await self.pools.conns[connI].client.exec(sql)
|
||||
|
||||
|
||||
proc exec(self: RawBaradbQuery, queryString: string, args: JsonNode) {.async.} =
|
||||
var connI = self.transactionConn
|
||||
if not self.isInTransaction:
|
||||
connI = getFreeConn(self).await
|
||||
defer:
|
||||
if not self.isInTransaction:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
|
||||
var arr: seq[JsonNode]
|
||||
for arg in args.items:
|
||||
arr.add(arg)
|
||||
let sql = formatSql(queryString, arr)
|
||||
discard await self.pools.conns[connI].client.exec(sql)
|
||||
|
||||
|
||||
proc insertId(self: BaradbQuery, queryString: string, key: string): Future[string] {.async.} =
|
||||
var connI = self.transactionConn
|
||||
if not self.isInTransaction:
|
||||
connI = getFreeConn(self).await
|
||||
defer:
|
||||
if not self.isInTransaction:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
|
||||
let sql = formatSql(queryString, self.placeHolder)
|
||||
discard await self.pools.conns[connI].client.exec(sql)
|
||||
|
||||
let table = self.query["table"].getStr
|
||||
let idSql = &"SELECT MAX(\"{key}\") FROM \"{table}\""
|
||||
let qr = await self.pools.conns[connI].client.query(idSql)
|
||||
if qr.rowCount > 0 and qr.rows[0].len > 0:
|
||||
return qr.rows[0][0]
|
||||
return ""
|
||||
|
||||
|
||||
proc getColumns(self: BaradbQuery, queryString: string, args = newJArray()): Future[seq[string]] {.async.} =
|
||||
var connI = self.transactionConn
|
||||
if not self.isInTransaction:
|
||||
connI = getFreeConn(self).await
|
||||
defer:
|
||||
if not self.isInTransaction:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
|
||||
var arr: seq[JsonNode]
|
||||
for arg in args.items:
|
||||
arr.add(arg["value"])
|
||||
let sql = formatSql(queryString, arr)
|
||||
let qr = await self.pools.conns[connI].client.query(sql)
|
||||
if qr.rowCount > 0:
|
||||
return qr.rows[0]
|
||||
return @[]
|
||||
|
||||
|
||||
proc transactionStart(self: BaradbConnections) {.async.} =
|
||||
let connI = getFreeConn(self).await
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
self.isInTransaction = true
|
||||
self.transactionConn = connI
|
||||
discard await self.pools.conns[connI].client.exec("BEGIN")
|
||||
|
||||
|
||||
proc transactionEnd(self: BaradbConnections, query: string) {.async.} =
|
||||
defer:
|
||||
self.returnConn(self.transactionConn).await
|
||||
self.transactionConn = 0
|
||||
self.isInTransaction = false
|
||||
|
||||
discard await self.pools.conns[self.transactionConn].client.exec(query)
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# public exec
|
||||
# ================================================================================
|
||||
|
||||
# ==================== return json ====================
|
||||
proc get*(self: BaradbQuery): Future[seq[JsonNode]] {.async.} =
|
||||
let sql = self.selectBuilder()
|
||||
try:
|
||||
self.log.logger(sql)
|
||||
return await self.getAllRows(sql)
|
||||
except CatchableError:
|
||||
self.log.echoErrorMsg(sql)
|
||||
self.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc first*(self: BaradbQuery): Future[Option[JsonNode]] {.async.} =
|
||||
let sql = self.selectFirstBuilder()
|
||||
try:
|
||||
self.log.logger(sql)
|
||||
return await self.getRow(sql)
|
||||
except CatchableError:
|
||||
self.log.echoErrorMsg(sql)
|
||||
self.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc find*(self: BaradbQuery, id: string, key = "id"): Future[Option[JsonNode]] {.async.} =
|
||||
self.placeHolder.add(%*{"key": key, "value": id})
|
||||
let sql = self.selectFindBuilder(key)
|
||||
try:
|
||||
self.log.logger(sql)
|
||||
return await self.getRow(sql)
|
||||
except CatchableError:
|
||||
self.log.echoErrorMsg(sql)
|
||||
self.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc find*(self: BaradbQuery, id: int, key = "id"): Future[Option[JsonNode]] {.async.} =
|
||||
return await self.find($id, key)
|
||||
|
||||
|
||||
# ==================== return string ====================
|
||||
proc getPlain*(self: BaradbQuery): Future[seq[seq[string]]] {.async.} =
|
||||
let sql = self.selectBuilder()
|
||||
try:
|
||||
self.log.logger(sql)
|
||||
return await self.getAllRowsPlain(sql, self.placeHolder)
|
||||
except CatchableError:
|
||||
self.log.echoErrorMsg(sql)
|
||||
self.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc firstPlain*(self: BaradbQuery): Future[seq[string]] {.async.} =
|
||||
let sql = self.selectFirstBuilder()
|
||||
try:
|
||||
self.log.logger(sql)
|
||||
return await self.getRowPlain(sql, self.placeHolder)
|
||||
except CatchableError:
|
||||
self.log.echoErrorMsg(sql)
|
||||
self.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc findPlain*(self: BaradbQuery, id: string, key = "id"): Future[seq[string]] {.async.} =
|
||||
self.placeHolder.add(%*{"key": key, "value": id})
|
||||
let sql = self.selectFindBuilder(key)
|
||||
try:
|
||||
self.log.logger(sql)
|
||||
return await self.getRowPlain(sql, self.placeHolder)
|
||||
except CatchableError:
|
||||
self.log.echoErrorMsg(sql)
|
||||
self.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc findPlain*(self: BaradbQuery, id: int, key = "id"): Future[seq[string]] {.async.} =
|
||||
return await self.findPlain($id, key)
|
||||
|
||||
|
||||
# ==================== insert JsonNode ====================
|
||||
proc insert*(self: BaradbQuery, items: JsonNode) {.async.} =
|
||||
let sql = self.insertValueBuilder(items)
|
||||
self.log.logger(sql)
|
||||
await self.exec(sql)
|
||||
|
||||
|
||||
proc insert*(self: BaradbQuery, items: seq[JsonNode]) {.async.} =
|
||||
let sql = self.insertValuesBuilder(items)
|
||||
self.log.logger(sql)
|
||||
await self.exec(sql)
|
||||
|
||||
|
||||
proc insertId*(self: BaradbQuery, items: JsonNode, key = "id"): Future[string] {.async.} =
|
||||
let sql = self.insertValueBuilder(items)
|
||||
self.log.logger(sql)
|
||||
return await self.insertId(sql, key)
|
||||
|
||||
|
||||
proc insertId*(self: BaradbQuery, items: seq[JsonNode], key = "id"): Future[seq[string]] {.async.} =
|
||||
result = newSeq[string](items.len)
|
||||
for i, item in items:
|
||||
let sql = self.insertValueBuilder(item)
|
||||
self.log.logger(sql)
|
||||
result[i] = await self.insertId(sql, key)
|
||||
self.placeHolder = newJArray()
|
||||
|
||||
|
||||
# ==================== insert Object ====================
|
||||
proc insert*[T](self: BaradbQuery, items: T) {.async.} =
|
||||
let sql = self.insertValueBuilder(%items)
|
||||
self.log.logger(sql)
|
||||
await self.exec(sql)
|
||||
|
||||
|
||||
proc insert*[T](self: BaradbQuery, items: seq[T]) {.async.} =
|
||||
let items = items.mapIt(%it)
|
||||
let sql = self.insertValuesBuilder(items)
|
||||
self.log.logger(sql)
|
||||
await self.exec(sql)
|
||||
|
||||
|
||||
proc insertId*[T](self: BaradbQuery, items: T, key = "id"): Future[string] {.async.} =
|
||||
let sql = self.insertValueBuilder(%items)
|
||||
self.log.logger(sql)
|
||||
return await self.insertId(sql, key)
|
||||
|
||||
|
||||
proc insertId*[T](self: BaradbQuery, items: seq[T], key = "id"): Future[seq[string]] {.async.} =
|
||||
result = newSeq[string](items.len)
|
||||
for i, item in items:
|
||||
let sql = self.insertValueBuilder(%item)
|
||||
self.log.logger(sql)
|
||||
result[i] = await self.insertId(sql, key)
|
||||
self.placeHolder = newJArray()
|
||||
|
||||
|
||||
# ==================== update ====================
|
||||
proc update*(self: BaradbQuery, items: JsonNode) {.async.} =
|
||||
let sql = self.updateBuilder(items)
|
||||
self.log.logger(sql)
|
||||
await self.exec(sql)
|
||||
|
||||
|
||||
proc update*[T](self: BaradbQuery, items: T) {.async.} =
|
||||
let sql = self.updateBuilder(%items)
|
||||
self.log.logger(sql)
|
||||
await self.exec(sql)
|
||||
|
||||
|
||||
proc delete*(self: BaradbQuery) {.async.} =
|
||||
let sql = self.deleteBuilder()
|
||||
self.log.logger(sql)
|
||||
await self.exec(sql)
|
||||
|
||||
|
||||
proc delete*(self: BaradbQuery, id: int, key = "id") {.async.} =
|
||||
let sql = self.deleteByIdBuilder(id, key)
|
||||
self.log.logger(sql)
|
||||
self.placeHolder.add(%*{"key": key, "value": id})
|
||||
await self.exec(sql)
|
||||
|
||||
|
||||
proc columns*(self: BaradbQuery): Future[seq[string]] {.async.} =
|
||||
let sql = self.columnBuilder()
|
||||
self.log.logger(sql)
|
||||
return await self.getColumns(sql, self.placeHolder)
|
||||
|
||||
|
||||
proc count*(self: BaradbQuery): Future[int] {.async.} =
|
||||
let sql = self.countBuilder()
|
||||
self.log.logger(sql)
|
||||
let response = await self.getRow(sql)
|
||||
if response.isSome:
|
||||
return response.get["aggregate"].getStr().parseInt()
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
proc min*(self: BaradbQuery, column: string): Future[Option[string]] {.async.} =
|
||||
let sql = self.minBuilder(column)
|
||||
self.log.logger(sql)
|
||||
let response = await self.getRow(sql)
|
||||
if response.isSome:
|
||||
case response.get["aggregate"].kind
|
||||
of JInt:
|
||||
return some($(response.get["aggregate"].getInt))
|
||||
of JFloat:
|
||||
return some($(response.get["aggregate"].getFloat))
|
||||
else:
|
||||
return some(response.get["aggregate"].getStr)
|
||||
else:
|
||||
return none(string)
|
||||
|
||||
|
||||
proc max*(self: BaradbQuery, column: string): Future[Option[string]] {.async.} =
|
||||
let sql = self.maxBuilder(column)
|
||||
self.log.logger(sql)
|
||||
let response = await self.getRow(sql)
|
||||
if response.isSome:
|
||||
case response.get["aggregate"].kind
|
||||
of JInt:
|
||||
return some($(response.get["aggregate"].getInt))
|
||||
of JFloat:
|
||||
return some($(response.get["aggregate"].getFloat))
|
||||
else:
|
||||
return some(response.get["aggregate"].getStr)
|
||||
else:
|
||||
return none(string)
|
||||
|
||||
|
||||
proc avg*(self: BaradbQuery, column: string): Future[Option[float]] {.async.} =
|
||||
let sql = self.avgBuilder(column)
|
||||
self.log.logger(sql)
|
||||
let response = await self.getRow(sql)
|
||||
if response.isSome:
|
||||
return response.get["aggregate"].getStr().parseFloat.some
|
||||
else:
|
||||
return none(float)
|
||||
|
||||
|
||||
proc sum*(self: BaradbQuery, column: string): Future[Option[float]] {.async.} =
|
||||
let sql = self.sumBuilder(column)
|
||||
self.log.logger(sql)
|
||||
let response = await self.getRow(sql)
|
||||
if response.isSome:
|
||||
return response.get["aggregate"].getStr.parseFloat.some
|
||||
else:
|
||||
return none(float)
|
||||
|
||||
|
||||
proc begin*(self: BaradbConnections) {.async.} =
|
||||
self.log.logger("BEGIN")
|
||||
await self.transactionStart()
|
||||
|
||||
|
||||
proc rollback*(self: BaradbConnections) {.async.} =
|
||||
self.log.logger("ROLLBACK")
|
||||
await self.transactionEnd("ROLLBACK")
|
||||
|
||||
|
||||
proc commit*(self: BaradbConnections) {.async.} =
|
||||
self.log.logger("COMMIT")
|
||||
await self.transactionEnd("COMMIT")
|
||||
|
||||
|
||||
# raw queries
|
||||
proc get*(self: RawBaradbQuery): Future[seq[JsonNode]] {.async.} =
|
||||
self.log.logger(self.queryString)
|
||||
return await self.getAllRows(self.queryString)
|
||||
|
||||
|
||||
proc getPlain*(self: RawBaradbQuery): Future[seq[seq[string]]] {.async.} =
|
||||
self.log.logger(self.queryString)
|
||||
return await self.getAllRowsPlain(self.queryString, self.placeHolder)
|
||||
|
||||
|
||||
proc exec*(self: RawBaradbQuery) {.async.} =
|
||||
self.log.logger(self.queryString)
|
||||
await self.exec(self.queryString, self.placeHolder)
|
||||
|
||||
|
||||
proc first*(self: RawBaradbQuery): Future[Option[JsonNode]] {.async.} =
|
||||
self.log.logger(self.queryString)
|
||||
return await self.getRow(self.queryString)
|
||||
|
||||
|
||||
proc firstPlain*(self: RawBaradbQuery): Future[seq[string]] {.async.} =
|
||||
self.log.logger(self.queryString)
|
||||
return await self.getRowPlain(self.queryString, self.placeHolder)
|
||||
|
||||
|
||||
# seeder templates
|
||||
template seeder*(rdb: BaradbConnections, tableName: string, body: untyped): untyped =
|
||||
block:
|
||||
if waitFor rdb.table(tableName).count() == 0:
|
||||
body
|
||||
|
||||
|
||||
template seeder*(rdb: BaradbConnections, tableName, column: string, body: untyped): untyped =
|
||||
block:
|
||||
if waitFor rdb.select(column).table(tableName).count() == 0:
|
||||
body
|
||||
@@ -0,0 +1,52 @@
|
||||
import std/asyncdispatch
|
||||
import std/deques
|
||||
import std/json
|
||||
import std/tables
|
||||
import std/times
|
||||
import ../../libs/baradb/baradb_client
|
||||
import ../../log
|
||||
import ./baradb_types
|
||||
|
||||
|
||||
proc dbOpen*(_: type Baradb, database: string, user: string, password: string,
|
||||
host: string, port: int, maxConnections = 1, timeout = 30,
|
||||
shouldDisplayLog = false, shouldOutputLogFile = false, logDir = "",
|
||||
maxConnectionLifetime = DEFAULT_CONN_MAX_LIFETIME_SECONDS,
|
||||
maxConnectionIdleTime = DEFAULT_CONN_MAX_IDLE_SECONDS): BaradbConnections =
|
||||
var conns = newSeq[Connection](maxConnections)
|
||||
for i in 0..<maxConnections:
|
||||
let config = ClientConfig(
|
||||
host: host,
|
||||
port: port,
|
||||
database: database,
|
||||
username: user,
|
||||
password: password,
|
||||
timeoutMs: timeout * 1000,
|
||||
maxRetries: 3,
|
||||
)
|
||||
let client = newClient(config)
|
||||
waitFor client.connect()
|
||||
conns[i] = Connection(
|
||||
client: client,
|
||||
isBusy: false,
|
||||
createdAt: getTime().toUnix(),
|
||||
lastUsedAt: getTime().toUnix(),
|
||||
)
|
||||
let pools = Connections(
|
||||
conns: conns,
|
||||
timeout: timeout,
|
||||
maxConnectionLifetime: maxConnectionLifetime,
|
||||
maxConnectionIdleTime: maxConnectionIdleTime,
|
||||
database: database,
|
||||
user: user,
|
||||
password: password,
|
||||
host: host,
|
||||
port: port,
|
||||
waiters: initDeque[Future[void]](),
|
||||
columnTypeCache: initTable[string, seq[seq[string]]](),
|
||||
preparedCache: initTable[string, BaradbPreparedEntry](),
|
||||
)
|
||||
result = BaradbConnections(
|
||||
pools: pools,
|
||||
log: LogSetting(shouldDisplayLog: shouldDisplayLog, shouldOutputLogFile: shouldOutputLogFile, logDir: logDir)
|
||||
)
|
||||
@@ -0,0 +1,386 @@
|
||||
import std/asyncdispatch
|
||||
import std/json
|
||||
import std/options
|
||||
import std/strutils
|
||||
import std/times
|
||||
import ../../enums
|
||||
import ./baradb_types
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# query
|
||||
# ================================================================================
|
||||
|
||||
proc select*(self: BaradbConnections, columnsArg: varargs[string]): BaradbQuery =
|
||||
let query = newJObject()
|
||||
|
||||
if columnsArg.len == 0 or columnsArg[0] == "*":
|
||||
query["select"] = %["*"]
|
||||
else:
|
||||
query["select"] = %columnsArg
|
||||
|
||||
let baradbQuery = BaradbQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
query: query,
|
||||
queryString: "",
|
||||
placeHolder: newJArray(),
|
||||
isInTransaction: self.isInTransaction,
|
||||
transactionConn: self.transactionConn,
|
||||
)
|
||||
return baradbQuery
|
||||
|
||||
|
||||
proc table*(self: BaradbConnections, tableArg: string): BaradbQuery =
|
||||
let query = newJObject()
|
||||
query["table"] = %tableArg
|
||||
|
||||
let baradbQuery = BaradbQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
query: query,
|
||||
queryString: "",
|
||||
placeHolder: newJArray(),
|
||||
isInTransaction: self.isInTransaction,
|
||||
transactionConn: self.transactionConn,
|
||||
)
|
||||
return baradbQuery
|
||||
|
||||
|
||||
proc table*(self: BaradbQuery, tableArg: string): BaradbQuery =
|
||||
self.query["table"] = %tableArg
|
||||
return self
|
||||
|
||||
|
||||
proc `distinct`*(self: BaradbQuery): BaradbQuery =
|
||||
self.query["distinct"] = %true
|
||||
return self
|
||||
|
||||
|
||||
# ============================== Conditions ==============================
|
||||
|
||||
proc join*(self: BaradbQuery, table: string, column1: string, symbol: string,
|
||||
column2: string): BaradbQuery =
|
||||
if self.query.hasKey("join") == false:
|
||||
self.query["join"] = %*[{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
}]
|
||||
else:
|
||||
self.query["join"].add(%*{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc leftJoin*(self: BaradbQuery, table: string, column1: string, symbol: string,
|
||||
column2: string): BaradbQuery =
|
||||
if self.query.hasKey("left_join") == false:
|
||||
self.query["left_join"] = %*[{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
}]
|
||||
else:
|
||||
self.query["left_join"].add(%*{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
const whereSymbols = ["is", "is not", "=", "!=", "<", "<=", ">=", ">", "<>", "LIKE","%LIKE","LIKE%","%LIKE%"]
|
||||
const whereSymbolsError = """Arg position 3 is only allowed of ["is", "is not", "=", "!=", "<", "<=", ">=", ">", "<>", "LIKE","%LIKE","LIKE%","%LIKE%"]"""
|
||||
|
||||
proc where*(self: BaradbQuery, column: string, symbol: string,
|
||||
value: string|int|float): BaradbQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(CatchableError, whereSymbolsError)
|
||||
|
||||
self.placeHolder.add(%*{"key": column, "value": value})
|
||||
|
||||
if self.query.hasKey("where") == false:
|
||||
self.query["where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["where"].add(%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc where*(self: BaradbQuery, column: string, symbol: string,
|
||||
value: bool): BaradbQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(CatchableError, whereSymbolsError)
|
||||
|
||||
self.placeHolder.add(%*{"key": column, "value": value})
|
||||
|
||||
if self.query.hasKey("where") == false:
|
||||
self.query["where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["where"].add(%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc where*(self: BaradbQuery, column: string, symbol: string, value: nil.type): BaradbQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(CatchableError, whereSymbolsError)
|
||||
|
||||
if self.query.hasKey("where") == false:
|
||||
self.query["where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["where"].add(%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc orWhere*(self: BaradbQuery, column: string, symbol: string,
|
||||
value: string|int|float|bool): BaradbQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(CatchableError, whereSymbolsError)
|
||||
|
||||
self.placeHolder.add(%*{"key": column, "value": value})
|
||||
|
||||
if self.query.hasKey("or_where") == false:
|
||||
self.query["or_where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["or_where"].add(%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc orWhere*(self: BaradbQuery, column: string, symbol: string, value: nil.type): BaradbQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(CatchableError, whereSymbolsError)
|
||||
|
||||
if self.query.hasKey("or_where") == false:
|
||||
self.query["or_where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["or_where"].add(%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetween*(self: BaradbQuery, column: string, width: array[2, int|float]): BaradbQuery =
|
||||
if self.query.hasKey("where_between") == false:
|
||||
self.query["where_between"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_between"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetween*(self: BaradbQuery, column: string, width: array[2, string]): BaradbQuery =
|
||||
if self.query.hasKey("where_between_string") == false:
|
||||
self.query["where_between_string"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_between_string"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetween*(self: BaradbQuery, column: string, width: array[2, int|float]): BaradbQuery =
|
||||
if self.query.hasKey("where_not_between") == false:
|
||||
self.query["where_not_between"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_between"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetween*(self: BaradbQuery, column: string, width: array[2, string]): BaradbQuery =
|
||||
if self.query.hasKey("where_not_between_string") == false:
|
||||
self.query["where_not_between_string"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_between_string"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereIn*(self: BaradbQuery, column: string, width: seq[int|float|string]): BaradbQuery =
|
||||
if self.query.hasKey("where_in") == false:
|
||||
self.query["where_in"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_in"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotIn*(self: BaradbQuery, column: string, width: seq[int|float|string]): BaradbQuery =
|
||||
if self.query.hasKey("where_not_in") == false:
|
||||
self.query["where_not_in"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_in"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNull*(self: BaradbQuery, column: string): BaradbQuery =
|
||||
if self.query.hasKey("where_null") == false:
|
||||
self.query["where_null"] = %*[{
|
||||
"column": column
|
||||
}]
|
||||
else:
|
||||
self.query["where_null"].add(%*{
|
||||
"column": column
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc groupBy*(self: BaradbQuery, column: string): BaradbQuery =
|
||||
if self.query.hasKey("group_by") == false:
|
||||
self.query["group_by"] = %*[{"column": column}]
|
||||
else:
|
||||
self.query["group_by"].add(%*{"column": column})
|
||||
return self
|
||||
|
||||
|
||||
proc having*(self: BaradbQuery, column: string, symbol: string,
|
||||
value: string|int|float|bool): BaradbQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(CatchableError, whereSymbolsError)
|
||||
|
||||
self.placeHolder.add(%*{"key": column, "value": value})
|
||||
|
||||
if self.query.hasKey("having") == false:
|
||||
self.query["having"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["having"].add(%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc having*(self: BaradbQuery, column: string, symbol: string, value: nil.type): BaradbQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(CatchableError, whereSymbolsError)
|
||||
|
||||
if self.query.hasKey("having") == false:
|
||||
self.query["having"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["having"].add(%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc orderBy*(self: BaradbQuery, column: string, order: Order): BaradbQuery =
|
||||
if self.query.hasKey("order_by") == false:
|
||||
self.query["order_by"] = %*[{
|
||||
"column": column,
|
||||
"order": $order
|
||||
}]
|
||||
else:
|
||||
self.query["order_by"].add(%*{
|
||||
"column": column,
|
||||
"order": $order
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc limit*(self: BaradbQuery, num: int): BaradbQuery =
|
||||
self.query["limit"] = %num
|
||||
return self
|
||||
|
||||
|
||||
proc offset*(self: BaradbQuery, num: int): BaradbQuery =
|
||||
self.query["offset"] = %num
|
||||
return self
|
||||
|
||||
|
||||
proc raw*(self: BaradbConnections, sql: string, arges = newJArray()): RawBaradbQuery =
|
||||
let rawQueryRdb = RawBaradbQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
query: newJObject(),
|
||||
queryString: sql,
|
||||
placeHolder: arges,
|
||||
isInTransaction: false,
|
||||
transactionConn: 0
|
||||
)
|
||||
return rawQueryRdb
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import std/macros
|
||||
import std/strutils
|
||||
import std/strformat
|
||||
import ../../models/baradb/baradb_types
|
||||
|
||||
|
||||
when NimMajor == 1:
|
||||
macro rdbTransaction(rdb: BaradbConnections, callback: untyped): untyped =
|
||||
var callbackStr = callback.repr
|
||||
callbackStr.removePrefix
|
||||
callbackStr = callbackStr.indent(4)
|
||||
callbackStr = fmt"""
|
||||
block:
|
||||
{rdb.repr}.begin().await
|
||||
try:
|
||||
{callbackStr}
|
||||
{rdb.repr}.commit().await
|
||||
except:
|
||||
{rdb.repr}.rollback().await
|
||||
"""
|
||||
let body = callbackStr.parseStmt()
|
||||
return body
|
||||
else:
|
||||
macro rdbTransaction(rdb: BaradbConnections, callback: untyped): untyped =
|
||||
var callbackStr = callback.repr
|
||||
callbackStr.removePrefix
|
||||
callbackStr = callbackStr.indent(4)
|
||||
callbackStr = fmt"""
|
||||
block:
|
||||
{rdb.repr}.begin().await
|
||||
try:
|
||||
{callbackStr}
|
||||
{rdb.repr}.commit().await
|
||||
except DbError, CatchableError:
|
||||
{rdb.repr}.rollback().await
|
||||
"""
|
||||
let body = callbackStr.parseStmt()
|
||||
return body
|
||||
|
||||
|
||||
template transaction*(rdb: BaradbConnections, callback: untyped) =
|
||||
rdbTransaction(rdb, callback)
|
||||
@@ -0,0 +1,90 @@
|
||||
import std/asyncdispatch
|
||||
import std/deques
|
||||
import std/json
|
||||
import std/tables
|
||||
import ../../log
|
||||
import ../../libs/baradb/baradb_client
|
||||
|
||||
|
||||
type Baradb* = object
|
||||
|
||||
|
||||
const DEFAULT_CONN_MAX_LIFETIME_SECONDS* = 300
|
||||
const DEFAULT_CONN_MAX_IDLE_SECONDS* = 300
|
||||
|
||||
|
||||
type Connection* = ref object
|
||||
client*: BaraClient
|
||||
isBusy*: bool
|
||||
createdAt*: int64
|
||||
lastUsedAt*: int64
|
||||
|
||||
|
||||
type BaradbPreparedEntry* = ref object
|
||||
sql*: string
|
||||
nArgs*: int
|
||||
refCount*: int
|
||||
lastUsedAt*: int64
|
||||
|
||||
|
||||
type Connections* = ref object
|
||||
conns*: seq[Connection]
|
||||
timeout*: int
|
||||
maxConnectionLifetime*: int
|
||||
maxConnectionIdleTime*: int
|
||||
database*: string
|
||||
user*: string
|
||||
password*: string
|
||||
host*: string
|
||||
port*: int
|
||||
waiters*: Deque[Future[void]]
|
||||
columnTypeCache*: Table[string, seq[seq[string]]]
|
||||
preparedCache*: Table[string, BaradbPreparedEntry]
|
||||
|
||||
|
||||
type BaradbConnections* = ref object
|
||||
log*: LogSetting
|
||||
pools*: Connections
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
type BaradbPreparedContext* = ref object
|
||||
owner*: BaradbConnections
|
||||
connI*: int
|
||||
|
||||
|
||||
type BaradbPreparedStatement* = ref object
|
||||
owner*: BaradbConnections
|
||||
entry*: BaradbPreparedEntry
|
||||
sql*: string
|
||||
nArgs*: int
|
||||
isClosed*: bool
|
||||
|
||||
|
||||
type BaradbQuery* = ref object
|
||||
log*: LogSetting
|
||||
pools*: Connections
|
||||
query*: JsonNode
|
||||
queryString*: string
|
||||
placeHolder*: JsonNode
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
type RawBaradbQuery* = ref object
|
||||
log*: LogSetting
|
||||
pools*: Connections
|
||||
query*: JsonNode
|
||||
queryString*: string
|
||||
placeHolder*: JsonNode
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
proc `$`*(self: BaradbConnections | BaradbQuery | RawBaradbQuery): string =
|
||||
return "Baradb"
|
||||
|
||||
|
||||
proc isConnected*(self: BaradbConnections | BaradbQuery | RawBaradbQuery): bool =
|
||||
return self.pools.conns.len > 0
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
import std/json
|
||||
import ../baradb_types
|
||||
import ./baradb_generator
|
||||
|
||||
|
||||
# ==================== SELECT ====================
|
||||
|
||||
proc selectBuilder*(self: BaradbQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
|
||||
proc selectFirstBuilder*(self: BaradbQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.selectFirstSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
|
||||
proc selectFindBuilder*(self: BaradbQuery, key: string): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.selectByIdSql(key)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== INSERT ====================
|
||||
|
||||
proc insertValueBuilder*(self: BaradbQuery, items: JsonNode): string =
|
||||
return self
|
||||
.insertSql()
|
||||
.insertValueSql(items)
|
||||
.queryString
|
||||
|
||||
|
||||
proc insertValuesBuilder*(self: BaradbQuery, rows: openArray[JsonNode]): string =
|
||||
return self
|
||||
.insertSql()
|
||||
.insertValuesSql(rows)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== UPDATE ====================
|
||||
|
||||
proc updateBuilder*(self: BaradbQuery, items: JsonNode): string =
|
||||
return self
|
||||
.updateSql()
|
||||
.updateValuesSql(items)
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== DELETE ====================
|
||||
|
||||
proc deleteBuilder*(self: BaradbQuery): string =
|
||||
return self
|
||||
.deleteSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
|
||||
proc deleteByIdBuilder*(self: BaradbQuery, id: int, key: string): string =
|
||||
return self
|
||||
.deleteSql()
|
||||
.fromSql()
|
||||
.deleteByIdSql(id, key)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== Aggregates ====================
|
||||
|
||||
proc countBuilder*(self: BaradbQuery): string =
|
||||
return self
|
||||
.selectCountSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
|
||||
proc maxBuilder*(self: BaradbQuery, column: string): string =
|
||||
return self
|
||||
.selectMaxSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
|
||||
proc minBuilder*(self: BaradbQuery, column: string): string =
|
||||
return self
|
||||
.selectMinSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
|
||||
proc avgBuilder*(self: BaradbQuery, column: string): string =
|
||||
return self
|
||||
.selectAvgSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
|
||||
proc sumBuilder*(self: BaradbQuery, column: string): string =
|
||||
return self
|
||||
.selectSumSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
|
||||
proc columnBuilder*(self: BaradbQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.selectFirstSql()
|
||||
.queryString
|
||||
+397
@@ -0,0 +1,397 @@
|
||||
import std/json
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
import ../baradb_types
|
||||
|
||||
|
||||
proc quote(input:string):string =
|
||||
var tmp = newSeq[string]()
|
||||
for segment in input.split("."):
|
||||
if segment.contains(" as "):
|
||||
let parts = segment.split(" as ", maxsplit = 1)
|
||||
let expression = parts[0]
|
||||
let alias = parts[1]
|
||||
if expression.contains("("):
|
||||
let funcStart = expression.find('(')
|
||||
let funcEnd = expression.find(')', funcStart)
|
||||
let funcName = expression[0 ..< funcStart]
|
||||
let columnName = expression[funcStart + 1 ..< funcEnd]
|
||||
tmp.add(&"{funcName}(`{columnName}`) as `{alias}`")
|
||||
else:
|
||||
tmp.add(&"`{expression}` as `{alias}`")
|
||||
elif segment.contains("("):
|
||||
tmp.add(segment)
|
||||
else:
|
||||
tmp.add(&"`{segment}`")
|
||||
return tmp.join(".")
|
||||
|
||||
|
||||
# ==================== SELECT ====================
|
||||
|
||||
proc selectSql*(self: BaradbQuery): BaradbQuery =
|
||||
var queryString = ""
|
||||
|
||||
if self.query.hasKey("distinct"):
|
||||
queryString.add("SELECT DISTINCT")
|
||||
else:
|
||||
queryString.add("SELECT")
|
||||
|
||||
if self.query.hasKey("select"):
|
||||
for i, item in self.query["select"].getElems():
|
||||
if i > 0: queryString.add(",")
|
||||
var column = item.getStr()
|
||||
if column != "*": column = quote(column)
|
||||
queryString.add(&" {column}")
|
||||
else:
|
||||
queryString.add(" *")
|
||||
|
||||
self.queryString = queryString
|
||||
return self
|
||||
|
||||
|
||||
proc fromSql*(self: BaradbQuery): BaradbQuery =
|
||||
let table = self.query["table"].getStr()
|
||||
self.queryString.add(&" FROM `{table}`")
|
||||
return self
|
||||
|
||||
|
||||
proc selectFirstSql*(self: BaradbQuery): BaradbQuery =
|
||||
self.queryString.add(" LIMIT 1")
|
||||
return self
|
||||
|
||||
|
||||
proc selectByIdSql*(self: BaradbQuery, key: string): BaradbQuery =
|
||||
let key = key.quote()
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {key} = ? LIMIT 1")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {key} = ? LIMIT 1")
|
||||
return self
|
||||
|
||||
|
||||
proc joinSql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("join"):
|
||||
for row in self.query["join"]:
|
||||
let table = row["table"].getStr().quote()
|
||||
let column1 = row["column1"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
let column2 = row["column2"].getStr().quote()
|
||||
|
||||
self.queryString.add(&" INNER JOIN {table} ON {column1} {symbol} {column2}")
|
||||
return self
|
||||
|
||||
|
||||
proc leftJoinSql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("left_join"):
|
||||
for row in self.query["left_join"]:
|
||||
let table = row["table"].getStr().quote()
|
||||
let column1 = row["column1"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
let column2 = row["column2"].getStr().quote()
|
||||
|
||||
self.queryString.add(&" LEFT JOIN {table} ON {column1} {symbol} {column2}")
|
||||
return self
|
||||
|
||||
|
||||
proc whereSql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("where"):
|
||||
for i, row in self.query["where"].getElems():
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
if i == 0:
|
||||
self.queryString.add(&" WHERE {column} {symbol} ?")
|
||||
else:
|
||||
self.queryString.add(&" AND {column} {symbol} ?")
|
||||
return self
|
||||
|
||||
|
||||
proc orWhereSql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("or_where"):
|
||||
for row in self.query["or_where"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" OR {column} {symbol} ?")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} {symbol} ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetweenSql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("where_between"):
|
||||
for row in self.query["where_between"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} BETWEEN ? AND ?")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetweenStringSql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("where_between_string"):
|
||||
for row in self.query["where_between_string"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} BETWEEN ? AND ?")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetweenSql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("where_not_between"):
|
||||
for row in self.query["where_not_between"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} NOT BETWEEN ? AND ?")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} NOT BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetweenStringSql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("where_not_between_string"):
|
||||
for row in self.query["where_not_between_string"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} NOT BETWEEN ? AND ?")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} NOT BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereInSql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("where_in"):
|
||||
var widthString = ""
|
||||
for row in self.query["where_in"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
for i, val in row["width"].getElems():
|
||||
if i > 0: widthString.add(", ")
|
||||
widthString.add("?")
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} IN ({widthString})")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} IN ({widthString})")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotInSql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("where_not_in"):
|
||||
var widthString = ""
|
||||
for row in self.query["where_not_in"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
for i, val in row["width"].getElems():
|
||||
if i > 0: widthString.add(", ")
|
||||
widthString.add("?")
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} NOT IN ({widthString})")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} NOT IN ({widthString})")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNullSql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("where_null"):
|
||||
for row in self.query["where_null"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} {symbol} null")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} {symbol} null")
|
||||
return self
|
||||
|
||||
|
||||
proc groupBySql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("group_by"):
|
||||
for row in self.query["group_by"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
if self.queryString.contains("GROUP BY"):
|
||||
self.queryString.add(&", {column}")
|
||||
else:
|
||||
self.queryString.add(&" GROUP BY {column}")
|
||||
return self
|
||||
|
||||
|
||||
proc havingSql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("having"):
|
||||
for i, row in self.query["having"].getElems():
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
|
||||
if i == 0:
|
||||
self.queryString.add(&" HAVING {column} {symbol} ?")
|
||||
else:
|
||||
self.queryString.add(&" AND {column} {symbol} ?")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc orderBySql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("order_by"):
|
||||
for row in self.query["order_by"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let order = row["order"].getStr()
|
||||
|
||||
if self.queryString.contains("ORDER BY"):
|
||||
self.queryString.add(&", {column} {order}")
|
||||
else:
|
||||
self.queryString.add(&" ORDER BY {column} {order}")
|
||||
return self
|
||||
|
||||
|
||||
proc limitSql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("limit"):
|
||||
let num = self.query["limit"].getInt()
|
||||
self.queryString.add(&" LIMIT {num}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc offsetSql*(self: BaradbQuery): BaradbQuery =
|
||||
if self.query.hasKey("offset"):
|
||||
let num = self.query["offset"].getInt()
|
||||
self.queryString.add(&" OFFSET {num}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
# ==================== INSERT ====================
|
||||
|
||||
proc insertSql*(self: BaradbQuery): BaradbQuery =
|
||||
let table = self.query["table"].getStr()
|
||||
self.queryString = &"INSERT INTO `{table}`"
|
||||
return self
|
||||
|
||||
|
||||
proc insertValueSql*(self: BaradbQuery, items: JsonNode): BaradbQuery =
|
||||
var columns = ""
|
||||
var values = ""
|
||||
|
||||
var i = 0
|
||||
for key, val in items.pairs:
|
||||
defer: i += 1
|
||||
if i > 0:
|
||||
columns.add(", ")
|
||||
values.add(", ")
|
||||
columns.add(&"`{key}`")
|
||||
|
||||
self.placeHolder.add(%*{"key": key, "value": val})
|
||||
values.add("?")
|
||||
|
||||
self.queryString.add(&" ({columns}) VALUES ({values})")
|
||||
return self
|
||||
|
||||
|
||||
proc insertValuesSql*(self: BaradbQuery, rows: openArray[JsonNode]): BaradbQuery =
|
||||
var columns = ""
|
||||
|
||||
var i = 0
|
||||
for key, value in rows[0]:
|
||||
defer: i += 1
|
||||
if i > 0: columns.add(", ")
|
||||
columns.add(&"`{key}`")
|
||||
|
||||
var values = ""
|
||||
var valuesCount = 0
|
||||
for items in rows:
|
||||
var valueCount = 0
|
||||
var value = ""
|
||||
for key, val in items.pairs:
|
||||
defer: valueCount += 1
|
||||
if valueCount > 0: value.add(", ")
|
||||
|
||||
self.placeHolder.add(%*{"key": key, "value": val})
|
||||
value.add("?")
|
||||
|
||||
if valuesCount > 0: values.add(", ")
|
||||
valuesCount += 1
|
||||
values.add(&"({value})")
|
||||
|
||||
self.queryString.add(&" ({columns}) VALUES {values}")
|
||||
return self
|
||||
|
||||
|
||||
# ==================== UPDATE ====================
|
||||
|
||||
proc updateSql*(self: BaradbQuery): BaradbQuery =
|
||||
var queryString = ""
|
||||
queryString.add("UPDATE")
|
||||
|
||||
var table = self.query["table"].getStr()
|
||||
queryString.add(&" `{table}` SET")
|
||||
self.queryString = queryString
|
||||
return self
|
||||
|
||||
|
||||
proc updateValuesSql*(self: BaradbQuery, items: JsonNode): BaradbQuery =
|
||||
var value = ""
|
||||
let placeHolder = newJArray()
|
||||
|
||||
var i = 0
|
||||
for key, val in items.pairs:
|
||||
defer: i += 1
|
||||
if i > 0: value.add(",")
|
||||
value.add(&" `{key}` = ?")
|
||||
placeHolder.add(%*{"key": key, "value": val})
|
||||
|
||||
for row in self.placeHolder.items:
|
||||
placeHolder.add(row)
|
||||
|
||||
self.placeHolder = placeHolder
|
||||
|
||||
self.queryString.add(value)
|
||||
return self
|
||||
|
||||
|
||||
# ==================== DELETE ====================
|
||||
|
||||
proc deleteSql*(self: BaradbQuery): BaradbQuery =
|
||||
self.queryString = "DELETE"
|
||||
return self
|
||||
|
||||
|
||||
proc deleteByIdSql*(self: BaradbQuery, id: int, key: string): BaradbQuery =
|
||||
self.queryString.add(&" WHERE `{key}` = ?")
|
||||
return self
|
||||
|
||||
|
||||
# ==================== Aggregates ====================
|
||||
|
||||
proc selectCountSql*(self: BaradbQuery): BaradbQuery =
|
||||
let queryString =
|
||||
if self.query.hasKey("select"):
|
||||
let column = self.query["select"][0].getStr
|
||||
&"`{column}`"
|
||||
else:
|
||||
"*"
|
||||
self.queryString = &"SELECT count({queryString}) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectMaxSql*(self: BaradbQuery, column: string): BaradbQuery =
|
||||
self.queryString = &"SELECT max(`{column}`) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectMinSql*(self: BaradbQuery, column: string): BaradbQuery =
|
||||
self.queryString = &"SELECT min(`{column}`) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectAvgSql*(self: BaradbQuery, column: string): BaradbQuery =
|
||||
self.queryString = &"SELECT avg(`{column}`) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectSumSql*(self: BaradbQuery, column: string): BaradbQuery =
|
||||
self.queryString = &"SELECT sum(`{column}`) as aggregate"
|
||||
return self
|
||||
@@ -0,0 +1,71 @@
|
||||
type
|
||||
Row* = seq[string]
|
||||
|
||||
DbTypeKind* = enum ## a superset of datatypes that might be supported.
|
||||
dbUnknown, ## unknown datatype
|
||||
dbSerial, ## datatype used for primary auto-increment keys
|
||||
dbNull, ## datatype used for the NULL value
|
||||
dbBit, ## bit datatype
|
||||
dbBool, ## boolean datatype
|
||||
dbBlob, ## blob datatype
|
||||
dbFixedChar, ## string of fixed length
|
||||
dbVarchar, ## string datatype
|
||||
dbJson, ## JSON datatype
|
||||
dbXml, ## XML datatype
|
||||
dbInt, ## some integer type
|
||||
dbUInt, ## some unsigned integer type
|
||||
dbDecimal, ## decimal numbers (fixed-point number)
|
||||
dbFloat, ## some floating point type
|
||||
dbDate, ## a year-month-day description
|
||||
dbTime, ## HH:MM:SS information
|
||||
dbDatetime, ## year-month-day and HH:MM:SS information,
|
||||
## plus optional time or timezone information
|
||||
dbTimestamp, ## Timestamp values are stored as the number of seconds
|
||||
## since the epoch ('1970-01-01 00:00:00' UTC).
|
||||
dbTimeInterval, ## an interval [a,b] of times
|
||||
dbEnum, ## some enum
|
||||
dbSet, ## set of enum values
|
||||
dbArray, ## an array of values
|
||||
dbComposite, ## composite type (record, struct, etc)
|
||||
dbUrl, ## a URL
|
||||
dbUuid, ## a UUID
|
||||
dbInet, ## an IP address
|
||||
dbMacAddress, ## a MAC address
|
||||
dbGeometry, ## some geometric type
|
||||
dbPoint, ## Point on a plane (x,y)
|
||||
dbLine, ## Infinite line ((x1,y1),(x2,y2))
|
||||
dbLseg, ## Finite line segment ((x1,y1),(x2,y2))
|
||||
dbBox, ## Rectangular box ((x1,y1),(x2,y2))
|
||||
dbPath, ## Closed or open path (similar to polygon) ((x1,y1),...)
|
||||
dbPolygon, ## Polygon (similar to closed path) ((x1,y1),...)
|
||||
dbCircle, ## Circle <(x,y),r> (center point and radius)
|
||||
dbUser1, ## user definable datatype 1 (for unknown extensions)
|
||||
dbUser2, ## user definable datatype 2 (for unknown extensions)
|
||||
dbUser3, ## user definable datatype 3 (for unknown extensions)
|
||||
dbUser4, ## user definable datatype 4 (for unknown extensions)
|
||||
dbUser5 ## user definable datatype 5 (for unknown extensions)
|
||||
|
||||
DbType* = object ## describes a database type
|
||||
kind*: DbTypeKind ## the kind of the described type
|
||||
notNull*: bool ## does the type contain NULL?
|
||||
name*: string ## the name of the type
|
||||
size*: Natural ## the size of the datatype; 0 if of variable size
|
||||
maxReprLen*: Natural ## maximal length required for the representation
|
||||
precision*, scale*: Natural ## precision and scale of the number
|
||||
min*, max*: BiggestInt ## the minimum and maximum of allowed values
|
||||
validValues*: seq[string] ## valid values of an enum or a set
|
||||
|
||||
DbColumn* = object ## information about a database column
|
||||
name*: string ## name of the column
|
||||
tableName*: string ## name of the table the column belongs to (optional)
|
||||
typ*: DbType ## type of the column
|
||||
primaryKey*: bool ## is this a primary key?
|
||||
foreignKey*: bool ## is this a foreign key?
|
||||
|
||||
DbColumns* = seq[DbColumn]
|
||||
DbRows* = seq[DbColumns]
|
||||
DbEffect* = object of IOEffect ## effect that denotes a database operation
|
||||
ReadDbEffect* = object of DbEffect ## effect that denotes a read operation
|
||||
WriteDbEffect* = object of DbEffect ## effect that denotes a write operation
|
||||
|
||||
const errorConnectionNum* = 99999
|
||||
+1229
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,90 @@
|
||||
import std/asyncdispatch
|
||||
import std/deques
|
||||
import std/tables
|
||||
import std/times
|
||||
import ../../libs/database_url
|
||||
import ../../libs/mariadb/mariadb_rdb
|
||||
import ../../error
|
||||
import ../../log
|
||||
import ./mariadb_types
|
||||
|
||||
|
||||
proc dbOpen*(_: type MariaDB, database: string, user: string, password: string,
|
||||
host: string, port: int, maxConnections=1, timeout=30,
|
||||
shouldDisplayLog=false, shouldOutputLogFile=false, logDir="",
|
||||
maxConnectionLifetime=DEFAULT_CONN_MAX_LIFETIME_SECONDS,
|
||||
maxConnectionIdleTime=DEFAULT_CONN_MAX_IDLE_SECONDS): MariadbConnections =
|
||||
var conns = newSeq[Connection](maxConnections)
|
||||
for i in 0..<maxConnections:
|
||||
let conn = mariadb_rdb.init(nil)
|
||||
if conn == nil:
|
||||
mariadb_rdb.close(conn)
|
||||
dbError("mariadb_rdb.init() failed")
|
||||
if mariadb_rdb.options(conn, MYSQL_OPT_NONBLOCK, nil) != 0:
|
||||
let errmsg = $mariadb_rdb.error(conn)
|
||||
mariadb_rdb.close(conn)
|
||||
dbError(errmsg)
|
||||
if mariadb_rdb.real_connect(conn, host, user, password, database, port.int32, nil, 0) == nil:
|
||||
var errmsg = $mariadb_rdb.error(conn)
|
||||
mariadb_rdb.close(conn)
|
||||
dbError(errmsg)
|
||||
conns[i] = Connection(
|
||||
conn: conn,
|
||||
isBusy: false,
|
||||
createdAt: getTime().toUnix(),
|
||||
lastUsedAt: getTime().toUnix()
|
||||
)
|
||||
let pools = Connections(
|
||||
conns: conns,
|
||||
timeout: timeout,
|
||||
maxConnectionLifetime: maxConnectionLifetime,
|
||||
maxConnectionIdleTime: maxConnectionIdleTime,
|
||||
info: ConnectionInfo(
|
||||
database: database,
|
||||
user: user,
|
||||
password: password,
|
||||
host: host,
|
||||
port: port
|
||||
),
|
||||
waiters: initDeque[Future[void]](),
|
||||
columnTypeCache: initTable[string, seq[seq[string]]](),
|
||||
preparedCache: initTable[string, MariadbPreparedEntry](),
|
||||
)
|
||||
result = MariadbConnections(
|
||||
pools: pools,
|
||||
info: pools.info,
|
||||
log: LogSetting(shouldDisplayLog:shouldDisplayLog, shouldOutputLogFile:shouldOutputLogFile, logDir:logDir)
|
||||
)
|
||||
|
||||
|
||||
proc dbOpen*(_:type MariaDB, url: string, maxConnections: int = 1, timeout=30,
|
||||
shouldDisplayLog=false, shouldOutputLogFile=false, logDir="",
|
||||
maxConnectionLifetime=DEFAULT_CONN_MAX_LIFETIME_SECONDS,
|
||||
maxConnectionIdleTime=DEFAULT_CONN_MAX_IDLE_SECONDS): MariadbConnections =
|
||||
return dbOpen(MariaDB, asDatabaseUrl(url), maxConnections, timeout, shouldDisplayLog, shouldOutputLogFile, logDir, maxConnectionLifetime, maxConnectionIdleTime)
|
||||
|
||||
|
||||
proc dbOpen*(_:type MariaDB, databaseUrl: DatabaseUrl, maxConnections=1, timeout=30,
|
||||
shouldDisplayLog=false, shouldOutputLogFile=false, logDir="",
|
||||
maxConnectionLifetime=DEFAULT_CONN_MAX_LIFETIME_SECONDS,
|
||||
maxConnectionIdleTime=DEFAULT_CONN_MAX_IDLE_SECONDS): MariadbConnections =
|
||||
let parsed = parseDatabaseUrl(databaseUrl)
|
||||
requireDatabaseUrlScheme(parsed, ["mariadb"], "MariaDB")
|
||||
|
||||
let database = databaseName(parsed)
|
||||
let port = portOrDefault(parsed, 3306)
|
||||
return dbOpen(
|
||||
MariaDB,
|
||||
database,
|
||||
parsed.username,
|
||||
parsed.password,
|
||||
parsed.hostname,
|
||||
port,
|
||||
maxConnections,
|
||||
timeout,
|
||||
shouldDisplayLog,
|
||||
shouldOutputLogFile,
|
||||
logDir,
|
||||
maxConnectionLifetime,
|
||||
maxConnectionIdleTime
|
||||
)
|
||||
+439
@@ -0,0 +1,439 @@
|
||||
import std/asyncdispatch
|
||||
import std/json
|
||||
import std/options
|
||||
import std/strutils
|
||||
import std/times
|
||||
import ../../libs/mariadb/mariadb_impl
|
||||
import ../../enums
|
||||
import ./mariadb_types
|
||||
|
||||
|
||||
proc select*(self:MariadbConnections, columnsArg:varargs[string]):MariadbQuery =
|
||||
let query = newJObject()
|
||||
|
||||
if columnsArg.len == 0:
|
||||
query["select"] = %["*"]
|
||||
else:
|
||||
query["select"] = %columnsArg
|
||||
|
||||
let MariadbQuery = MariadbQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
info: self.info,
|
||||
query: query,
|
||||
queryString: "",
|
||||
placeHolder: newJArray(),
|
||||
isInTransaction: self.isInTransaction,
|
||||
transactionConn: self.transactionConn,
|
||||
)
|
||||
return MariadbQuery
|
||||
|
||||
|
||||
proc table*(self:MariadbConnections, tableArg: string): MariadbQuery =
|
||||
let query = newJObject()
|
||||
query["table"] = %tableArg
|
||||
|
||||
let MariadbQuery = MariadbQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
query: query,
|
||||
queryString: "",
|
||||
placeHolder: newJArray(),
|
||||
isInTransaction: self.isInTransaction,
|
||||
transactionConn: self.transactionConn,
|
||||
)
|
||||
return MariadbQuery
|
||||
|
||||
|
||||
proc table*(self:MariadbQuery, tableArg: string): MariadbQuery =
|
||||
self.query["table"] = %tableArg
|
||||
return self
|
||||
|
||||
|
||||
proc `distinct`*(self: MariadbQuery): MariadbQuery =
|
||||
self.query["distinct"] = %true
|
||||
return self
|
||||
|
||||
# ============================== Conditions ==============================
|
||||
|
||||
proc join*(self: MariadbQuery, table: string, column1: string, symbol: string,
|
||||
column2: string): MariadbQuery =
|
||||
if self.query.hasKey("join") == false:
|
||||
self.query["join"] = %*[{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
}]
|
||||
else:
|
||||
self.query["join"].add(%*{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc leftJoin*(self: MariadbQuery, table: string, column1: string, symbol: string,
|
||||
column2: string): MariadbQuery =
|
||||
if self.query.hasKey("left_join") == false:
|
||||
self.query["left_join"] = %*[{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
}]
|
||||
else:
|
||||
self.query["left_join"].add(%*{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
const whereSymbols = ["is", "is not", "=", "!=", "<", "<=", ">=", ">", "<>", "LIKE","%LIKE","LIKE%","%LIKE%"]
|
||||
const whereSymbolsError = """Arg position 3 is only allowed of ["is", "is not", "=", "!=", "<", "<=", ">=", ">", "<>", "LIKE","%LIKE","LIKE%","%LIKE%"]"""
|
||||
|
||||
proc where*(self: MariadbQuery, column: string, symbol: string,
|
||||
value: string|int|float): MariadbQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key": column, "value": value})
|
||||
|
||||
if self.query.hasKey("where") == false:
|
||||
self.query["where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc where*(self: MariadbQuery, column: string, symbol: string,
|
||||
value: bool): MariadbQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key":column, "value":value})
|
||||
|
||||
if self.query.hasKey("where") == false:
|
||||
self.query["where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc where*(self: MariadbQuery, column: string, symbol: string, value: nil.type): MariadbQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("where_null") == false:
|
||||
self.query["where_null"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["where_null"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc orWhere*(self: MariadbQuery, column: string, symbol: string,
|
||||
value: string|int|float|bool): MariadbQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key":column, "value":value})
|
||||
|
||||
if self.query.hasKey("or_where") == false:
|
||||
self.query["or_where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["or_where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc orWhere*(self: MariadbQuery, column: string, symbol: string, value: nil.type): MariadbQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("or_where") == false:
|
||||
self.query["or_where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["or_where"].add(%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetween*(self:MariadbQuery, column:string, width:array[2, int|float]): MariadbQuery =
|
||||
self.placeHolder.add(%*{"key": column, "value": width[0]})
|
||||
self.placeHolder.add(%*{"key": column, "value": width[1]})
|
||||
|
||||
if self.query.hasKey("where_between") == false:
|
||||
self.query["where_between"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_between"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetween*(self:MariadbQuery, column:string, width:array[2, string]): MariadbQuery =
|
||||
self.placeHolder.add(%*{"key": column, "value": width[0]})
|
||||
self.placeHolder.add(%*{"key": column, "value": width[1]})
|
||||
|
||||
if self.query.hasKey("where_between_string") == false:
|
||||
self.query["where_between_string"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_between_string"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetween*(self:MariadbQuery, column:string, width:array[2, int|float]): MariadbQuery =
|
||||
self.placeHolder.add(%*{"key": column, "value": width[0]})
|
||||
self.placeHolder.add(%*{"key": column, "value": width[1]})
|
||||
|
||||
if self.query.hasKey("where_not_between") == false:
|
||||
self.query["where_not_between"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_between"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetween*(self:MariadbQuery, column:string, width:array[2, string]): MariadbQuery =
|
||||
self.placeHolder.add(%*{"key": column, "value": width[0]})
|
||||
self.placeHolder.add(%*{"key": column, "value": width[1]})
|
||||
|
||||
if self.query.hasKey("where_not_between_string") == false:
|
||||
self.query["where_not_between_string"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_between_string"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereIn*(self:MariadbQuery, column:string, width:seq[int|float|string]): MariadbQuery =
|
||||
for row in width:
|
||||
self.placeHolder.add(%*{"key": column, "value": row})
|
||||
|
||||
if self.query.hasKey("where_in") == false:
|
||||
self.query["where_in"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_in"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotIn*(self:MariadbQuery, column:string, width:seq[int|float|string]): MariadbQuery =
|
||||
for row in width:
|
||||
self.placeHolder.add(%*{"key": column, "value": row})
|
||||
|
||||
if self.query.hasKey("where_not_in") == false:
|
||||
self.query["where_not_in"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_in"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNull*(self:MariadbQuery, column:string): MariadbQuery =
|
||||
if self.query.hasKey("where_null") == false:
|
||||
self.query["where_null"] = %*[{
|
||||
"column": column,
|
||||
"symbol": "is",
|
||||
}]
|
||||
else:
|
||||
self.query["where_null"].add(%*{
|
||||
"column": column,
|
||||
"symbol": "is",
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc groupBy*(self:MariadbQuery, column:string): MariadbQuery =
|
||||
if self.query.hasKey("group_by") == false:
|
||||
self.query["group_by"] = %*[{"column": column}]
|
||||
else:
|
||||
self.query["group_by"].add(%*{"column": column})
|
||||
return self
|
||||
|
||||
|
||||
proc having*(self: MariadbQuery, column: string, symbol: string,
|
||||
value: string|int|float|bool): MariadbQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key":column, "value":value})
|
||||
|
||||
if self.query.hasKey("having") == false:
|
||||
self.query["having"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["having"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
proc having*(self: MariadbQuery, column: string, symbol: string, value: nil.type): MariadbQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("having") == false:
|
||||
self.query["having"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["having"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc orderBy*(self:MariadbQuery, column:string, order:Order): MariadbQuery =
|
||||
if self.query.hasKey("order_by") == false:
|
||||
self.query["order_by"] = %*[{
|
||||
"column": column,
|
||||
"order": $order
|
||||
}]
|
||||
else:
|
||||
self.query["order_by"].add(%*{
|
||||
"column": column,
|
||||
"order": $order
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc limit*(self: MariadbQuery, num: int): MariadbQuery =
|
||||
self.query["limit"] = %num
|
||||
return self
|
||||
|
||||
|
||||
proc offset*(self: MariadbQuery, num: int): MariadbQuery =
|
||||
self.query["offset"] = %num
|
||||
return self
|
||||
|
||||
|
||||
proc raw*(self:MariadbConnections, sql:string, arges=newJArray()): RawMariadbQuery =
|
||||
## arges is `JArray` `[true, 1, 1.1, "str"]`
|
||||
##
|
||||
## can't use BLOB data.
|
||||
let rawQueryRdb = RawMariadbQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
info: self.info,
|
||||
query: newJObject(),
|
||||
queryString: sql.strip(),
|
||||
placeHolder: arges,
|
||||
isInTransaction: false,
|
||||
transactionConn: 0
|
||||
)
|
||||
return rawQueryRdb
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import std/macros
|
||||
import std/strutils
|
||||
import std/strformat
|
||||
import ../../models/mariadb/mariadb_types
|
||||
|
||||
|
||||
when NimMajor == 1:
|
||||
macro rdbTransaction(rdb:MariadbConnections, callback: untyped):untyped =
|
||||
var callbackStr = callback.repr
|
||||
callbackStr.removePrefix
|
||||
callbackStr = callbackStr.indent(4)
|
||||
callbackStr = fmt"""
|
||||
block:
|
||||
{rdb.repr}.begin().await
|
||||
try:
|
||||
{callbackStr}
|
||||
{rdb.repr}.commit().await
|
||||
except DbError:
|
||||
{rdb.repr}.rollback().await
|
||||
"""
|
||||
let body = callbackStr.parseStmt()
|
||||
return body
|
||||
else:
|
||||
macro rdbTransaction(rdb:MariadbConnections, callback: untyped):untyped =
|
||||
var callbackStr = callback.repr
|
||||
callbackStr.removePrefix
|
||||
callbackStr = callbackStr.indent(4)
|
||||
callbackStr = fmt"""
|
||||
block:
|
||||
{rdb.repr}.begin().await
|
||||
try:
|
||||
{callbackStr}
|
||||
{rdb.repr}.commit().await
|
||||
except CatchableError:
|
||||
{rdb.repr}.rollback().await
|
||||
"""
|
||||
let body = callbackStr.parseStmt()
|
||||
return body
|
||||
|
||||
|
||||
template transaction*(rdb:MariadbConnections, callback: untyped) =
|
||||
rdbTransaction(rdb, callback)
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
import std/asyncdispatch
|
||||
import std/deques
|
||||
import std/json
|
||||
import std/tables
|
||||
import ../../log
|
||||
import ../../libs/mariadb/mariadb_rdb
|
||||
|
||||
|
||||
type MariaDB* = object
|
||||
|
||||
|
||||
const DEFAULT_CONN_MAX_LIFETIME_SECONDS* = 300
|
||||
const DEFAULT_CONN_MAX_IDLE_SECONDS* = 300
|
||||
|
||||
|
||||
type ConnectionInfo* = object
|
||||
database*:string
|
||||
user*:string
|
||||
password*:string
|
||||
host*:string
|
||||
port*:int
|
||||
|
||||
|
||||
type Connection* = object
|
||||
conn*: PMySQL
|
||||
isBusy*: bool
|
||||
createdAt*: int64
|
||||
lastUsedAt*: int64
|
||||
|
||||
|
||||
type MariadbPreparedEntry* = ref object
|
||||
sql*: string
|
||||
nArgs*: int
|
||||
stmts*: seq[PSTMT]
|
||||
refCount*: int
|
||||
lastUsedAt*: int64
|
||||
|
||||
|
||||
type Connections* = ref object
|
||||
conns*: seq[Connection]
|
||||
timeout*:int
|
||||
maxConnectionLifetime*: int
|
||||
maxConnectionIdleTime*: int
|
||||
info*: ConnectionInfo
|
||||
waiters*: Deque[Future[void]]
|
||||
columnTypeCache*: Table[string, seq[seq[string]]]
|
||||
preparedCache*: Table[string, MariadbPreparedEntry]
|
||||
|
||||
|
||||
## created by `let rdb = dbOpen(MySQL, "localhost", 3306)`
|
||||
type MariadbConnections* = ref object
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
info*:ConnectionInfo
|
||||
# for transaction
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
type MariadbPreparedContext* = ref object
|
||||
owner*: MariadbConnections
|
||||
connI*: int
|
||||
|
||||
|
||||
## created by `rdb.select("columnName")` or `rdb.table("tableName")`
|
||||
type MariadbQuery* = ref object
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
info*:ConnectionInfo
|
||||
query*: JsonNode
|
||||
queryString*: string
|
||||
placeHolder*: JsonNode # [{"key":"user", "value":"user1"}]
|
||||
# for transaction
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
type RawMariadbQuery* = ref object
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
info*:ConnectionInfo
|
||||
query*: JsonNode
|
||||
queryString*: string
|
||||
placeHolder*: JsonNode # ["user1", "user1@example.com"]
|
||||
# for transaction
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
type MariadbResultBindCache* = ref object
|
||||
binds*: seq[BIND]
|
||||
buffers*: seq[string]
|
||||
lengths*: seq[culong]
|
||||
nullFlags*: seq[my_bool]
|
||||
errorFlags*: seq[my_bool]
|
||||
|
||||
|
||||
type MariadbPreparedStatement* = ref object
|
||||
owner*: MariadbConnections
|
||||
info*: ConnectionInfo
|
||||
entry*: MariadbPreparedEntry
|
||||
sql*: string
|
||||
nArgs*: int
|
||||
isClosed*: bool
|
||||
resultBindCache*: seq[MariadbResultBindCache]
|
||||
|
||||
|
||||
|
||||
|
||||
proc `$`*(self:MariadbConnections|MariadbQuery|RawMariadbQuery):string =
|
||||
return "MariaDB"
|
||||
|
||||
|
||||
proc isConnected*(self:MariadbConnections|MariadbQuery|RawMariadbQuery):bool =
|
||||
return self.pools.conns.len > 0
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
import std/json
|
||||
import ../mariadb_types
|
||||
import ./mariadb_generator
|
||||
|
||||
|
||||
# ==================== SELECT ====================
|
||||
|
||||
proc selectBuilder*(self: MariadbQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc selectFirstBuilder*(self: MariadbQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.selectFirstSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc selectFindBuilder*(self: MariadbQuery, key: string): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.selectByIdSql(key)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== INSERT ====================
|
||||
|
||||
proc insertValueBuilder*(self: MariadbQuery, items: JsonNode): string =
|
||||
return self
|
||||
.insertSql()
|
||||
.insertValueSql(items)
|
||||
.queryString
|
||||
|
||||
proc insertValuesBuilder*(self: MariadbQuery, rows: openArray[JsonNode]): string =
|
||||
return self
|
||||
.insertSql()
|
||||
.insertValuesSql(rows)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== UPDATE ====================
|
||||
|
||||
proc updateBuilder*(self: MariadbQuery, items: JsonNode): string =
|
||||
return self
|
||||
.updateSql()
|
||||
.updateValuesSql(items)
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== DELETE ====================
|
||||
|
||||
proc deleteBuilder*(self: MariadbQuery): string =
|
||||
return self
|
||||
.deleteSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc deleteByIdBuilder*(self: MariadbQuery, id: int, key: string): string =
|
||||
return self
|
||||
.deleteSql()
|
||||
.fromSql()
|
||||
.deleteByIdSql(id, key)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== Aggregates ====================
|
||||
|
||||
proc countBuilder*(self:MariadbQuery): string =
|
||||
return self
|
||||
.selectCountSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc maxBuilder*(self:MariadbQuery, column:string): string =
|
||||
return self
|
||||
.selectMaxSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc minBuilder*(self:MariadbQuery, column:string): string =
|
||||
return self
|
||||
.selectMinSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc avgBuilder*(self:MariadbQuery, column:string): string =
|
||||
return self
|
||||
.selectAvgSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc sumBuilder*(self:MariadbQuery, column:string): string =
|
||||
return self
|
||||
.selectSumSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc columnBuilder*(self:MariadbQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.selectFirstSql()
|
||||
.queryString
|
||||
+429
@@ -0,0 +1,429 @@
|
||||
import std/json
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
# import ../../database_types
|
||||
import ../mariadb_types
|
||||
|
||||
|
||||
proc quote(input:string):string =
|
||||
## "User.id as userId" => ```User```.```id``` as ```userId```
|
||||
##
|
||||
## "COUNT(id) as count" => ```COUNT(id)``` as ```count```
|
||||
##
|
||||
## "MAX(id) as maxId" => ```MAX(id)``` as ```maxId```
|
||||
##
|
||||
var tmp = newSeq[string]()
|
||||
for segment in input.split("."):
|
||||
if segment.contains(" as "):
|
||||
# Split once on ' as ' to separate the expression and its alias
|
||||
let parts = segment.split(" as ", maxsplit = 1)
|
||||
let expression = parts[0]
|
||||
let alias = parts[1]
|
||||
if expression.contains("("):
|
||||
# Extract function name and column name using index-based slicing
|
||||
let funcStart = expression.find('(')
|
||||
let funcEnd = expression.find(')', funcStart)
|
||||
let funcName = expression[0 ..< funcStart]
|
||||
let columnName = expression[funcStart + 1 ..< funcEnd]
|
||||
tmp.add(&"{funcName}(`{columnName}`) as `{alias}`")
|
||||
else:
|
||||
# Quote the expression and alias
|
||||
tmp.add(&"`{expression}` as `{alias}`")
|
||||
elif segment.contains("("):
|
||||
# Leave functions without alias as is
|
||||
tmp.add(segment)
|
||||
else:
|
||||
# Quote standalone identifiers
|
||||
tmp.add(&"`{segment}`")
|
||||
return tmp.join(".")
|
||||
|
||||
|
||||
# ==================== SELECT ====================
|
||||
|
||||
proc selectSql*(self: MariadbQuery): MariadbQuery =
|
||||
var queryString = ""
|
||||
|
||||
if self.query.hasKey("distinct"):
|
||||
queryString.add("SELECT DISTINCT")
|
||||
else:
|
||||
queryString.add("SELECT")
|
||||
|
||||
if self.query.hasKey("select"):
|
||||
for i, item in self.query["select"].getElems():
|
||||
if i > 0: queryString.add(",")
|
||||
var column = item.getStr()
|
||||
if column != "*": column = quote(column)
|
||||
queryString.add(&" {column}")
|
||||
else:
|
||||
queryString.add(" *")
|
||||
|
||||
self.queryString = queryString
|
||||
return self
|
||||
|
||||
|
||||
proc fromSql*(self: MariadbQuery): MariadbQuery =
|
||||
let table = self.query["table"].getStr()
|
||||
self.queryString.add(&" FROM `{table}`")
|
||||
return self
|
||||
|
||||
|
||||
proc selectFirstSql*(self: MariadbQuery): MariadbQuery =
|
||||
self.queryString.add(" LIMIT 1")
|
||||
return self
|
||||
|
||||
|
||||
proc selectByIdSql*(self: MariadbQuery, key:string): MariadbQuery =
|
||||
let key = key.quote()
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {key} = ? LIMIT 1")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {key} = ? LIMIT 1")
|
||||
return self
|
||||
|
||||
|
||||
proc joinSql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("join"):
|
||||
for row in self.query["join"]:
|
||||
let table = row["table"].getStr().quote()
|
||||
let column1 = row["column1"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
let column2 = row["column2"].getStr().quote()
|
||||
|
||||
self.queryString.add(&" INNER JOIN {table} ON {column1} {symbol} {column2}")
|
||||
return self
|
||||
|
||||
|
||||
proc leftJoinSql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("left_join"):
|
||||
for row in self.query["left_join"]:
|
||||
let table = row["table"].getStr().quote()
|
||||
let column1 = row["column1"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
let column2 = row["column2"].getStr().quote()
|
||||
|
||||
self.queryString.add(&" LEFT JOIN {table} ON {column1} {symbol} {column2}")
|
||||
return self
|
||||
|
||||
|
||||
proc whereSql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("where"):
|
||||
for i, row in self.query["where"].getElems():
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
# let value = row["value"].getStr()
|
||||
if i == 0:
|
||||
self.queryString.add(&" WHERE {column} {symbol} ?")
|
||||
else:
|
||||
self.queryString.add(&" AND {column} {symbol} ?")
|
||||
return self
|
||||
|
||||
|
||||
proc orWhereSql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("or_where"):
|
||||
for row in self.query["or_where"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
# let value = row["value"].getStr()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" OR {column} {symbol} ?")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} {symbol} ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetweenSql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("where_between"):
|
||||
for row in self.query["where_between"]:
|
||||
let column = row["column"].getStr()
|
||||
# let start = row["width"][0].getFloat()
|
||||
# let stop = row["width"][1].getFloat()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
# self.queryString.add(&" AND `{column}` BETWEEN {start} AND {stop}")
|
||||
self.queryString.add(&" AND `{column}` BETWEEN ? AND ?")
|
||||
else:
|
||||
# self.queryString.add(&" WHERE `{column}` BETWEEN {start} AND {stop}")
|
||||
self.queryString.add(&" WHERE `{column}` BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetweenStringSql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("where_between_string"):
|
||||
for row in self.query["where_between_string"]:
|
||||
let column = row["column"].getStr()
|
||||
# let start = row["width"][0].getStr
|
||||
# let stop = row["width"][1].getStr
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
# self.queryString.add(&" AND `{column}` BETWEEN '{start}' AND '{stop}'")
|
||||
self.queryString.add(&" AND `{column}` BETWEEN ? AND ?")
|
||||
else:
|
||||
# self.queryString.add(&" WHERE `{column}` BETWEEN '{start}' AND '{stop}'")
|
||||
self.queryString.add(&" WHERE `{column}` BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetweenSql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("where_not_between"):
|
||||
for row in self.query["where_not_between"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
# let start = row["width"][0].getFloat()
|
||||
# let stop = row["width"][1].getFloat()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
# self.queryString.add(&" AND `{column}` NOT BETWEEN {start} AND {stop}")
|
||||
self.queryString.add(&" AND {column} NOT BETWEEN ? AND ?")
|
||||
else:
|
||||
# self.queryString.add(&" WHERE `{column}` NOT BETWEEN {start} AND {stop}")
|
||||
self.queryString.add(&" WHERE {column} NOT BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetweenStringSql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("where_not_between_string"):
|
||||
for row in self.query["where_not_between_string"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
# let start = row["width"][0].getStr
|
||||
# let stop = row["width"][1].getStr
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
# self.queryString.add(&" AND `{column}` NOT BETWEEN '{start}' AND '{stop}'")
|
||||
self.queryString.add(&" AND {column} NOT BETWEEN ? AND ?")
|
||||
else:
|
||||
# self.queryString.add(&" WHERE `{column}` NOT BETWEEN '{start}' AND '{stop}'")
|
||||
self.queryString.add(&" WHERE {column} NOT BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereInSql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("where_in"):
|
||||
var widthString = ""
|
||||
for row in self.query["where_in"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
for i, val in row["width"].getElems():
|
||||
if i > 0: widthString.add(", ")
|
||||
widthString.add("?")
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} IN ({widthString})")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} IN ({widthString})")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotInSql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("where_not_in"):
|
||||
var widthString = ""
|
||||
for row in self.query["where_not_in"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
for i, val in row["width"].getElems():
|
||||
if i > 0: widthString.add(", ")
|
||||
widthString.add("?")
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} NOT IN ({widthString})")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} NOT IN ({widthString})")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNullSql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("where_null"):
|
||||
for row in self.query["where_null"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} {symbol} NULL")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} {symbol} NULL")
|
||||
return self
|
||||
|
||||
|
||||
proc groupBySql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("group_by"):
|
||||
for row in self.query["group_by"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
if self.queryString.contains("GROUP BY"):
|
||||
self.queryString.add(&", {column}")
|
||||
else:
|
||||
self.queryString.add(&" GROUP BY {column}")
|
||||
return self
|
||||
|
||||
|
||||
proc havingSql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("having"):
|
||||
for i, row in self.query["having"].getElems():
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
# let value = row["value"].getStr()
|
||||
|
||||
if i == 0:
|
||||
self.queryString.add(&" HAVING {column} {symbol} ?")
|
||||
else:
|
||||
self.queryString.add(&" AND {column} {symbol} ?")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc orderBySql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("order_by"):
|
||||
for row in self.query["order_by"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let order = row["order"].getStr()
|
||||
|
||||
if self.queryString.contains("ORDER BY"):
|
||||
self.queryString.add(&", {column} {order}")
|
||||
else:
|
||||
self.queryString.add(&" ORDER BY {column} {order}")
|
||||
return self
|
||||
|
||||
|
||||
proc limitSql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("limit"):
|
||||
let num = self.query["limit"].getInt()
|
||||
self.queryString.add(&" LIMIT {num}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc offsetSql*(self: MariadbQuery): MariadbQuery =
|
||||
if self.query.hasKey("offset"):
|
||||
let num = self.query["offset"].getInt()
|
||||
self.queryString.add(&" OFFSET {num}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
# ==================== INSERT ====================
|
||||
|
||||
proc insertSql*(self: MariadbQuery): MariadbQuery =
|
||||
let table = self.query["table"].getStr()
|
||||
self.queryString = &"INSERT INTO `{table}`"
|
||||
return self
|
||||
|
||||
|
||||
proc insertValueSql*(self: MariadbQuery, items: JsonNode): MariadbQuery =
|
||||
var columns = ""
|
||||
var values = ""
|
||||
|
||||
var i = 0
|
||||
for key, val in items.pairs:
|
||||
defer: i += 1
|
||||
if i > 0:
|
||||
columns.add(", ")
|
||||
values.add(", ")
|
||||
# If column name contains Upper letter, column name is covered by double quote
|
||||
columns.add(&"`{key}`")
|
||||
|
||||
self.placeHolder.add(%*{"key":key, "value":val})
|
||||
values.add("?")
|
||||
|
||||
self.queryString.add(&" ({columns}) VALUES ({values})")
|
||||
return self
|
||||
|
||||
|
||||
proc insertValuesSql*(self: MariadbQuery, rows: openArray[JsonNode]): MariadbQuery =
|
||||
var columns = ""
|
||||
|
||||
var i = 0
|
||||
for key, value in rows[0]:
|
||||
defer: i += 1
|
||||
if i > 0: columns.add(", ")
|
||||
# If column name contains Upper letter, column name is covered by double quote
|
||||
columns.add(&"`{key}`")
|
||||
|
||||
var values = ""
|
||||
var valuesCount = 0
|
||||
for items in rows:
|
||||
var valueCount = 0
|
||||
var value = ""
|
||||
for key, val in items.pairs:
|
||||
defer: valueCount += 1
|
||||
if valueCount > 0: value.add(", ")
|
||||
|
||||
self.placeHolder.add(%*{"key":key, "value":val})
|
||||
value.add("?")
|
||||
|
||||
if valuesCount > 0: values.add(", ")
|
||||
valuesCount += 1
|
||||
values.add(&"({value})")
|
||||
|
||||
self.queryString.add(&" ({columns}) VALUES {values}")
|
||||
return self
|
||||
|
||||
|
||||
# ==================== UPDATE ====================
|
||||
|
||||
proc updateSql*(self: MariadbQuery): MariadbQuery =
|
||||
var queryString = ""
|
||||
queryString.add("UPDATE")
|
||||
|
||||
var table = self.query["table"].getStr()
|
||||
queryString.add(&" `{table}` SET")
|
||||
self.queryString = queryString
|
||||
return self
|
||||
|
||||
|
||||
proc updateValuesSql*(self: MariadbQuery, items:JsonNode): MariadbQuery =
|
||||
var value = ""
|
||||
let placeHolder = newJArray()
|
||||
|
||||
var i = 0
|
||||
for key, val in items.pairs:
|
||||
defer: i += 1
|
||||
if i > 0: value.add(",")
|
||||
value.add(&" `{key}` = ?")
|
||||
placeHolder.add(%*{"key":key, "value":val})
|
||||
|
||||
for row in self.placeHolder.items:
|
||||
placeHolder.add(row)
|
||||
|
||||
self.placeHolder = placeHolder
|
||||
|
||||
self.queryString.add(value)
|
||||
return self
|
||||
|
||||
|
||||
# ==================== DELETE ====================
|
||||
|
||||
proc deleteSql*(self: MariadbQuery): MariadbQuery =
|
||||
self.queryString = "DELETE"
|
||||
return self
|
||||
|
||||
|
||||
proc deleteByIdSql*(self: MariadbQuery, id: int, key: string): MariadbQuery =
|
||||
self.queryString.add(&" WHERE `{key}` = ?")
|
||||
return self
|
||||
|
||||
# ==================== Aggregates ====================
|
||||
|
||||
proc selectCountSql*(self: MariadbQuery): MariadbQuery =
|
||||
let queryString =
|
||||
if self.query.hasKey("select"):
|
||||
let column = self.query["select"][0].getStr
|
||||
&"`{column}`"
|
||||
else:
|
||||
"*"
|
||||
self.queryString = &"SELECT count({queryString}) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectMaxSql*(self: MariadbQuery, column:string): MariadbQuery =
|
||||
self.queryString = &"SELECT max(`{column}`) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectMinSql*(self: MariadbQuery, column:string): MariadbQuery =
|
||||
self.queryString = &"SELECT min(`{column}`) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectAvgSql*(self: MariadbQuery, column:string): MariadbQuery =
|
||||
self.queryString = &"SELECT avg(`{column}`) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectSumSql*(self: MariadbQuery, column:string): MariadbQuery =
|
||||
self.queryString = &"SELECT sum(`{column}`) as aggregate"
|
||||
return self
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,89 @@
|
||||
import std/tables
|
||||
import std/times
|
||||
import ../../libs/database_url
|
||||
import ../../libs/mysql/mysql_rdb
|
||||
import ../../error
|
||||
import ../../log
|
||||
import ./mysql_types
|
||||
|
||||
|
||||
proc dbOpen*(_:type MySQL, database: string, user: string, password: string,
|
||||
host: string, port: int, maxConnections: int = 1, timeout=30,
|
||||
shouldDisplayLog=false, shouldOutputLogFile=false, logDir="",
|
||||
maxConnectionLifetime=DEFAULT_CONN_MAX_LIFETIME_SECONDS,
|
||||
maxConnectionIdleTime=DEFAULT_CONN_MAX_IDLE_SECONDS): MysqlConnections =
|
||||
var conns = newSeq[Connection](maxConnections)
|
||||
for i in 0..<maxConnections:
|
||||
let conn = mysql_rdb.init(nil)
|
||||
if conn == nil:
|
||||
mysql_rdb.close(conn)
|
||||
dbError("mysql_rdb.init() failed")
|
||||
if mysql_rdb.real_connect(conn, host, user, password, database, port.int32, nil, 0) == nil:
|
||||
var errmsg = $mysql_rdb.error(conn)
|
||||
mysql_rdb.close(conn)
|
||||
let info = {
|
||||
"database": database,
|
||||
"user": user,
|
||||
"password": password,
|
||||
"host": host,
|
||||
"port": $port
|
||||
}
|
||||
dbError(errmsg & $info)
|
||||
conns[i] = Connection(
|
||||
conn: conn,
|
||||
isBusy: false,
|
||||
createdAt: getTime().toUnix(),
|
||||
lastUsedAt: getTime().toUnix()
|
||||
)
|
||||
let pools = Connections(
|
||||
conns: conns,
|
||||
timeout: timeout,
|
||||
maxConnectionLifetime: maxConnectionLifetime,
|
||||
maxConnectionIdleTime: maxConnectionIdleTime,
|
||||
info: ConnectionInfo(
|
||||
database: database,
|
||||
user: user,
|
||||
password: password,
|
||||
host: host,
|
||||
port: port
|
||||
),
|
||||
preparedCache: initTable[string, MysqlPreparedEntry](),
|
||||
)
|
||||
result = MysqlConnections(
|
||||
pools: pools,
|
||||
info: pools.info,
|
||||
log: LogSetting(shouldDisplayLog:shouldDisplayLog, shouldOutputLogFile:shouldOutputLogFile, logDir:logDir)
|
||||
)
|
||||
|
||||
|
||||
proc dbOpen*(_:type MySQL, url: string, maxConnections=1, timeout=30,
|
||||
shouldDisplayLog=false, shouldOutputLogFile=false, logDir="",
|
||||
maxConnectionLifetime=DEFAULT_CONN_MAX_LIFETIME_SECONDS,
|
||||
maxConnectionIdleTime=DEFAULT_CONN_MAX_IDLE_SECONDS): MysqlConnections =
|
||||
return dbOpen(MySQL, asDatabaseUrl(url), maxConnections, timeout, shouldDisplayLog, shouldOutputLogFile, logDir, maxConnectionLifetime, maxConnectionIdleTime)
|
||||
|
||||
|
||||
proc dbOpen*(_:type MySQL, databaseUrl: DatabaseUrl, maxConnections=1, timeout=30,
|
||||
shouldDisplayLog=false, shouldOutputLogFile=false, logDir="",
|
||||
maxConnectionLifetime=DEFAULT_CONN_MAX_LIFETIME_SECONDS,
|
||||
maxConnectionIdleTime=DEFAULT_CONN_MAX_IDLE_SECONDS): MysqlConnections =
|
||||
let parsed = parseDatabaseUrl(databaseUrl)
|
||||
requireDatabaseUrlScheme(parsed, ["mysql"], "MySQL")
|
||||
|
||||
let database = databaseName(parsed)
|
||||
let port = portOrDefault(parsed, 3306)
|
||||
return dbOpen(
|
||||
MySQL,
|
||||
database,
|
||||
parsed.username,
|
||||
parsed.password,
|
||||
parsed.hostname,
|
||||
port,
|
||||
maxConnections,
|
||||
timeout,
|
||||
shouldDisplayLog,
|
||||
shouldOutputLogFile,
|
||||
logDir,
|
||||
maxConnectionLifetime,
|
||||
maxConnectionIdleTime
|
||||
)
|
||||
@@ -0,0 +1,438 @@
|
||||
import std/asyncdispatch
|
||||
import std/json
|
||||
import std/options
|
||||
import std/strutils
|
||||
import std/times
|
||||
import ../../libs/mysql/mysql_impl
|
||||
import ../../enums
|
||||
import ./mysql_types
|
||||
|
||||
|
||||
proc select*(self:MysqlConnections, columnsArg:varargs[string]):MysqlQuery =
|
||||
let query = newJObject()
|
||||
|
||||
if columnsArg.len == 0:
|
||||
query["select"] = %["*"]
|
||||
else:
|
||||
query["select"] = %columnsArg
|
||||
|
||||
let mysqlQuery = MysqlQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
query: query,
|
||||
queryString: "",
|
||||
placeHolder: newJArray(),
|
||||
isInTransaction: self.isInTransaction,
|
||||
transactionConn: self.transactionConn,
|
||||
)
|
||||
return mysqlQuery
|
||||
|
||||
|
||||
proc table*(self:MysqlConnections, tableArg: string): MysqlQuery =
|
||||
let query = newJObject()
|
||||
query["table"] = %tableArg
|
||||
|
||||
let mysqlQuery = MysqlQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
query: query,
|
||||
queryString: "",
|
||||
placeHolder: newJArray(),
|
||||
isInTransaction: self.isInTransaction,
|
||||
transactionConn: self.transactionConn,
|
||||
)
|
||||
return mysqlQuery
|
||||
|
||||
|
||||
proc table*(self:MysqlQuery, tableArg: string): MysqlQuery =
|
||||
self.query["table"] = %tableArg
|
||||
return self
|
||||
|
||||
|
||||
proc `distinct`*(self: MysqlQuery): MysqlQuery =
|
||||
self.query["distinct"] = %true
|
||||
return self
|
||||
|
||||
# ============================== Conditions ==============================
|
||||
|
||||
proc join*(self: MysqlQuery, table: string, column1: string, symbol: string,
|
||||
column2: string): MysqlQuery =
|
||||
if self.query.hasKey("join") == false:
|
||||
self.query["join"] = %*[{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
}]
|
||||
else:
|
||||
self.query["join"].add(%*{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc leftJoin*(self: MysqlQuery, table: string, column1: string, symbol: string,
|
||||
column2: string): MysqlQuery =
|
||||
if self.query.hasKey("left_join") == false:
|
||||
self.query["left_join"] = %*[{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
}]
|
||||
else:
|
||||
self.query["left_join"].add(%*{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
const whereSymbols = ["is", "is not", "=", "!=", "<", "<=", ">=", ">", "<>", "LIKE","%LIKE","LIKE%","%LIKE%"]
|
||||
const whereSymbolsError = """Arg position 3 is only allowed of ["is", "is not", "=", "!=", "<", "<=", ">=", ">", "<>", "LIKE","%LIKE","LIKE%","%LIKE%"]"""
|
||||
|
||||
proc where*(self: MysqlQuery, column: string, symbol: string,
|
||||
value: string|int|float): MysqlQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key": column, "value": value})
|
||||
|
||||
if self.query.hasKey("where") == false:
|
||||
self.query["where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc where*(self: MysqlQuery, column: string, symbol: string,
|
||||
value: bool): MysqlQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key":column, "value":value})
|
||||
|
||||
if self.query.hasKey("where") == false:
|
||||
self.query["where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc where*(self: MysqlQuery, column: string, symbol: string, value: nil.type): MysqlQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("where_null") == false:
|
||||
self.query["where_null"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["where_null"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc orWhere*(self: MysqlQuery, column: string, symbol: string,
|
||||
value: string|int|float|bool): MysqlQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key":column, "value":value})
|
||||
|
||||
if self.query.hasKey("or_where") == false:
|
||||
self.query["or_where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["or_where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc orWhere*(self: MysqlQuery, column: string, symbol: string, value: nil.type): MysqlQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("or_where") == false:
|
||||
self.query["or_where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["or_where"].add(%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetween*(self:MysqlQuery, column:string, width:array[2, int|float]): MysqlQuery =
|
||||
self.placeHolder.add(%*{"key": column, "value": width[0]})
|
||||
self.placeHolder.add(%*{"key": column, "value": width[1]})
|
||||
|
||||
if self.query.hasKey("where_between") == false:
|
||||
self.query["where_between"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_between"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetween*(self:MysqlQuery, column:string, width:array[2, string]): MysqlQuery =
|
||||
self.placeHolder.add(%*{"key": column, "value": width[0]})
|
||||
self.placeHolder.add(%*{"key": column, "value": width[1]})
|
||||
|
||||
if self.query.hasKey("where_between_string") == false:
|
||||
self.query["where_between_string"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_between_string"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetween*(self:MysqlQuery, column:string, width:array[2, int|float]): MysqlQuery =
|
||||
self.placeHolder.add(%*{"key": column, "value": width[0]})
|
||||
self.placeHolder.add(%*{"key": column, "value": width[1]})
|
||||
|
||||
if self.query.hasKey("where_not_between") == false:
|
||||
self.query["where_not_between"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_between"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetween*(self:MysqlQuery, column:string, width:array[2, string]): MysqlQuery =
|
||||
self.placeHolder.add(%*{"key": column, "value": width[0]})
|
||||
self.placeHolder.add(%*{"key": column, "value": width[1]})
|
||||
|
||||
if self.query.hasKey("where_not_between_string") == false:
|
||||
self.query["where_not_between_string"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_between_string"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereIn*(self:MysqlQuery, column:string, width:seq[int|float|string]): MysqlQuery =
|
||||
for row in width:
|
||||
self.placeHolder.add(%*{"key": column, "value": row})
|
||||
|
||||
if self.query.hasKey("where_in") == false:
|
||||
self.query["where_in"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_in"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotIn*(self:MysqlQuery, column:string, width:seq[int|float|string]): MysqlQuery =
|
||||
for row in width:
|
||||
self.placeHolder.add(%*{"key": column, "value": row})
|
||||
|
||||
if self.query.hasKey("where_not_in") == false:
|
||||
self.query["where_not_in"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_in"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNull*(self:MysqlQuery, column:string): MysqlQuery =
|
||||
if self.query.hasKey("where_null") == false:
|
||||
self.query["where_null"] = %*[{
|
||||
"column": column,
|
||||
"symbol": "is",
|
||||
}]
|
||||
else:
|
||||
self.query["where_null"].add(%*{
|
||||
"column": column,
|
||||
"symbol": "is",
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc groupBy*(self:MysqlQuery, column:string): MysqlQuery =
|
||||
if self.query.hasKey("group_by") == false:
|
||||
self.query["group_by"] = %*[{"column": column}]
|
||||
else:
|
||||
self.query["group_by"].add(%*{"column": column})
|
||||
return self
|
||||
|
||||
|
||||
proc having*(self: MysqlQuery, column: string, symbol: string,
|
||||
value: string|int|float|bool): MysqlQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key":column, "value":value})
|
||||
|
||||
if self.query.hasKey("having") == false:
|
||||
self.query["having"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["having"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
proc having*(self: MysqlQuery, column: string, symbol: string, value: nil.type): MysqlQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("having") == false:
|
||||
self.query["having"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["having"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc orderBy*(self:MysqlQuery, column:string, order:Order): MysqlQuery =
|
||||
if self.query.hasKey("order_by") == false:
|
||||
self.query["order_by"] = %*[{
|
||||
"column": column,
|
||||
"order": $order
|
||||
}]
|
||||
else:
|
||||
self.query["order_by"].add(%*{
|
||||
"column": column,
|
||||
"order": $order
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc limit*(self: MysqlQuery, num: int): MysqlQuery =
|
||||
self.query["limit"] = %num
|
||||
return self
|
||||
|
||||
|
||||
proc offset*(self: MysqlQuery, num: int): MysqlQuery =
|
||||
self.query["offset"] = %num
|
||||
return self
|
||||
|
||||
|
||||
proc raw*(self:MysqlConnections, sql:string, arges=newJArray()): RawMysqlQuery =
|
||||
## arges is `JArray` `[true, 1, 1.1, "str"]`
|
||||
##
|
||||
## can't use BLOB data.
|
||||
let rawQueryRdb = RawMysqlQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
info: self.info,
|
||||
query: newJObject(),
|
||||
queryString: sql.strip(),
|
||||
placeHolder: arges,
|
||||
isInTransaction: false,
|
||||
transactionConn: 0
|
||||
)
|
||||
return rawQueryRdb
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import std/macros
|
||||
import std/strutils
|
||||
import std/strformat
|
||||
import ../../models/mysql/mysql_types
|
||||
|
||||
|
||||
when NimMajor == 1:
|
||||
macro rdbTransaction(rdb:MysqlConnections, callback: untyped):untyped =
|
||||
var callbackStr = callback.repr
|
||||
callbackStr.removePrefix
|
||||
callbackStr = callbackStr.indent(4)
|
||||
callbackStr = fmt"""
|
||||
block:
|
||||
{rdb.repr}.begin().await
|
||||
try:
|
||||
{callbackStr}
|
||||
{rdb.repr}.commit().await
|
||||
except DbError:
|
||||
{rdb.repr}.rollback().await
|
||||
"""
|
||||
let body = callbackStr.parseStmt()
|
||||
return body
|
||||
else:
|
||||
macro rdbTransaction(rdb:MysqlConnections, callback: untyped):untyped =
|
||||
var callbackStr = callback.repr
|
||||
callbackStr.removePrefix
|
||||
callbackStr = callbackStr.indent(4)
|
||||
callbackStr = fmt"""
|
||||
block:
|
||||
{rdb.repr}.begin().await
|
||||
try:
|
||||
{callbackStr}
|
||||
{rdb.repr}.commit().await
|
||||
except CatchableError:
|
||||
{rdb.repr}.rollback().await
|
||||
"""
|
||||
let body = callbackStr.parseStmt()
|
||||
return body
|
||||
|
||||
|
||||
template transaction*(rdb:MysqlConnections, callback: untyped) =
|
||||
rdbTransaction(rdb, callback)
|
||||
@@ -0,0 +1,109 @@
|
||||
import std/tables
|
||||
import std/json
|
||||
import ../../log
|
||||
import ../../libs/mysql/mysql_rdb
|
||||
|
||||
|
||||
type MySQL* = object
|
||||
|
||||
|
||||
const DEFAULT_CONN_MAX_LIFETIME_SECONDS* = 300
|
||||
const DEFAULT_CONN_MAX_IDLE_SECONDS* = 300
|
||||
|
||||
|
||||
type ConnectionInfo* = object
|
||||
database*:string
|
||||
user*:string
|
||||
password*:string
|
||||
host*:string
|
||||
port*:int
|
||||
|
||||
|
||||
type Connection* = object
|
||||
conn*: PMySQL
|
||||
isBusy*: bool
|
||||
createdAt*: int64
|
||||
lastUsedAt*: int64
|
||||
|
||||
|
||||
type MysqlPreparedEntry* = ref object
|
||||
sql*: string
|
||||
nArgs*: int
|
||||
stmts*: seq[PSTMT]
|
||||
refCount*: int
|
||||
lastUsedAt*: int64
|
||||
|
||||
|
||||
type Connections* = ref object
|
||||
conns*: seq[Connection]
|
||||
timeout*:int
|
||||
maxConnectionLifetime*: int
|
||||
maxConnectionIdleTime*: int
|
||||
info*: ConnectionInfo
|
||||
preparedCache*: Table[string, MysqlPreparedEntry]
|
||||
|
||||
|
||||
## created by `let rdb = dbOpen(MySQL, "localhost", 3306)`
|
||||
type MysqlConnections* = ref object
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
info*:ConnectionInfo
|
||||
# for transaction
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
type MysqlPreparedContext* = ref object
|
||||
owner*: MysqlConnections
|
||||
connI*: int
|
||||
|
||||
|
||||
## created by `rdb.select("columnName")` or `rdb.table("tableName")`
|
||||
type MysqlQuery* = ref object
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
info*:ConnectionInfo
|
||||
query*: JsonNode
|
||||
queryString*: string
|
||||
placeHolder*: JsonNode # [{"key":"user", "value":"user1"}]
|
||||
# for transaction
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
type RawMysqlQuery* = ref object
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
info*:ConnectionInfo
|
||||
query*: JsonNode
|
||||
queryString*: string
|
||||
placeHolder*: JsonNode # ["user1", "user1@example.com"]
|
||||
# for transaction
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
type MysqlResultBindCache* = ref object
|
||||
binds*: seq[BIND]
|
||||
buffers*: seq[string]
|
||||
lengths*: seq[culong]
|
||||
nullFlags*: seq[my_bool]
|
||||
errorFlags*: seq[my_bool]
|
||||
|
||||
|
||||
type MysqlPreparedStatement* = ref object
|
||||
owner*: MysqlConnections
|
||||
info*: ConnectionInfo
|
||||
entry*: MysqlPreparedEntry
|
||||
sql*: string
|
||||
nArgs*: int
|
||||
isClosed*: bool
|
||||
resultBindCache*: seq[MysqlResultBindCache]
|
||||
|
||||
|
||||
proc `$`*(self:MysqlConnections|MysqlQuery|RawMysqlQuery):string =
|
||||
return "MySQL"
|
||||
|
||||
|
||||
proc isConnected*(self:MysqlConnections|MysqlQuery|RawMysqlQuery):bool =
|
||||
return self.pools.conns.len > 0
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
import std/json
|
||||
import ../mysql_types
|
||||
import ./mysql_generator
|
||||
|
||||
|
||||
# ==================== SELECT ====================
|
||||
|
||||
proc selectBuilder*(self: MysqlQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc selectFirstBuilder*(self: MysqlQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.selectFirstSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc selectFindBuilder*(self: MysqlQuery, key: string): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.selectByIdSql(key)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== INSERT ====================
|
||||
|
||||
proc insertValueBuilder*(self: MysqlQuery, items: JsonNode): string =
|
||||
return self
|
||||
.insertSql()
|
||||
.insertValueSql(items)
|
||||
.queryString
|
||||
|
||||
proc insertValuesBuilder*(self: MysqlQuery, rows: openArray[JsonNode]): string =
|
||||
return self
|
||||
.insertSql()
|
||||
.insertValuesSql(rows)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== UPDATE ====================
|
||||
|
||||
proc updateBuilder*(self: MysqlQuery, items: JsonNode): string =
|
||||
return self
|
||||
.updateSql()
|
||||
.updateValuesSql(items)
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== DELETE ====================
|
||||
|
||||
proc deleteBuilder*(self: MysqlQuery): string =
|
||||
return self
|
||||
.deleteSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc deleteByIdBuilder*(self: MysqlQuery, id: int, key: string): string =
|
||||
return self
|
||||
.deleteSql()
|
||||
.fromSql()
|
||||
.deleteByIdSql(id, key)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== Aggregates ====================
|
||||
|
||||
proc countBuilder*(self:MysqlQuery): string =
|
||||
return self
|
||||
.selectCountSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc maxBuilder*(self:MysqlQuery, column:string): string =
|
||||
return self
|
||||
.selectMaxSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc minBuilder*(self:MysqlQuery, column:string): string =
|
||||
return self
|
||||
.selectMinSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc avgBuilder*(self:MysqlQuery, column:string): string =
|
||||
return self
|
||||
.selectAvgSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc sumBuilder*(self:MysqlQuery, column:string): string =
|
||||
return self
|
||||
.selectSumSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc columnBuilder*(self:MysqlQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.selectFirstSql()
|
||||
.queryString
|
||||
+430
@@ -0,0 +1,430 @@
|
||||
import std/json
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
# import ../../database_types
|
||||
import ../mysql_types
|
||||
|
||||
|
||||
proc quote(input:string):string =
|
||||
## "User.id as userId" => ```User```.```id``` as ```userId```
|
||||
var tmp = newSeq[string]()
|
||||
for segment in input.split("."):
|
||||
if segment.contains(" as "):
|
||||
# Split once on ' as ' to separate the expression and its alias
|
||||
let parts = segment.split(" as ", maxsplit = 1)
|
||||
let expression = parts[0]
|
||||
let alias = parts[1]
|
||||
if expression.contains("("):
|
||||
# Extract function name and column name using index-based slicing
|
||||
let funcStart = expression.find('(')
|
||||
let funcEnd = expression.find(')', funcStart)
|
||||
let funcName = expression[0 ..< funcStart]
|
||||
let columnName = expression[funcStart + 1 ..< funcEnd]
|
||||
tmp.add(&"{funcName}(`{columnName}`) as `{alias}`")
|
||||
else:
|
||||
# Quote the expression and alias
|
||||
tmp.add(&"`{expression}` as `{alias}`")
|
||||
elif segment.contains("("):
|
||||
# Leave functions without alias as is
|
||||
tmp.add(segment)
|
||||
else:
|
||||
# Quote standalone identifiers
|
||||
tmp.add(&"`{segment}`")
|
||||
return tmp.join(".")
|
||||
|
||||
|
||||
# ==================== SELECT ====================
|
||||
|
||||
proc selectSql*(self: MysqlQuery): MysqlQuery =
|
||||
var queryString = ""
|
||||
|
||||
if self.query.hasKey("distinct"):
|
||||
queryString.add("SELECT DISTINCT")
|
||||
else:
|
||||
queryString.add("SELECT")
|
||||
|
||||
if self.query.hasKey("select"):
|
||||
for i, item in self.query["select"].getElems():
|
||||
if i > 0: queryString.add(",")
|
||||
var column = item.getStr()
|
||||
# if column.contains("as"):
|
||||
# let original = column.split("as")[0].strip()
|
||||
# let renamed = column.split("as")[1].strip()
|
||||
# queryString.add(&" `{original}` as `{renamed}`")
|
||||
# else:
|
||||
# queryString.add(&" `{column}`")
|
||||
column = quote(column)
|
||||
queryString.add(&" {column}")
|
||||
else:
|
||||
queryString.add(" *")
|
||||
|
||||
self.queryString = queryString
|
||||
return self
|
||||
|
||||
|
||||
proc fromSql*(self: MysqlQuery): MysqlQuery =
|
||||
let table = self.query["table"].getStr().quote()
|
||||
self.queryString.add(&" FROM {table}")
|
||||
return self
|
||||
|
||||
|
||||
proc selectFirstSql*(self: MysqlQuery): MysqlQuery =
|
||||
self.queryString.add(" LIMIT 1")
|
||||
return self
|
||||
|
||||
|
||||
proc selectByIdSql*(self: MysqlQuery, key:string): MysqlQuery =
|
||||
let key = key.quote()
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {key} = ? LIMIT 1")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {key} = ? LIMIT 1")
|
||||
return self
|
||||
|
||||
|
||||
proc joinSql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("join"):
|
||||
for row in self.query["join"]:
|
||||
let table = row["table"].getStr().quote()
|
||||
let column1 = row["column1"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
let column2 = row["column2"].getStr().quote()
|
||||
|
||||
self.queryString.add(&" INNER JOIN {table} ON {column1} {symbol} {column2}")
|
||||
return self
|
||||
|
||||
|
||||
proc leftJoinSql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("left_join"):
|
||||
for row in self.query["left_join"]:
|
||||
let table = row["table"].getStr().quote()
|
||||
let column1 = row["column1"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
let column2 = row["column2"].getStr().quote()
|
||||
|
||||
self.queryString.add(&" LEFT JOIN {table} ON {column1} {symbol} {column2}")
|
||||
return self
|
||||
|
||||
|
||||
proc whereSql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("where"):
|
||||
for i, row in self.query["where"].getElems():
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
# let value = row["value"].getStr()
|
||||
if i == 0:
|
||||
self.queryString.add(&" WHERE {column} {symbol} ?")
|
||||
else:
|
||||
self.queryString.add(&" AND {column} {symbol} ?")
|
||||
return self
|
||||
|
||||
|
||||
proc orWhereSql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("or_where"):
|
||||
for row in self.query["or_where"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
# let value = row["value"].getStr()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" OR {column} {symbol} ?")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} {symbol} ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetweenSql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("where_between"):
|
||||
for row in self.query["where_between"]:
|
||||
let column = row["column"].getStr()
|
||||
# let start = row["width"][0].getFloat()
|
||||
# let stop = row["width"][1].getFloat()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
# self.queryString.add(&" AND `{column}` BETWEEN {start} AND {stop}")
|
||||
self.queryString.add(&" AND `{column}` BETWEEN ? AND ?")
|
||||
else:
|
||||
# self.queryString.add(&" WHERE `{column}` BETWEEN {start} AND {stop}")
|
||||
self.queryString.add(&" WHERE `{column}` BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetweenStringSql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("where_between_string"):
|
||||
for row in self.query["where_between_string"]:
|
||||
let column = row["column"].getStr()
|
||||
# let start = row["width"][0].getStr
|
||||
# let stop = row["width"][1].getStr
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
# self.queryString.add(&" AND `{column}` BETWEEN '{start}' AND '{stop}'")
|
||||
self.queryString.add(&" AND `{column}` BETWEEN ? AND ?")
|
||||
else:
|
||||
# self.queryString.add(&" WHERE `{column}` BETWEEN '{start}' AND '{stop}'")
|
||||
self.queryString.add(&" WHERE `{column}` BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetweenSql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("where_not_between"):
|
||||
for row in self.query["where_not_between"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
# let start = row["width"][0].getFloat()
|
||||
# let stop = row["width"][1].getFloat()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
# self.queryString.add(&" AND `{column}` NOT BETWEEN {start} AND {stop}")
|
||||
self.queryString.add(&" AND {column} NOT BETWEEN ? AND ?")
|
||||
else:
|
||||
# self.queryString.add(&" WHERE `{column}` NOT BETWEEN {start} AND {stop}")
|
||||
self.queryString.add(&" WHERE {column} NOT BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetweenStringSql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("where_not_between_string"):
|
||||
for row in self.query["where_not_between_string"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
# let start = row["width"][0].getStr
|
||||
# let stop = row["width"][1].getStr
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
# self.queryString.add(&" AND `{column}` NOT BETWEEN '{start}' AND '{stop}'")
|
||||
self.queryString.add(&" AND {column} NOT BETWEEN ? AND ?")
|
||||
else:
|
||||
# self.queryString.add(&" WHERE `{column}` NOT BETWEEN '{start}' AND '{stop}'")
|
||||
self.queryString.add(&" WHERE {column} NOT BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereInSql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("where_in"):
|
||||
var widthString = ""
|
||||
for row in self.query["where_in"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
for i, val in row["width"].getElems():
|
||||
if i > 0: widthString.add(", ")
|
||||
widthString.add("?")
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} IN ({widthString})")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} IN ({widthString})")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotInSql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("where_not_in"):
|
||||
var widthString = ""
|
||||
for row in self.query["where_not_in"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
for i, val in row["width"].getElems():
|
||||
if i > 0: widthString.add(", ")
|
||||
widthString.add("?")
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} NOT IN ({widthString})")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} NOT IN ({widthString})")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNullSql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("where_null"):
|
||||
for row in self.query["where_null"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} {symbol} NULL")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} {symbol} NULL")
|
||||
return self
|
||||
|
||||
|
||||
proc groupBySql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("group_by"):
|
||||
for row in self.query["group_by"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
if self.queryString.contains("GROUP BY"):
|
||||
self.queryString.add(&", {column}")
|
||||
else:
|
||||
self.queryString.add(&" GROUP BY {column}")
|
||||
return self
|
||||
|
||||
|
||||
proc havingSql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("having"):
|
||||
for i, row in self.query["having"].getElems():
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
# let value = row["value"].getStr()
|
||||
|
||||
if i == 0:
|
||||
self.queryString.add(&" HAVING {column} {symbol} ?")
|
||||
else:
|
||||
self.queryString.add(&" AND {column} {symbol} ?")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc orderBySql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("order_by"):
|
||||
for row in self.query["order_by"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let order = row["order"].getStr()
|
||||
|
||||
if self.queryString.contains("ORDER BY"):
|
||||
self.queryString.add(&", {column} {order}")
|
||||
else:
|
||||
self.queryString.add(&" ORDER BY {column} {order}")
|
||||
return self
|
||||
|
||||
|
||||
proc limitSql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("limit"):
|
||||
let num = self.query["limit"].getInt()
|
||||
self.queryString.add(&" LIMIT {num}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc offsetSql*(self: MysqlQuery): MysqlQuery =
|
||||
if self.query.hasKey("offset"):
|
||||
let num = self.query["offset"].getInt()
|
||||
self.queryString.add(&" OFFSET {num}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
# ==================== INSERT ====================
|
||||
|
||||
proc insertSql*(self: MysqlQuery): MysqlQuery =
|
||||
let table = self.query["table"].getStr()
|
||||
self.queryString = &"INSERT INTO `{table}`"
|
||||
return self
|
||||
|
||||
|
||||
proc insertValueSql*(self: MysqlQuery, items: JsonNode): MysqlQuery =
|
||||
var columns = ""
|
||||
var values = ""
|
||||
|
||||
var i = 0
|
||||
for key, val in items.pairs:
|
||||
defer: i += 1
|
||||
if i > 0:
|
||||
columns.add(", ")
|
||||
values.add(", ")
|
||||
# If column name contains Upper letter, column name is covered by double quote
|
||||
columns.add(&"`{key}`")
|
||||
|
||||
self.placeHolder.add(%*{"key":key, "value":val})
|
||||
values.add("?")
|
||||
|
||||
self.queryString.add(&" ({columns}) VALUES ({values})")
|
||||
return self
|
||||
|
||||
|
||||
proc insertValuesSql*(self: MysqlQuery, rows: openArray[JsonNode]): MysqlQuery =
|
||||
var columns = ""
|
||||
|
||||
var i = 0
|
||||
for key, value in rows[0]:
|
||||
defer: i += 1
|
||||
if i > 0: columns.add(", ")
|
||||
# If column name contains Upper letter, column name is covered by double quote
|
||||
columns.add(&"`{key}`")
|
||||
|
||||
var values = ""
|
||||
var valuesCount = 0
|
||||
for items in rows:
|
||||
var valueCount = 0
|
||||
var value = ""
|
||||
for key, val in items.pairs:
|
||||
defer: valueCount += 1
|
||||
if valueCount > 0: value.add(", ")
|
||||
|
||||
self.placeHolder.add(%*{"key":key, "value":val})
|
||||
value.add("?")
|
||||
|
||||
if valuesCount > 0: values.add(", ")
|
||||
valuesCount += 1
|
||||
values.add(&"({value})")
|
||||
|
||||
self.queryString.add(&" ({columns}) VALUES {values}")
|
||||
return self
|
||||
|
||||
|
||||
# ==================== UPDATE ====================
|
||||
|
||||
proc updateSql*(self: MysqlQuery): MysqlQuery =
|
||||
var queryString = ""
|
||||
queryString.add("UPDATE")
|
||||
|
||||
var table = self.query["table"].getStr()
|
||||
queryString.add(&" `{table}` SET")
|
||||
self.queryString = queryString
|
||||
return self
|
||||
|
||||
|
||||
proc updateValuesSql*(self: MysqlQuery, items:JsonNode): MysqlQuery =
|
||||
var value = ""
|
||||
let placeHolder = newJArray()
|
||||
|
||||
var i = 0
|
||||
for key, val in items.pairs:
|
||||
defer: i += 1
|
||||
if i > 0: value.add(",")
|
||||
value.add(&" `{key}` = ?")
|
||||
placeHolder.add(%*{"key":key, "value":val})
|
||||
|
||||
for row in self.placeHolder.items:
|
||||
placeHolder.add(row)
|
||||
|
||||
self.placeHolder = placeHolder
|
||||
|
||||
self.queryString.add(value)
|
||||
return self
|
||||
|
||||
|
||||
# ==================== DELETE ====================
|
||||
|
||||
proc deleteSql*(self: MysqlQuery): MysqlQuery =
|
||||
self.queryString = "DELETE"
|
||||
return self
|
||||
|
||||
|
||||
proc deleteByIdSql*(self: MysqlQuery, id: int, key: string): MysqlQuery =
|
||||
self.queryString.add(&" WHERE `{key}` = ?")
|
||||
return self
|
||||
|
||||
# ==================== Aggregates ====================
|
||||
|
||||
proc selectCountSql*(self: MysqlQuery): MysqlQuery =
|
||||
let queryString =
|
||||
if self.query.hasKey("select"):
|
||||
let column = self.query["select"][0].getStr
|
||||
&"`{column}`"
|
||||
else:
|
||||
"*"
|
||||
self.queryString = &"SELECT count({queryString}) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectMaxSql*(self: MysqlQuery, column:string): MysqlQuery =
|
||||
self.queryString = &"SELECT max(`{column}`) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectMinSql*(self: MysqlQuery, column:string): MysqlQuery =
|
||||
self.queryString = &"SELECT min(`{column}`) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectAvgSql*(self: MysqlQuery, column:string): MysqlQuery =
|
||||
self.queryString = &"SELECT avg(`{column}`) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectSumSql*(self: MysqlQuery, column:string): MysqlQuery =
|
||||
self.queryString = &"SELECT sum(`{column}`) as aggregate"
|
||||
return self
|
||||
@@ -0,0 +1,18 @@
|
||||
import std/asyncdispatch
|
||||
import std/options
|
||||
import std/json
|
||||
|
||||
proc orm*[T](response:Future[seq[JsonNode]], typ:typedesc[T]):Future[seq[T]] {.async.} =
|
||||
let response = response.await
|
||||
var result = newSeq[T](response.len)
|
||||
for i, res in response:
|
||||
result[i] = res.to(typ)
|
||||
return result
|
||||
|
||||
|
||||
proc orm*[T](response:Future[Option[JsonNode]], typ:typedesc[T]):Future[Option[T]] {.async.} =
|
||||
let response = response.await
|
||||
if response.isSome:
|
||||
return response.get.to(typ).some()
|
||||
else:
|
||||
return none(T)
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import std/macros
|
||||
import std/strutils
|
||||
import std/strformat
|
||||
import ../../models/postgres/postgres_types
|
||||
|
||||
|
||||
when NimMajor == 1:
|
||||
macro rdbTransaction(rdb:PostgresConnections, callback: untyped):untyped =
|
||||
var callbackStr = callback.repr
|
||||
callbackStr.removePrefix
|
||||
callbackStr = callbackStr.indent(4)
|
||||
callbackStr = fmt"""
|
||||
block:
|
||||
{rdb.repr}.begin().await
|
||||
try:
|
||||
{callbackStr}
|
||||
{rdb.repr}.commit().await
|
||||
except DbError:
|
||||
{rdb.repr}.rollback().await
|
||||
"""
|
||||
let body = callbackStr.parseStmt()
|
||||
return body
|
||||
else:
|
||||
macro rdbTransaction(rdb:PostgresConnections, callback: untyped):untyped =
|
||||
var callbackStr = callback.repr
|
||||
callbackStr.removePrefix
|
||||
callbackStr = callbackStr.indent(4)
|
||||
callbackStr = fmt"""
|
||||
block:
|
||||
{rdb.repr}.begin().await
|
||||
try:
|
||||
{callbackStr}
|
||||
{rdb.repr}.commit().await
|
||||
except CatchableError:
|
||||
{rdb.repr}.rollback().await
|
||||
"""
|
||||
let body = callbackStr.parseStmt()
|
||||
return body
|
||||
|
||||
|
||||
template transaction*(rdb:PostgresConnections, callback: untyped) =
|
||||
rdbTransaction(rdb, callback)
|
||||
+1330
File diff suppressed because it is too large
Load Diff
+82
@@ -0,0 +1,82 @@
|
||||
import std/asyncdispatch
|
||||
import std/deques
|
||||
import std/tables
|
||||
import std/times
|
||||
import ../database_types
|
||||
import ../../error
|
||||
import ../../libs/database_url
|
||||
import ../../libs/postgres/postgres_rdb
|
||||
import ../../libs/postgres/postgres_lib
|
||||
import ../../log
|
||||
import ./postgres_types
|
||||
|
||||
proc dbOpen*(_:type PostgreSQL, database: string, user: string, password: string,
|
||||
host: string, port: int, maxConnections=1, timeout=30,
|
||||
shouldDisplayLog=false, shouldOutputLogFile=false, logDir="",
|
||||
maxConnectionLifetime=DEFAULT_CONN_MAX_LIFETIME_SECONDS,
|
||||
maxConnectionIdleTime=DEFAULT_CONN_MAX_IDLE_SECONDS): PostgresConnections =
|
||||
var conns = newSeq[Connection](maxConnections)
|
||||
for i in 0..<maxConnections:
|
||||
let conn = postgres_rdb.pqsetdbLogin(host, port.`$`.cstring, nil, nil, database, user, password)
|
||||
if pqStatus(conn) != CONNECTION_OK: dbError(conn)
|
||||
if pqsetnonblocking(conn, 1'i32) != 0'i32:
|
||||
dbError(conn)
|
||||
if pqisnonblocking(conn) != 1'i32:
|
||||
raise newException(DbError, "PostgreSQL connection could not be set to non-blocking mode")
|
||||
conns[i] = Connection(
|
||||
conn: conn,
|
||||
isBusy: false,
|
||||
createdAt: getTime().toUnix(),
|
||||
lastUsedAt: getTime().toUnix(),
|
||||
)
|
||||
let pools = Connections(
|
||||
conns: conns,
|
||||
timeout: timeout,
|
||||
maxConnectionLifetime: maxConnectionLifetime,
|
||||
maxConnectionIdleTime: maxConnectionIdleTime,
|
||||
database: database,
|
||||
user: user,
|
||||
password: password,
|
||||
host: host,
|
||||
port: port,
|
||||
waiters: initDeque[Future[void]](),
|
||||
columnTypeCache: initTable[string, seq[Row]](),
|
||||
preparedCache: initTable[string, PostgresPreparedEntry](),
|
||||
)
|
||||
result = PostgresConnections(
|
||||
pools: pools,
|
||||
log: LogSetting(shouldDisplayLog:shouldDisplayLog, shouldOutputLogFile:shouldOutputLogFile, logDir:logDir)
|
||||
)
|
||||
|
||||
|
||||
proc dbOpen*(_:type PostgreSQL, url: string, maxConnections: int = 1, timeout=30,
|
||||
shouldDisplayLog=false, shouldOutputLogFile=false, logDir="",
|
||||
maxConnectionLifetime=DEFAULT_CONN_MAX_LIFETIME_SECONDS,
|
||||
maxConnectionIdleTime=DEFAULT_CONN_MAX_IDLE_SECONDS): PostgresConnections =
|
||||
return dbOpen(PostgreSQL, asDatabaseUrl(url), maxConnections, timeout, shouldDisplayLog, shouldOutputLogFile, logDir, maxConnectionLifetime, maxConnectionIdleTime)
|
||||
|
||||
|
||||
proc dbOpen*(_:type PostgreSQL, databaseUrl: DatabaseUrl, maxConnections: int = 1, timeout=30,
|
||||
shouldDisplayLog=false, shouldOutputLogFile=false, logDir="",
|
||||
maxConnectionLifetime=DEFAULT_CONN_MAX_LIFETIME_SECONDS,
|
||||
maxConnectionIdleTime=DEFAULT_CONN_MAX_IDLE_SECONDS): PostgresConnections =
|
||||
let parsed = parseDatabaseUrl(databaseUrl)
|
||||
requireDatabaseUrlScheme(parsed, ["postgresql", "postgres"], "PostgreSQL")
|
||||
|
||||
let database = databaseName(parsed)
|
||||
let port = portOrDefault(parsed, 5432)
|
||||
return dbOpen(
|
||||
PostgreSQL,
|
||||
database,
|
||||
parsed.username,
|
||||
parsed.password,
|
||||
parsed.hostname,
|
||||
port,
|
||||
maxConnections,
|
||||
timeout,
|
||||
shouldDisplayLog,
|
||||
shouldOutputLogFile,
|
||||
logDir,
|
||||
maxConnectionLifetime,
|
||||
maxConnectionIdleTime
|
||||
)
|
||||
+441
@@ -0,0 +1,441 @@
|
||||
import std/asyncdispatch
|
||||
import std/json
|
||||
import std/options
|
||||
import std/strutils
|
||||
import std/times
|
||||
import ../../libs/postgres/postgres_impl
|
||||
import ../../enums
|
||||
import ./postgres_types
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# query
|
||||
# ================================================================================
|
||||
|
||||
proc select*(self:PostgresConnections, columnsArg:varargs[string]):PostgresQuery =
|
||||
let query = newJObject()
|
||||
|
||||
if columnsArg.len == 0 or columnsArg[0] == "*":
|
||||
query["select"] = %["*"]
|
||||
else:
|
||||
query["select"] = %columnsArg
|
||||
|
||||
let postgresQuery = PostgresQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
query: query,
|
||||
queryString: "",
|
||||
placeHolder: newJArray(),
|
||||
isInTransaction: self.isInTransaction,
|
||||
transactionConn: self.transactionConn,
|
||||
)
|
||||
return postgresQuery
|
||||
|
||||
|
||||
proc table*(self:PostgresConnections, tableArg: string): PostgresQuery =
|
||||
let query = newJObject()
|
||||
query["table"] = %tableArg
|
||||
|
||||
let postgresQuery = PostgresQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
query: query,
|
||||
queryString: "",
|
||||
placeHolder: newJArray(),
|
||||
isInTransaction: self.isInTransaction,
|
||||
transactionConn: self.transactionConn,
|
||||
)
|
||||
return postgresQuery
|
||||
|
||||
|
||||
proc table*(self:PostgresQuery, tableArg: string): PostgresQuery =
|
||||
self.query["table"] = %tableArg
|
||||
return self
|
||||
|
||||
|
||||
proc `distinct`*(self: PostgresQuery): PostgresQuery =
|
||||
self.query["distinct"] = %true
|
||||
return self
|
||||
|
||||
# ============================== Conditions ==============================
|
||||
|
||||
proc join*(self: PostgresQuery, table: string, column1: string, symbol: string,
|
||||
column2: string): PostgresQuery =
|
||||
if self.query.hasKey("join") == false:
|
||||
self.query["join"] = %*[{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
}]
|
||||
else:
|
||||
self.query["join"].add(%*{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc leftJoin*(self: PostgresQuery, table: string, column1: string, symbol: string,
|
||||
column2: string): PostgresQuery =
|
||||
if self.query.hasKey("left_join") == false:
|
||||
self.query["left_join"] = %*[{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
}]
|
||||
else:
|
||||
self.query["left_join"].add(%*{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
const whereSymbols = ["is", "is not", "=", "!=", "<", "<=", ">=", ">", "<>", "LIKE","%LIKE","LIKE%","%LIKE%"]
|
||||
const whereSymbolsError = """Arg position 3 is only allowed of ["is", "is not", "=", "!=", "<", "<=", ">=", ">", "<>", "LIKE","%LIKE","LIKE%","%LIKE%"]"""
|
||||
|
||||
proc where*(self: PostgresQuery, column: string, symbol: string,
|
||||
value: string|int|float): PostgresQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key": column, "value": value})
|
||||
|
||||
if self.query.hasKey("where") == false:
|
||||
self.query["where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc where*(self: PostgresQuery, column: string, symbol: string,
|
||||
value: bool): PostgresQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key":column, "value":value})
|
||||
|
||||
if self.query.hasKey("where") == false:
|
||||
self.query["where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc where*(self: PostgresQuery, column: string, symbol: string, value: nil.type): PostgresQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("where_null") == false:
|
||||
self.query["where_null"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["where_null"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc orWhere*(self: PostgresQuery, column: string, symbol: string,
|
||||
value: string|int|float|bool): PostgresQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key":column, "value":value})
|
||||
|
||||
if self.query.hasKey("or_where") == false:
|
||||
self.query["or_where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["or_where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc orWhere*(self: PostgresQuery, column: string, symbol: string, value: nil.type): PostgresQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("or_where") == false:
|
||||
self.query["or_where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["or_where"].add(%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetween*(self:PostgresQuery, column:string, width:array[2, int|float]): PostgresQuery =
|
||||
self.placeHolder.add(%*{"key": column, "value": width[0]})
|
||||
self.placeHolder.add(%*{"key": column, "value": width[1]})
|
||||
|
||||
if self.query.hasKey("where_between") == false:
|
||||
self.query["where_between"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_between"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetween*(self:PostgresQuery, column:string, width:array[2, string]): PostgresQuery =
|
||||
self.placeHolder.add(%*{"key": column, "value": width[0]})
|
||||
self.placeHolder.add(%*{"key": column, "value": width[1]})
|
||||
|
||||
if self.query.hasKey("where_between_string") == false:
|
||||
self.query["where_between_string"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_between_string"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetween*(self:PostgresQuery, column:string, width:array[2, int|float]): PostgresQuery =
|
||||
self.placeHolder.add(%*{"key": column, "value": width[0]})
|
||||
self.placeHolder.add(%*{"key": column, "value": width[1]})
|
||||
|
||||
if self.query.hasKey("where_not_between") == false:
|
||||
self.query["where_not_between"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_between"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetween*(self:PostgresQuery, column:string, width:array[2, string]): PostgresQuery =
|
||||
self.placeHolder.add(%*{"key": column, "value": width[0]})
|
||||
self.placeHolder.add(%*{"key": column, "value": width[1]})
|
||||
|
||||
if self.query.hasKey("where_not_between_string") == false:
|
||||
self.query["where_not_between_string"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_between_string"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereIn*(self:PostgresQuery, column:string, width:seq[int|float|string]): PostgresQuery =
|
||||
for row in width:
|
||||
self.placeHolder.add(%*{"key": column, "value": row})
|
||||
|
||||
if self.query.hasKey("where_in") == false:
|
||||
self.query["where_in"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_in"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotIn*(self:PostgresQuery, column:string, width:seq[int|float|string]): PostgresQuery =
|
||||
for row in width:
|
||||
self.placeHolder.add(%*{"key": column, "value": row})
|
||||
|
||||
if self.query.hasKey("where_not_in") == false:
|
||||
self.query["where_not_in"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_in"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNull*(self:PostgresQuery, column:string): PostgresQuery =
|
||||
if self.query.hasKey("where_null") == false:
|
||||
self.query["where_null"] = %*[{
|
||||
"column": column,
|
||||
"symbol": "is",
|
||||
}]
|
||||
else:
|
||||
self.query["where_null"].add(%*{
|
||||
"column": column,
|
||||
"symbol": "is",
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc groupBy*(self:PostgresQuery, column:string): PostgresQuery =
|
||||
if self.query.hasKey("group_by") == false:
|
||||
self.query["group_by"] = %*[{"column": column}]
|
||||
else:
|
||||
self.query["group_by"].add(%*{"column": column})
|
||||
return self
|
||||
|
||||
|
||||
proc having*(self: PostgresQuery, column: string, symbol: string,
|
||||
value: string|int|float|bool): PostgresQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key":column, "value":value})
|
||||
|
||||
if self.query.hasKey("having") == false:
|
||||
self.query["having"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["having"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
proc having*(self: PostgresQuery, column: string, symbol: string, value: nil.type): PostgresQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("having") == false:
|
||||
self.query["having"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["having"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc orderBy*(self:PostgresQuery, column:string, order:Order): PostgresQuery =
|
||||
if self.query.hasKey("order_by") == false:
|
||||
self.query["order_by"] = %*[{
|
||||
"column": column,
|
||||
"order": $order
|
||||
}]
|
||||
else:
|
||||
self.query["order_by"].add(%*{
|
||||
"column": column,
|
||||
"order": $order
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc limit*(self: PostgresQuery, num: int): PostgresQuery =
|
||||
self.query["limit"] = %num
|
||||
return self
|
||||
|
||||
|
||||
proc offset*(self: PostgresQuery, num: int): PostgresQuery =
|
||||
self.query["offset"] = %num
|
||||
return self
|
||||
|
||||
|
||||
proc raw*(self:PostgresConnections, sql:string, arges=newJArray()): RawPostgresQuery =
|
||||
## arges is `JArray`
|
||||
##
|
||||
## can't use BLOB data.
|
||||
let rawQueryRdb = RawPostgresQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
query: newJObject(),
|
||||
queryString: sql,
|
||||
placeHolder: arges,
|
||||
isInTransaction: false,
|
||||
transactionConn: 0
|
||||
)
|
||||
return rawQueryRdb
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
import std/asyncdispatch
|
||||
import std/deques
|
||||
import std/json
|
||||
import std/tables
|
||||
import ../database_types
|
||||
import ../../log
|
||||
import ../../libs/postgres/postgres_rdb
|
||||
|
||||
|
||||
type PostgreSQL* = object
|
||||
|
||||
|
||||
const DEFAULT_CONN_MAX_LIFETIME_SECONDS* = 300
|
||||
const DEFAULT_CONN_MAX_IDLE_SECONDS* = 300
|
||||
|
||||
|
||||
type Connection* = ref object
|
||||
conn*: PPGconn
|
||||
isBusy*: bool
|
||||
createdAt*: int64
|
||||
lastUsedAt*: int64
|
||||
|
||||
|
||||
type PostgresPreparedEntry* = ref object
|
||||
sql*: string
|
||||
nArgs*: int
|
||||
stmtBaseName*: string
|
||||
stmtNames*: seq[string]
|
||||
refCount*: int
|
||||
lastUsedAt*: int64
|
||||
|
||||
|
||||
type Connections* = ref object
|
||||
conns*: seq[Connection]
|
||||
timeout*: int
|
||||
maxConnectionLifetime*: int
|
||||
maxConnectionIdleTime*: int
|
||||
database*: string
|
||||
user*: string
|
||||
password*: string
|
||||
host*: string
|
||||
port*: int
|
||||
## `getFreeConn` が接続を待つときに積む Future。`returnConn` が先頭から 1 件だけ完了させる。
|
||||
waiters*: Deque[Future[void]]
|
||||
## `exec` / `insertId` 用。テーブルごとに information_schema 相当の列型を初回のみ取得して保持する。
|
||||
columnTypeCache*: Table[string, seq[Row]]
|
||||
## SQL 単位の prepared statement cache。物理 prepare 状態は conn ごとに保持する。
|
||||
preparedCache*: Table[string, PostgresPreparedEntry]
|
||||
|
||||
|
||||
## created by `let rdb = dbOpen(PostgreSQL, "localhost", 5432)`
|
||||
type PostgresConnections* = ref object
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
# for transaction
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
## created by `rdb.select("columnName")` or `rdb.table("tableName")`
|
||||
type PostgresQuery* = ref object
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
query*: JsonNode # JObject
|
||||
queryString*: string
|
||||
placeHolder*: JsonNode # JArray [{"key":"user", "value":"user1"}]
|
||||
# for transaction
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
type RawPostgresQuery* = ref object
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
query*: JsonNode
|
||||
queryString*: string
|
||||
placeHolder*: JsonNode # JArray ["user1", "user1@example.com"]
|
||||
# for transaction
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
type PostgresPreparedContext* = ref object
|
||||
owner*: PostgresConnections
|
||||
connI*: int
|
||||
|
||||
|
||||
type PostgresPreparedStatement* = ref object
|
||||
owner*: PostgresConnections
|
||||
entry*: PostgresPreparedEntry
|
||||
sql*: string
|
||||
nArgs*: int
|
||||
isClosed*: bool
|
||||
|
||||
|
||||
proc `$`*(self:PostgresConnections|PostgresQuery|RawPostgresQuery):string =
|
||||
return "PostgreSQL"
|
||||
|
||||
|
||||
proc isConnected*(self:PostgresConnections|PostgresQuery|RawPostgresQuery):bool =
|
||||
return self.pools.conns.len > 0
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
import std/json
|
||||
import ../postgres_types
|
||||
import ./postgres_generator
|
||||
|
||||
|
||||
# ==================== SELECT ====================
|
||||
|
||||
proc selectBuilder*(self: PostgresQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc selectFirstBuilder*(self: PostgresQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.selectFirstSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc selectFindBuilder*(self: PostgresQuery, key: string): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.selectByIdSql(key)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== INSERT ====================
|
||||
|
||||
proc insertValueBuilder*(self: PostgresQuery, items: JsonNode): string =
|
||||
return self
|
||||
.insertSql()
|
||||
.insertValueSql(items)
|
||||
.queryString
|
||||
|
||||
proc insertValuesBuilder*(self: PostgresQuery, rows: openArray[JsonNode]): string =
|
||||
return self
|
||||
.insertSql()
|
||||
.insertValuesSql(rows)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== UPDATE ====================
|
||||
|
||||
proc updateBuilder*(self: PostgresQuery, items: JsonNode): string =
|
||||
return self
|
||||
.updateSql()
|
||||
.updateValuesSql(items)
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== DELETE ====================
|
||||
|
||||
proc deleteBuilder*(self: PostgresQuery): string =
|
||||
return self
|
||||
.deleteSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc deleteByIdBuilder*(self: PostgresQuery, id: int, key: string): string =
|
||||
return self
|
||||
.deleteSql()
|
||||
.fromSql()
|
||||
.deleteByIdSql(id, key)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== Aggregates ====================
|
||||
|
||||
proc countBuilder*(self:PostgresQuery): string =
|
||||
return self
|
||||
.selectCountSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc maxBuilder*(self:PostgresQuery, column:string): string =
|
||||
return self
|
||||
.selectMaxSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc minBuilder*(self:PostgresQuery, column:string): string =
|
||||
return self
|
||||
.selectMinSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc avgBuilder*(self:PostgresQuery, column:string): string =
|
||||
return self
|
||||
.selectAvgSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc sumBuilder*(self:PostgresQuery, column:string): string =
|
||||
return self
|
||||
.selectSumSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc columnBuilder*(self:PostgresQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.selectFirstSql()
|
||||
.queryString
|
||||
+429
@@ -0,0 +1,429 @@
|
||||
import std/json
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
import ../postgres_types
|
||||
|
||||
|
||||
proc quote(input:string):string =
|
||||
## `User.id as userId` => `"User"."id" as "userId"`
|
||||
##
|
||||
## `COUNT(id) as count` => `COUNT("id") as "count"`
|
||||
##
|
||||
## `MAX(id) as maxId` => `MAX("id") as "maxId"`
|
||||
##
|
||||
|
||||
var tmp = newSeq[string]()
|
||||
for segment in input.split("."):
|
||||
if segment.contains(" as "):
|
||||
# Split once on ' as ' to separate the expression and its alias
|
||||
let parts = segment.split(" as ", maxsplit = 1)
|
||||
let expression = parts[0]
|
||||
let alias = parts[1]
|
||||
if expression.contains("("):
|
||||
# Extract function name and column name using index-based slicing
|
||||
let funcStart = expression.find('(')
|
||||
let funcEnd = expression.find(')', funcStart)
|
||||
let funcName = expression[0 ..< funcStart]
|
||||
let columnName = expression[funcStart + 1 ..< funcEnd]
|
||||
tmp.add(&"{funcName}(\"{columnName}\") as \"{alias}\"")
|
||||
else:
|
||||
# Quote the expression and alias
|
||||
tmp.add(&"\"{expression}\" as \"{alias}\"")
|
||||
elif segment.contains("("):
|
||||
# Leave functions without alias as is
|
||||
tmp.add(segment)
|
||||
else:
|
||||
# Quote standalone identifiers
|
||||
tmp.add(&"\"{segment}\"")
|
||||
return tmp.join(".")
|
||||
|
||||
|
||||
# ==================== SELECT ====================
|
||||
|
||||
proc selectSql*(self: PostgresQuery): PostgresQuery =
|
||||
var queryString = ""
|
||||
|
||||
if self.query.hasKey("distinct"):
|
||||
queryString.add("SELECT DISTINCT")
|
||||
else:
|
||||
queryString.add("SELECT")
|
||||
|
||||
if self.query.hasKey("select"):
|
||||
for i, item in self.query["select"].getElems():
|
||||
if i > 0: queryString.add(",")
|
||||
var column = item.getStr()
|
||||
if column != "*": column = quote(column)
|
||||
queryString.add(&" {column}")
|
||||
else:
|
||||
queryString.add(" *")
|
||||
|
||||
self.queryString = queryString
|
||||
return self
|
||||
|
||||
|
||||
proc fromSql*(self: PostgresQuery): PostgresQuery =
|
||||
let table = self.query["table"].getStr()
|
||||
self.queryString.add(&" FROM \"{table}\"")
|
||||
return self
|
||||
|
||||
|
||||
proc selectFirstSql*(self: PostgresQuery): PostgresQuery =
|
||||
self.queryString.add(" LIMIT 1")
|
||||
return self
|
||||
|
||||
|
||||
proc selectByIdSql*(self: PostgresQuery, key:string): PostgresQuery =
|
||||
let key = key.quote()
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {key} = ? LIMIT 1")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {key} = ? LIMIT 1")
|
||||
return self
|
||||
|
||||
|
||||
proc joinSql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("join"):
|
||||
for row in self.query["join"]:
|
||||
let table = row["table"].getStr().quote()
|
||||
let column1 = row["column1"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
let column2 = row["column2"].getStr().quote()
|
||||
|
||||
self.queryString.add(&" INNER JOIN {table} ON {column1} {symbol} {column2}")
|
||||
return self
|
||||
|
||||
|
||||
proc leftJoinSql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("left_join"):
|
||||
for row in self.query["left_join"]:
|
||||
let table = row["table"].getStr().quote()
|
||||
let column1 = row["column1"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
let column2 = row["column2"].getStr().quote()
|
||||
|
||||
self.queryString.add(&" LEFT JOIN {table} ON {column1} {symbol} {column2}")
|
||||
return self
|
||||
|
||||
|
||||
proc whereSql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("where"):
|
||||
for i, row in self.query["where"].getElems():
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
# let value = row["value"].getStr()
|
||||
if i == 0:
|
||||
self.queryString.add(&" WHERE {column} {symbol} ?")
|
||||
else:
|
||||
self.queryString.add(&" AND {column} {symbol} ?")
|
||||
return self
|
||||
|
||||
|
||||
proc orWhereSql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("or_where"):
|
||||
for row in self.query["or_where"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
# let value = row["value"].getStr()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" OR {column} {symbol} ?")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} {symbol} ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetweenSql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("where_between"):
|
||||
for row in self.query["where_between"]:
|
||||
let column = row["column"].getStr()
|
||||
# let start = row["width"][0].getFloat()
|
||||
# let stop = row["width"][1].getFloat()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
# self.queryString.add(&" AND \"{column}\" BETWEEN {start} AND {stop}")
|
||||
self.queryString.add(&" AND \"{column}\" BETWEEN ? AND ?")
|
||||
else:
|
||||
# self.queryString.add(&" WHERE \"{column}\" BETWEEN {start} AND {stop}")
|
||||
self.queryString.add(&" WHERE \"{column}\" BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetweenStringSql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("where_between_string"):
|
||||
for row in self.query["where_between_string"]:
|
||||
let column = row["column"].getStr()
|
||||
# let start = row["width"][0].getStr
|
||||
# let stop = row["width"][1].getStr
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
# self.queryString.add(&" AND \"{column}\" BETWEEN '{start}' AND '{stop}'")
|
||||
self.queryString.add(&" AND \"{column}\" BETWEEN ? AND ?")
|
||||
else:
|
||||
# self.queryString.add(&" WHERE \"{column}\" BETWEEN '{start}' AND '{stop}'")
|
||||
self.queryString.add(&" WHERE \"{column}\" BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetweenSql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("where_not_between"):
|
||||
for row in self.query["where_not_between"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
# let start = row["width"][0].getFloat()
|
||||
# let stop = row["width"][1].getFloat()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
# self.queryString.add(&" AND \"{column}\" NOT BETWEEN {start} AND {stop}")
|
||||
self.queryString.add(&" AND {column} NOT BETWEEN ? AND ?")
|
||||
else:
|
||||
# self.queryString.add(&" WHERE \"{column}\" NOT BETWEEN {start} AND {stop}")
|
||||
self.queryString.add(&" WHERE {column} NOT BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetweenStringSql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("where_not_between_string"):
|
||||
for row in self.query["where_not_between_string"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
# let start = row["width"][0].getStr
|
||||
# let stop = row["width"][1].getStr
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
# self.queryString.add(&" AND \"{column}\" NOT BETWEEN '{start}' AND '{stop}'")
|
||||
self.queryString.add(&" AND {column} NOT BETWEEN ? AND ?")
|
||||
else:
|
||||
# self.queryString.add(&" WHERE \"{column}\" NOT BETWEEN '{start}' AND '{stop}'")
|
||||
self.queryString.add(&" WHERE {column} NOT BETWEEN ? AND ?")
|
||||
return self
|
||||
|
||||
|
||||
proc whereInSql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("where_in"):
|
||||
var widthString = ""
|
||||
for row in self.query["where_in"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
for i, val in row["width"].getElems():
|
||||
if i > 0: widthString.add(", ")
|
||||
widthString.add("?")
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} IN ({widthString})")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} IN ({widthString})")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotInSql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("where_not_in"):
|
||||
var widthString = ""
|
||||
for row in self.query["where_not_in"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
for i, val in row["width"].getElems():
|
||||
if i > 0: widthString.add(", ")
|
||||
widthString.add("?")
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} NOT IN ({widthString})")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} NOT IN ({widthString})")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNullSql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("where_null"):
|
||||
for row in self.query["where_null"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} {symbol} null")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} {symbol} null")
|
||||
return self
|
||||
|
||||
|
||||
proc groupBySql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("group_by"):
|
||||
for row in self.query["group_by"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
if self.queryString.contains("GROUP BY"):
|
||||
self.queryString.add(&", {column}")
|
||||
else:
|
||||
self.queryString.add(&" GROUP BY {column}")
|
||||
return self
|
||||
|
||||
|
||||
proc havingSql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("having"):
|
||||
for i, row in self.query["having"].getElems():
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
# let value = row["value"].getStr()
|
||||
|
||||
if i == 0:
|
||||
self.queryString.add(&" HAVING {column} {symbol} ?")
|
||||
else:
|
||||
self.queryString.add(&" AND {column} {symbol} ?")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc orderBySql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("order_by"):
|
||||
for row in self.query["order_by"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let order = row["order"].getStr()
|
||||
|
||||
if self.queryString.contains("ORDER BY"):
|
||||
self.queryString.add(&", {column} {order}")
|
||||
else:
|
||||
self.queryString.add(&" ORDER BY {column} {order}")
|
||||
return self
|
||||
|
||||
|
||||
proc limitSql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("limit"):
|
||||
let num = self.query["limit"].getInt()
|
||||
self.queryString.add(&" LIMIT {num}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc offsetSql*(self: PostgresQuery): PostgresQuery =
|
||||
if self.query.hasKey("offset"):
|
||||
let num = self.query["offset"].getInt()
|
||||
self.queryString.add(&" OFFSET {num}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
# ==================== INSERT ====================
|
||||
|
||||
proc insertSql*(self: PostgresQuery): PostgresQuery =
|
||||
let table = self.query["table"].getStr()
|
||||
self.queryString = &"INSERT INTO \"{table}\""
|
||||
return self
|
||||
|
||||
|
||||
proc insertValueSql*(self: PostgresQuery, items: JsonNode): PostgresQuery =
|
||||
var columns = ""
|
||||
var values = ""
|
||||
|
||||
var i = 0
|
||||
for key, val in items.pairs:
|
||||
defer: i += 1
|
||||
if i > 0:
|
||||
columns.add(", ")
|
||||
values.add(", ")
|
||||
# If column name contains Upper letter, column name is covered by double quote
|
||||
columns.add(&"\"{key}\"")
|
||||
|
||||
self.placeHolder.add(%*{"key":key, "value":val})
|
||||
values.add("?")
|
||||
|
||||
self.queryString.add(&" ({columns}) VALUES ({values})")
|
||||
return self
|
||||
|
||||
|
||||
proc insertValuesSql*(self: PostgresQuery, rows: openArray[JsonNode]): PostgresQuery =
|
||||
var columns = ""
|
||||
|
||||
var i = 0
|
||||
for key, value in rows[0]:
|
||||
defer: i += 1
|
||||
if i > 0: columns.add(", ")
|
||||
# If column name contains Upper letter, column name is covered by double quote
|
||||
columns.add(&"\"{key}\"")
|
||||
|
||||
var values = ""
|
||||
var valuesCount = 0
|
||||
for items in rows:
|
||||
var valueCount = 0
|
||||
var value = ""
|
||||
for key, val in items.pairs:
|
||||
defer: valueCount += 1
|
||||
if valueCount > 0: value.add(", ")
|
||||
|
||||
self.placeHolder.add(%*{"key":key, "value":val})
|
||||
value.add("?")
|
||||
|
||||
if valuesCount > 0: values.add(", ")
|
||||
valuesCount += 1
|
||||
values.add(&"({value})")
|
||||
|
||||
self.queryString.add(&" ({columns}) VALUES {values}")
|
||||
return self
|
||||
|
||||
|
||||
# ==================== UPDATE ====================
|
||||
|
||||
proc updateSql*(self: PostgresQuery): PostgresQuery =
|
||||
var queryString = ""
|
||||
queryString.add("UPDATE")
|
||||
|
||||
var table = self.query["table"].getStr()
|
||||
queryString.add(&" \"{table}\" SET")
|
||||
self.queryString = queryString
|
||||
return self
|
||||
|
||||
|
||||
proc updateValuesSql*(self: PostgresQuery, items:JsonNode): PostgresQuery =
|
||||
var value = ""
|
||||
let placeHolder = newJArray()
|
||||
|
||||
var i = 0
|
||||
for key, val in items.pairs:
|
||||
defer: i += 1
|
||||
if i > 0: value.add(",")
|
||||
value.add(&" \"{key}\" = ?")
|
||||
placeHolder.add(%*{"key":key, "value":val})
|
||||
|
||||
for row in self.placeHolder.items:
|
||||
placeHolder.add(row)
|
||||
|
||||
self.placeHolder = placeHolder
|
||||
|
||||
self.queryString.add(value)
|
||||
return self
|
||||
|
||||
|
||||
# ==================== DELETE ====================
|
||||
|
||||
proc deleteSql*(self: PostgresQuery): PostgresQuery =
|
||||
self.queryString = "DELETE"
|
||||
return self
|
||||
|
||||
|
||||
proc deleteByIdSql*(self: PostgresQuery, id: int, key: string): PostgresQuery =
|
||||
self.queryString.add(&" WHERE \"{key}\" = ?")
|
||||
return self
|
||||
|
||||
# ==================== Aggregates ====================
|
||||
|
||||
proc selectCountSql*(self: PostgresQuery): PostgresQuery =
|
||||
let queryString =
|
||||
if self.query.hasKey("select"):
|
||||
let column = self.query["select"][0].getStr
|
||||
&"\"{column}\""
|
||||
else:
|
||||
"*"
|
||||
self.queryString = &"SELECT count({queryString}) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectMaxSql*(self: PostgresQuery, column:string): PostgresQuery =
|
||||
self.queryString = &"SELECT max(\"{column}\") as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectMinSql*(self: PostgresQuery, column:string): PostgresQuery =
|
||||
self.queryString = &"SELECT min(\"{column}\") as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectAvgSql*(self: PostgresQuery, column:string): PostgresQuery =
|
||||
self.queryString = &"SELECT avg(\"{column}\") as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectSumSql*(self: PostgresQuery, column:string): PostgresQuery =
|
||||
self.queryString = &"SELECT sum(\"{column}\") as aggregate"
|
||||
return self
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
import std/json
|
||||
import ../sqlite_types
|
||||
import ./sqlite_generator
|
||||
|
||||
|
||||
# ==================== SELECT ====================
|
||||
|
||||
proc selectBuilder*(self: SqliteQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc selectFirstBuilder*(self: SqliteQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.selectFirstSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc selectFindBuilder*(self: SqliteQuery, key: string): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.selectByIdSql(key)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== INSERT ====================
|
||||
|
||||
proc insertValueBuilder*(self: SqliteQuery, items: JsonNode): string =
|
||||
## items is JOject
|
||||
return self
|
||||
.insertSql()
|
||||
.insertValueSql(items)
|
||||
.queryString
|
||||
|
||||
proc insertValuesBuilder*(self: SqliteQuery, rows: openArray[JsonNode]): string =
|
||||
return self
|
||||
.insertSql()
|
||||
.insertValuesSql(rows)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== UPDATE ====================
|
||||
|
||||
proc updateBuilder*(self: SqliteQuery, items: JsonNode): string =
|
||||
return self
|
||||
.updateSql()
|
||||
.updateValuesSql(items)
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== DELETE ====================
|
||||
|
||||
proc deleteBuilder*(self: SqliteQuery): string =
|
||||
return self
|
||||
.deleteSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc deleteByIdBuilder*(self: SqliteQuery, id: int, key: string): string =
|
||||
return self
|
||||
.deleteSql()
|
||||
.fromSql()
|
||||
.deleteByIdSql(id, key)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== Aggregates ====================
|
||||
|
||||
proc countBuilder*(self:SqliteQuery): string =
|
||||
return self
|
||||
.selectCountSql()
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc maxBuilder*(self:SqliteQuery, column:string): string =
|
||||
return self
|
||||
.selectMaxSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc minBuilder*(self:SqliteQuery, column:string): string =
|
||||
return self
|
||||
.selectMinSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc avgBuilder*(self:SqliteQuery, column:string): string =
|
||||
return self
|
||||
.selectAvgSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc sumBuilder*(self:SqliteQuery, column:string): string =
|
||||
return self
|
||||
.selectSumSql(column)
|
||||
.fromSql()
|
||||
.joinSql()
|
||||
.leftJoinSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.offsetSql()
|
||||
.queryString
|
||||
|
||||
proc columnBuilder*(self:SqliteQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.selectFirstSql()
|
||||
.queryString
|
||||
+431
@@ -0,0 +1,431 @@
|
||||
import std/json
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
# import ../../database_types
|
||||
import ../sqlite_types
|
||||
|
||||
|
||||
proc quote(input:string):string =
|
||||
## `User.id as userId` => `"User"."id" as "userId"`
|
||||
##
|
||||
## `COUNT(id) as count` => `COUNT("id") as "count"`
|
||||
##
|
||||
## `MAX(id) as maxId` => `MAX("id") as "maxId"`
|
||||
##
|
||||
var tmp = newSeq[string]()
|
||||
for segment in input.split("."):
|
||||
if segment.contains(" as "):
|
||||
# Split once on ' as ' to separate the expression and its alias
|
||||
let parts = segment.split(" as ", maxsplit = 1)
|
||||
let expression = parts[0]
|
||||
let alias = parts[1]
|
||||
if expression.contains("("):
|
||||
# Extract function name and column name using index-based slicing
|
||||
let funcStart = expression.find('(')
|
||||
let funcEnd = expression.find(')', funcStart)
|
||||
let funcName = expression[0 ..< funcStart]
|
||||
let columnName = expression[funcStart + 1 ..< funcEnd]
|
||||
tmp.add(&"{funcName}(\"{columnName}\") as \"{alias}\"")
|
||||
else:
|
||||
# Quote the expression and alias
|
||||
tmp.add(&"\"{expression}\" as \"{alias}\"")
|
||||
elif segment.contains("("):
|
||||
# Leave functions without alias as is
|
||||
tmp.add(segment)
|
||||
else:
|
||||
# Quote standalone identifiers
|
||||
tmp.add(&"\"{segment}\"")
|
||||
return tmp.join(".")
|
||||
|
||||
# ==================== SELECT ====================
|
||||
|
||||
proc selectSql*(self: SqliteQuery): SqliteQuery =
|
||||
var queryString = ""
|
||||
|
||||
if self.query.hasKey("distinct"):
|
||||
queryString.add("SELECT DISTINCT")
|
||||
else:
|
||||
queryString.add("SELECT")
|
||||
|
||||
if self.query.hasKey("select"):
|
||||
for i, item in self.query["select"].getElems():
|
||||
if i > 0: queryString.add(",")
|
||||
var column = item.getStr()
|
||||
if column != "*": column = quote(column)
|
||||
queryString.add(&" {column}")
|
||||
else:
|
||||
queryString.add(" *")
|
||||
|
||||
self.queryString = queryString
|
||||
return self
|
||||
|
||||
|
||||
proc fromSql*(self: SqliteQuery): SqliteQuery =
|
||||
let table = self.query["table"].getStr()
|
||||
self.queryString.add(&" FROM \"{table}\"")
|
||||
return self
|
||||
|
||||
|
||||
proc selectFirstSql*(self: SqliteQuery): SqliteQuery =
|
||||
self.queryString.add(" LIMIT 1")
|
||||
return self
|
||||
|
||||
|
||||
proc selectByIdSql*(self: SqliteQuery, key:string): SqliteQuery =
|
||||
let key = key.quote()
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {key} = ? LIMIT 1")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {key} = ? LIMIT 1")
|
||||
return self
|
||||
|
||||
|
||||
proc joinSql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("join"):
|
||||
for row in self.query["join"]:
|
||||
let table = row["table"].getStr().quote()
|
||||
let column1 = row["column1"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
let column2 = row["column2"].getStr().quote()
|
||||
|
||||
self.queryString.add(&" INNER JOIN {table} ON {column1} {symbol} {column2}")
|
||||
return self
|
||||
|
||||
|
||||
proc leftJoinSql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("left_join"):
|
||||
for row in self.query["left_join"]:
|
||||
let table = row["table"].getStr().quote()
|
||||
let column1 = row["column1"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
let column2 = row["column2"].getStr().quote()
|
||||
|
||||
self.queryString.add(&" LEFT JOIN {table} ON {column1} {symbol} {column2}")
|
||||
return self
|
||||
|
||||
|
||||
proc whereSql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("where"):
|
||||
for i, row in self.query["where"].getElems():
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
let value = row["value"].getStr()
|
||||
|
||||
if i == 0:
|
||||
self.queryString.add(&" WHERE {column} {symbol} {value}")
|
||||
else:
|
||||
self.queryString.add(&" AND {column} {symbol} {value}")
|
||||
return self
|
||||
|
||||
|
||||
proc orWhereSql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("or_where"):
|
||||
for row in self.query["or_where"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
let value = row["value"].getStr()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" OR {column} {symbol} {value}")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} {symbol} {value}")
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetweenSql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("where_between"):
|
||||
for row in self.query["where_between"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let start = row["width"][0].getFloat()
|
||||
let stop = row["width"][1].getFloat()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} BETWEEN {start} AND {stop}")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} BETWEEN {start} AND {stop}")
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetweenStringSql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("where_between_string"):
|
||||
for row in self.query["where_between_string"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let start = row["width"][0].getStr()
|
||||
let stop = row["width"][1].getStr()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} BETWEEN \"{start}\" AND \"{stop}\"")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} BETWEEN \"{start}\" AND \"{stop}\"")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetweenSql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("where_not_between"):
|
||||
for row in self.query["where_not_between"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let start = row["width"][0].getFloat()
|
||||
let stop = row["width"][1].getFloat()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} NOT BETWEEN {start} AND {stop}")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} NOT BETWEEN {start} AND {stop}")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetweenStringSql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("where_not_between_string"):
|
||||
for row in self.query["where_not_between_string"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let start = row["width"][0].getStr()
|
||||
let stop = row["width"][1].getStr()
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} NOT BETWEEN \"{start}\" AND \"{stop}\"")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} NOT BETWEEN \"{start}\" AND \"{stop}\"")
|
||||
return self
|
||||
|
||||
|
||||
proc whereInSql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("where_in"):
|
||||
var widthString = ""
|
||||
for row in self.query["where_in"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
for i, val in row["width"].getElems():
|
||||
if i > 0: widthString.add(", ")
|
||||
if val.kind == JInt:
|
||||
widthString.add($(val.getInt()))
|
||||
elif val.kind == JFloat:
|
||||
widthString.add($(val.getFloat()))
|
||||
elif val.kind == JString:
|
||||
widthString.add(&"\"{val.getStr()}\"")
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} IN ({widthString})")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} IN ({widthString})")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotInSql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("where_not_in"):
|
||||
var widthString = ""
|
||||
for row in self.query["where_not_in"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
for i, val in row["width"].getElems():
|
||||
if i > 0: widthString.add(", ")
|
||||
if val.kind == JInt:
|
||||
widthString.add($(val.getInt()))
|
||||
elif val.kind == JFloat:
|
||||
widthString.add($(val.getFloat()))
|
||||
elif val.kind == JString:
|
||||
widthString.add(&"\"{val.getStr()}\"")
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} NOT IN ({widthString})")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} NOT IN ({widthString})")
|
||||
return self
|
||||
|
||||
|
||||
proc whereNullSql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("where_null"):
|
||||
for row in self.query["where_null"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {column} is null")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} is null")
|
||||
return self
|
||||
|
||||
|
||||
proc groupBySql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("group_by"):
|
||||
for row in self.query["group_by"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
if self.queryString.contains("GROUP BY"):
|
||||
self.queryString.add(&", {column}")
|
||||
else:
|
||||
self.queryString.add(&" GROUP BY {column}")
|
||||
return self
|
||||
|
||||
|
||||
proc havingSql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("having"):
|
||||
for i, row in self.query["having"].getElems():
|
||||
let column = row["column"].getStr().quote()
|
||||
let symbol = row["symbol"].getStr()
|
||||
let value = row["value"].getStr()
|
||||
|
||||
if i == 0:
|
||||
self.queryString.add(&" HAVING {column} {symbol} {value}")
|
||||
else:
|
||||
self.queryString.add(&" AND {column} {symbol} {value}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc orderBySql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("order_by"):
|
||||
for row in self.query["order_by"]:
|
||||
let column = row["column"].getStr().quote()
|
||||
let order = row["order"].getStr()
|
||||
|
||||
if self.queryString.contains("ORDER BY"):
|
||||
self.queryString.add(&", {column} {order}")
|
||||
else:
|
||||
self.queryString.add(&" ORDER BY {column} {order}")
|
||||
return self
|
||||
|
||||
|
||||
proc limitSql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("limit"):
|
||||
let num = self.query["limit"].getInt()
|
||||
self.queryString.add(&" LIMIT {num}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc offsetSql*(self: SqliteQuery): SqliteQuery =
|
||||
if self.query.hasKey("offset"):
|
||||
let num = self.query["offset"].getInt()
|
||||
self.queryString.add(&" OFFSET {num}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
# ==================== INSERT ====================
|
||||
|
||||
proc insertSql*(self: SqliteQuery): SqliteQuery =
|
||||
let table = self.query["table"].getStr()
|
||||
self.queryString = &"INSERT INTO \"{table}\""
|
||||
return self
|
||||
|
||||
|
||||
proc insertValueSql*(self: SqliteQuery, items: JsonNode): SqliteQuery =
|
||||
## items is JObject
|
||||
var columns = ""
|
||||
var values = ""
|
||||
|
||||
var i = 0
|
||||
for key, val in items.pairs:
|
||||
if i > 0:
|
||||
columns.add(", ")
|
||||
values.add(", ")
|
||||
i += 1
|
||||
# If column name contains Upper letter, column name is covered by double quote
|
||||
columns.add(&"\"{key}\"")
|
||||
|
||||
self.placeHolder.add(%*{"key":key, "value":val})
|
||||
values.add("?")
|
||||
|
||||
self.queryString.add(&" ({columns}) VALUES ({values})")
|
||||
return self
|
||||
|
||||
|
||||
proc insertValuesSql*(self: SqliteQuery, rows: openArray[JsonNode]): SqliteQuery =
|
||||
var columns = ""
|
||||
|
||||
var i = 0
|
||||
for key, value in rows[0]:
|
||||
defer: i += 1
|
||||
if i > 0: columns.add(", ")
|
||||
# If column name contains Upper letter, column name is covered by double quote
|
||||
columns.add(&"\"{key}\"")
|
||||
|
||||
var values = ""
|
||||
var valuesCount = 0
|
||||
for items in rows:
|
||||
var valueCount = 0
|
||||
var value = ""
|
||||
for key, val in items.pairs:
|
||||
defer: valueCount += 1
|
||||
if valueCount > 0: value.add(", ")
|
||||
|
||||
self.placeHolder.add(%*{"key":key, "value":val})
|
||||
value.add("?")
|
||||
|
||||
if valuesCount > 0: values.add(", ")
|
||||
valuesCount += 1
|
||||
values.add(&"({value})")
|
||||
|
||||
self.queryString.add(&" ({columns}) VALUES {values}")
|
||||
return self
|
||||
|
||||
|
||||
# ==================== UPDATE ====================
|
||||
|
||||
proc updateSql*(self: SqliteQuery): SqliteQuery =
|
||||
var queryString = ""
|
||||
queryString.add("UPDATE")
|
||||
|
||||
var table = self.query["table"].getStr()
|
||||
queryString.add(&" \"{table}\" SET")
|
||||
self.queryString = queryString
|
||||
return self
|
||||
|
||||
|
||||
proc updateValuesSql*(self: SqliteQuery, items:JsonNode): SqliteQuery =
|
||||
var value = ""
|
||||
let placeHolder = newJArray()
|
||||
|
||||
var i = 0
|
||||
for key, val in items.pairs:
|
||||
defer: i.inc()
|
||||
if i > 0: value.add(",")
|
||||
value.add(&" \"{key}\" = ?")
|
||||
placeHolder.add(%*{"key":key, "value":val})
|
||||
|
||||
for row in self.placeHolder.items:
|
||||
placeHolder.add(row)
|
||||
|
||||
self.placeHolder = placeHolder
|
||||
|
||||
self.queryString.add(value)
|
||||
return self
|
||||
|
||||
|
||||
# ==================== DELETE ====================
|
||||
|
||||
proc deleteSql*(self: SqliteQuery): SqliteQuery =
|
||||
self.queryString = "DELETE"
|
||||
return self
|
||||
|
||||
|
||||
proc deleteByIdSql*(self: SqliteQuery, id: int, key: string): SqliteQuery =
|
||||
self.queryString.add(&" WHERE \"{key}\" = ?")
|
||||
return self
|
||||
|
||||
# ==================== Aggregates ====================
|
||||
|
||||
proc selectCountSql*(self: SqliteQuery): SqliteQuery =
|
||||
let queryString =
|
||||
if self.query.hasKey("select"):
|
||||
let column = self.query["select"][0].getStr
|
||||
&"{column}"
|
||||
else:
|
||||
"*"
|
||||
self.queryString = &"SELECT count({queryString}) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectMaxSql*(self: SqliteQuery, column:string): SqliteQuery =
|
||||
self.queryString = &"SELECT max({column}) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectMinSql*(self: SqliteQuery, column:string): SqliteQuery =
|
||||
self.queryString = &"SELECT min({column}) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectAvgSql*(self: SqliteQuery, column:string): SqliteQuery =
|
||||
self.queryString = &"SELECT avg({column}) as aggregate"
|
||||
return self
|
||||
|
||||
|
||||
proc selectSumSql*(self: SqliteQuery, column:string): SqliteQuery =
|
||||
self.queryString = &"SELECT sum({column}) as aggregate"
|
||||
return self
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,63 @@
|
||||
import std/asyncdispatch
|
||||
import std/deques
|
||||
import std/strutils
|
||||
import std/tables
|
||||
import std/times
|
||||
import ../../libs/database_url
|
||||
import ../../libs/sqlite/sqlite_rdb
|
||||
import ../../log
|
||||
import ./sqlite_types
|
||||
|
||||
|
||||
proc openSqlite(database: string; maxConnections: int; timeout: int;
|
||||
shouldDisplayLog: bool; shouldOutputLogFile: bool; logDir: string;
|
||||
maxConnectionLifetime: int = DEFAULT_CONN_MAX_LIFETIME_SECONDS;
|
||||
maxConnectionIdleTime: int = DEFAULT_CONN_MAX_IDLE_SECONDS): SqliteConnections =
|
||||
var conns = newSeq[Connection](maxConnections)
|
||||
for i in 0..<maxConnections:
|
||||
var db: PSqlite3
|
||||
discard sqlite_rdb.open(database, db)
|
||||
conns[i] = Connection(
|
||||
conn: db,
|
||||
isBusy: false,
|
||||
createdAt: getTime().toUnix(),
|
||||
lastUsedAt: getTime().toUnix(),
|
||||
)
|
||||
let pools = Connections(
|
||||
conns: conns,
|
||||
timeout: timeout,
|
||||
maxConnectionLifetime: maxConnectionLifetime,
|
||||
maxConnectionIdleTime: maxConnectionIdleTime,
|
||||
database: database,
|
||||
waiters: initDeque[Future[void]](),
|
||||
columnTypeCache: initTable[string, seq[(string, string)]](),
|
||||
preparedCache: initTable[string, SqlitePreparedEntry](),
|
||||
)
|
||||
result = SqliteConnections(
|
||||
pools: pools,
|
||||
log: LogSetting(shouldDisplayLog:shouldDisplayLog, shouldOutputLogFile:shouldOutputLogFile, logDir:logDir)
|
||||
)
|
||||
|
||||
|
||||
proc dbOpen*(_:type SQLite3, database: string = "",
|
||||
maxConnections: int = 1, timeout=30,
|
||||
shouldDisplayLog=false, shouldOutputLogFile=false, logDir="",
|
||||
maxConnectionLifetime=DEFAULT_CONN_MAX_LIFETIME_SECONDS,
|
||||
maxConnectionIdleTime=DEFAULT_CONN_MAX_IDLE_SECONDS): SqliteConnections =
|
||||
if database.len > 0 and database.contains("://"):
|
||||
let parsed = parseDatabaseUrl(asDatabaseUrl(database))
|
||||
requireDatabaseUrlScheme(parsed, ["sqlite"], "SQLite")
|
||||
return openSqlite(sqliteDatabasePath(parsed), maxConnections, timeout, shouldDisplayLog, shouldOutputLogFile, logDir, maxConnectionLifetime, maxConnectionIdleTime)
|
||||
|
||||
return openSqlite(database, maxConnections, timeout, shouldDisplayLog, shouldOutputLogFile, logDir, maxConnectionLifetime, maxConnectionIdleTime)
|
||||
|
||||
|
||||
proc dbOpen*(_:type SQLite3, databaseUrl: DatabaseUrl,
|
||||
maxConnections: int = 1, timeout=30,
|
||||
shouldDisplayLog=false, shouldOutputLogFile=false, logDir="",
|
||||
maxConnectionLifetime=DEFAULT_CONN_MAX_LIFETIME_SECONDS,
|
||||
maxConnectionIdleTime=DEFAULT_CONN_MAX_IDLE_SECONDS): SqliteConnections =
|
||||
let parsed = parseDatabaseUrl(databaseUrl)
|
||||
requireDatabaseUrlScheme(parsed, ["sqlite"], "SQLite")
|
||||
let database = sqliteDatabasePath(parsed)
|
||||
return openSqlite(database, maxConnections, timeout, shouldDisplayLog, shouldOutputLogFile, logDir, maxConnectionLifetime, maxConnectionIdleTime)
|
||||
@@ -0,0 +1,411 @@
|
||||
import std/asyncdispatch
|
||||
import std/json
|
||||
import std/options
|
||||
import std/strutils
|
||||
import std/times
|
||||
import ../../libs/sqlite/sqlite_impl
|
||||
import ../../enums
|
||||
import ./sqlite_types
|
||||
|
||||
|
||||
proc select*(self:SqliteConnections, columnsArg:varargs[string]):SqliteQuery =
|
||||
let query = newJObject()
|
||||
|
||||
if columnsArg.len == 0:
|
||||
query["select"] = %["*"]
|
||||
else:
|
||||
query["select"] = %columnsArg
|
||||
|
||||
let sqliteQuery = SqliteQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
query: query,
|
||||
queryString: "",
|
||||
placeHolder: newJArray(),
|
||||
isInTransaction: self.isInTransaction,
|
||||
transactionConn: self.transactionConn,
|
||||
)
|
||||
return sqliteQuery
|
||||
|
||||
|
||||
proc table*(self:SqliteConnections, tableArg: string): SqliteQuery =
|
||||
let query = newJObject()
|
||||
query["table"] = %tableArg
|
||||
|
||||
let sqliteQuery = SqliteQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
query: query,
|
||||
queryString: "",
|
||||
placeHolder: newJArray(),
|
||||
isInTransaction: self.isInTransaction,
|
||||
transactionConn: self.transactionConn,
|
||||
)
|
||||
return sqliteQuery
|
||||
|
||||
|
||||
proc table*(self:SqliteQuery, tableArg: string): SqliteQuery =
|
||||
self.query["table"] = %tableArg
|
||||
return self
|
||||
|
||||
|
||||
proc `distinct`*(self: SqliteQuery): SqliteQuery =
|
||||
self.query["distinct"] = %true
|
||||
return self
|
||||
|
||||
# ============================== Conditions ==============================
|
||||
|
||||
proc join*(self: SqliteQuery, table: string, column1: string, symbol: string,
|
||||
column2: string): SqliteQuery =
|
||||
if self.query.hasKey("join") == false:
|
||||
self.query["join"] = %*[{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
}]
|
||||
else:
|
||||
self.query["join"].add(%*{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc leftJoin*(self: SqliteQuery, table: string, column1: string, symbol: string,
|
||||
column2: string): SqliteQuery =
|
||||
if self.query.hasKey("left_join") == false:
|
||||
self.query["left_join"] = %*[{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
}]
|
||||
else:
|
||||
self.query["left_join"].add(%*{
|
||||
"table": table,
|
||||
"column1": column1,
|
||||
"symbol": symbol,
|
||||
"column2": column2
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
const whereSymbols = ["is", "is not", "=", "!=", "<", "<=", ">=", ">", "<>", "LIKE","%LIKE","LIKE%","%LIKE%"]
|
||||
const whereSymbolsError = """Arg position 3 is only allowed of ["is", "is not", "=", "!=", "<", "<=", ">=", ">", "<>", "LIKE","%LIKE","LIKE%","%LIKE%"]"""
|
||||
|
||||
proc where*(self: SqliteQuery, column: string, symbol: string,
|
||||
value: string|int|float): SqliteQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key": column, "value": value})
|
||||
|
||||
if self.query.hasKey("where") == false:
|
||||
self.query["where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc where*(self: SqliteQuery, column: string, symbol: string,
|
||||
value: bool): SqliteQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key":column, "value":value})
|
||||
|
||||
if self.query.hasKey("where") == false:
|
||||
self.query["where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc where*(self: SqliteQuery, column: string, symbol: string, value: nil.type): SqliteQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("where") == false:
|
||||
self.query["where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc orWhere*(self: SqliteQuery, column: string, symbol: string,
|
||||
value: string|int|float|bool): SqliteQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key":column, "value":value})
|
||||
|
||||
if self.query.hasKey("or_where") == false:
|
||||
self.query["or_where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["or_where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
proc orWhere*(self: SqliteQuery, column: string, symbol: string, value: nil.type): SqliteQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("or_where") == false:
|
||||
self.query["or_where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["or_where"].add(%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
})
|
||||
return self
|
||||
|
||||
proc whereBetween*(self:SqliteQuery, column:string, width:array[2, int|float]): SqliteQuery =
|
||||
if self.query.hasKey("where_between") == false:
|
||||
self.query["where_between"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_between"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
proc whereBetween*(self:SqliteQuery, column:string, width:array[2, string]): SqliteQuery =
|
||||
if self.query.hasKey("where_between_string") == false:
|
||||
self.query["where_between_string"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_between_string"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
proc whereNotBetween*(self:SqliteQuery, column:string, width:array[2, int|float]): SqliteQuery =
|
||||
if self.query.hasKey("where_not_between") == false:
|
||||
self.query["where_not_between"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_between"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
proc whereNotBetween*(self:SqliteQuery, column:string, width:array[2, string]): SqliteQuery =
|
||||
if self.query.hasKey("where_not_between_string") == false:
|
||||
self.query["where_not_between_string"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_between_string"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
proc whereIn*(self:SqliteQuery, column:string, width:seq[int|float|string]): SqliteQuery =
|
||||
if self.query.hasKey("where_in") == false:
|
||||
self.query["where_in"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_in"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotIn*(self:SqliteQuery, column:string, width:seq[int|float|string]): SqliteQuery =
|
||||
if self.query.hasKey("where_not_in") == false:
|
||||
self.query["where_not_in"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_in"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNull*(self:SqliteQuery, column:string): SqliteQuery =
|
||||
if self.query.hasKey("where_null") == false:
|
||||
self.query["where_null"] = %*[{
|
||||
"column": column
|
||||
}]
|
||||
else:
|
||||
self.query["where_null"].add(%*{
|
||||
"column": column
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc groupBy*(self:SqliteQuery, column:string): SqliteQuery =
|
||||
if self.query.hasKey("group_by") == false:
|
||||
self.query["group_by"] = %*[{"column": column}]
|
||||
else:
|
||||
self.query["group_by"].add(%*{"column": column})
|
||||
return self
|
||||
|
||||
|
||||
proc having*(self: SqliteQuery, column: string, symbol: string,
|
||||
value: string|int|float|bool): SqliteQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
self.placeHolder.add(%*{"key":column, "value":value})
|
||||
|
||||
if self.query.hasKey("having") == false:
|
||||
self.query["having"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}]
|
||||
else:
|
||||
self.query["having"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "?"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
proc having*(self: SqliteQuery, column: string, symbol: string, value: nil.type): SqliteQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("having") == false:
|
||||
self.query["having"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}]
|
||||
else:
|
||||
self.query["having"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": "null"
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc orderBy*(self:SqliteQuery, column:string, order:Order): SqliteQuery =
|
||||
if self.query.hasKey("order_by") == false:
|
||||
self.query["order_by"] = %*[{
|
||||
"column": column,
|
||||
"order": $order
|
||||
}]
|
||||
else:
|
||||
self.query["order_by"].add(%*{
|
||||
"column": column,
|
||||
"order": $order
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc limit*(self: SqliteQuery, num: int): SqliteQuery =
|
||||
self.query["limit"] = %num
|
||||
return self
|
||||
|
||||
|
||||
proc offset*(self: SqliteQuery, num: int): SqliteQuery =
|
||||
self.query["offset"] = %num
|
||||
return self
|
||||
|
||||
|
||||
proc raw*(self:SqliteConnections, sql:string, arges=newJArray()): RawSqliteQuery =
|
||||
## arges is `JArray`
|
||||
##
|
||||
## can't use BLOB data.
|
||||
let rawQueryRdb = RawSqliteQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
query: newJObject(),
|
||||
queryString: sql,
|
||||
placeHolder: arges,
|
||||
isInTransaction: false,
|
||||
transactionConn: 0
|
||||
)
|
||||
return rawQueryRdb
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import std/macros
|
||||
import std/strutils
|
||||
import std/strformat
|
||||
import ../../models/sqlite/sqlite_types
|
||||
|
||||
|
||||
when NimMajor == 1:
|
||||
macro rdbTransaction(rdb:SqliteConnections, callback: untyped):untyped =
|
||||
var callbackStr = callback.repr
|
||||
callbackStr.removePrefix
|
||||
callbackStr = callbackStr.indent(4)
|
||||
callbackStr = fmt"""
|
||||
block:
|
||||
{rdb.repr}.begin().await
|
||||
try:
|
||||
{callbackStr}
|
||||
{rdb.repr}.commit().await
|
||||
except:
|
||||
{rdb.repr}.rollback().await
|
||||
"""
|
||||
let body = callbackStr.parseStmt()
|
||||
return body
|
||||
else:
|
||||
macro rdbTransaction(rdb:SqliteConnections, callback: untyped):untyped =
|
||||
var callbackStr = callback.repr
|
||||
callbackStr.removePrefix
|
||||
callbackStr = callbackStr.indent(4)
|
||||
callbackStr = fmt"""
|
||||
block:
|
||||
{rdb.repr}.begin().await
|
||||
try:
|
||||
{callbackStr}
|
||||
{rdb.repr}.commit().await
|
||||
except DbError, CatchableError:
|
||||
{rdb.repr}.rollback().await
|
||||
"""
|
||||
let body = callbackStr.parseStmt()
|
||||
return body
|
||||
|
||||
|
||||
template transaction*(rdb:SqliteConnections, callback: untyped) =
|
||||
rdbTransaction(rdb, callback)
|
||||
@@ -0,0 +1,93 @@
|
||||
import std/asyncdispatch
|
||||
import std/deques
|
||||
import std/json
|
||||
import std/tables
|
||||
import ../database_types
|
||||
import ../../log
|
||||
import ../../libs/sqlite/sqlite_rdb
|
||||
|
||||
|
||||
type SQLite3* = object
|
||||
|
||||
|
||||
const DEFAULT_CONN_MAX_LIFETIME_SECONDS* = 300
|
||||
const DEFAULT_CONN_MAX_IDLE_SECONDS* = 300
|
||||
|
||||
|
||||
type Connection* = ref object
|
||||
conn*: PSqlite3
|
||||
isBusy*: bool
|
||||
createdAt*: int64
|
||||
lastUsedAt*: int64
|
||||
|
||||
|
||||
type SqlitePreparedEntry* = ref object
|
||||
sql*: string
|
||||
nArgs*: int
|
||||
stmts*: seq[PStmt]
|
||||
refCount*: int
|
||||
lastUsedAt*: int64
|
||||
|
||||
|
||||
type Connections* = ref object
|
||||
conns*: seq[Connection]
|
||||
timeout*: int
|
||||
maxConnectionLifetime*: int
|
||||
maxConnectionIdleTime*: int
|
||||
database*: string
|
||||
## `getFreeConn` が接続を待つときに積む Future。`returnConn` が先頭から 1 件だけ完了させる。
|
||||
waiters*: Deque[Future[void]]
|
||||
## `exec` / `insertId` 用。テーブルごとに PRAGMA table_info の結果を初回のみ保持する。
|
||||
columnTypeCache*: Table[string, seq[(string, string)]]
|
||||
preparedCache*: Table[string, SqlitePreparedEntry]
|
||||
|
||||
|
||||
## created by `let rdb = dbOpen(SQLite3, "/path/to/sqlite.db")`
|
||||
type SqliteConnections* = ref object
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
# for transaction
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
type SqlitePreparedContext* = ref object
|
||||
owner*: SqliteConnections
|
||||
connI*: int
|
||||
|
||||
|
||||
## created by `rdb.select("columnName")` or `rdb.table("tableName")`
|
||||
type SqliteQuery* = ref object
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
query*: JsonNode # JObject
|
||||
queryString*: string
|
||||
placeHolder*: JsonNode # JArray [{"key":"user", "value":"user1"}]
|
||||
# for transaction
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
type RawSqliteQuery* = ref object
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
query*: JsonNode # JObject
|
||||
queryString*: string
|
||||
placeHolder*: JsonNode # JArray ["user1", "user1@example.com"]
|
||||
# for transaction
|
||||
isInTransaction*: bool
|
||||
transactionConn*: int
|
||||
|
||||
|
||||
type SqlitePreparedStatement* = ref object
|
||||
owner*: SqliteConnections
|
||||
entry*: SqlitePreparedEntry
|
||||
sql*: string
|
||||
isClosed*: bool
|
||||
cachedColumns*: DbColumns
|
||||
hasCachedColumns*: bool
|
||||
|
||||
|
||||
|
||||
proc `$`*(self:SqliteConnections|SqliteQuery|RawSqliteQuery):string =
|
||||
return "SQLite"
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
import std/json
|
||||
import ../surreal_types
|
||||
import ./surreal_generator
|
||||
|
||||
|
||||
# ==================== SELECT ====================
|
||||
|
||||
proc selectBuilder*(self: SurrealQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
# .whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
# .whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
#.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.startSql()
|
||||
.fetchSql()
|
||||
.parallelSql()
|
||||
.queryString
|
||||
|
||||
|
||||
proc selectFirstBuilder*(self: SurrealQuery): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
# .whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
# .whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
#.havingSql()
|
||||
.orderBySql()
|
||||
.selectFirstSql() # LIMIT
|
||||
.startSql()
|
||||
.fetchSql()
|
||||
.parallelSql()
|
||||
.queryString
|
||||
|
||||
|
||||
proc selectFindBuilder*(self: SurrealQuery, id:SurrealId, key: string): string =
|
||||
return self
|
||||
.selectSql()
|
||||
.fromSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
# .whereBetweenStringSql()
|
||||
.whereNotBetweenSql()
|
||||
# .whereNotBetweenStringSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.selectByIdSql(id, key) # LIMIT
|
||||
.fetchSql()
|
||||
.parallelSql()
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== INSERT ====================
|
||||
|
||||
proc insertValueBuilder*(self: SurrealQuery, items: JsonNode): string =
|
||||
return self
|
||||
.insertSql()
|
||||
.insertValueSql(items)
|
||||
.parallelSql()
|
||||
.queryString
|
||||
|
||||
proc insertValuesBuilder*(self: SurrealQuery, items: openArray[JsonNode]): string =
|
||||
return self
|
||||
.insertSql()
|
||||
.insertValuesSql(items)
|
||||
.parallelSql()
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== UPDATE ====================
|
||||
|
||||
proc updateBuilder*(self:SurrealQuery, items:JsonNode):string =
|
||||
return self
|
||||
.updateSql()
|
||||
.updateValuesSql(items)
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.limitSql()
|
||||
.startSql()
|
||||
.parallelSql()
|
||||
.queryString
|
||||
|
||||
|
||||
proc updateMergeBuilder*(self:SurrealQuery, id:string, items:JsonNode):string =
|
||||
return self
|
||||
.updateMergeSql(id, items)
|
||||
# .whereSql()
|
||||
# .orWhereSql()
|
||||
# .limitSql()
|
||||
# .startSql()
|
||||
.parallelSql()
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== DELETE ====================
|
||||
|
||||
proc deleteBuilder*(self: SurrealQuery): string =
|
||||
return self
|
||||
.deleteSql()
|
||||
.fromSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.limitSql()
|
||||
.startSql()
|
||||
.queryString
|
||||
|
||||
proc deleteByIdBuilder*(self: SurrealQuery, id: string): string =
|
||||
return self
|
||||
.deleteSql()
|
||||
.deleteByIdSql(id)
|
||||
.queryString
|
||||
|
||||
|
||||
# ==================== Aggregates ====================
|
||||
|
||||
proc countBuilder*(self:SurrealQuery): string =
|
||||
let sql = self
|
||||
.selectCountSql()
|
||||
.fromSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
#.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.startSql()
|
||||
.fetchSql()
|
||||
.parallelSql()
|
||||
.queryString
|
||||
return sql & " GROUP ALL"
|
||||
|
||||
proc selectAvgBuilder*(self:SurrealQuery, column:string): string =
|
||||
return self
|
||||
.selectAvgSql(column)
|
||||
.fromSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
#.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.startSql()
|
||||
.fetchSql()
|
||||
.parallelSql()
|
||||
.groupAllSql()
|
||||
.queryString
|
||||
|
||||
proc selectSumBuilder*(self:SurrealQuery, column:string): string =
|
||||
return self
|
||||
.selectSumSql(column)
|
||||
.fromSql()
|
||||
.whereSql()
|
||||
.orWhereSql()
|
||||
.whereBetweenSql()
|
||||
.whereNotBetweenSql()
|
||||
.whereInSql()
|
||||
.whereNotInSql()
|
||||
.whereNullSql()
|
||||
.groupBySql()
|
||||
#.havingSql()
|
||||
.orderBySql()
|
||||
.limitSql()
|
||||
.startSql()
|
||||
.fetchSql()
|
||||
.parallelSql()
|
||||
.groupAllSql()
|
||||
.queryString
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
import std/json
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
# import ../databases/surreal_lib
|
||||
import ../surreal_types
|
||||
|
||||
|
||||
# proc quoteTable*(input:var string) =
|
||||
# input = &"`{input}`"
|
||||
|
||||
proc quote*(input:string):string =
|
||||
## "User.id as userId" => `User`.`id` as `userId`
|
||||
##
|
||||
## "count(id) as count" => count(`id`) as `count`
|
||||
##
|
||||
## "math::max(id) as maxId" => math::max(`id`) as `maxId`
|
||||
##
|
||||
var tmp = newSeq[string]()
|
||||
for segment in input.split("."):
|
||||
if segment.contains(" as "):
|
||||
# Split once on ' as ' to separate the expression and its alias
|
||||
let parts = segment.split(" as ", maxsplit = 1)
|
||||
let expression = parts[0]
|
||||
let alias = parts[1]
|
||||
if expression.contains("("):
|
||||
# Extract function name and column name using index-based slicing
|
||||
let funcStart = expression.find('(')
|
||||
let funcEnd = expression.find(')', funcStart)
|
||||
let funcName = expression[0 ..< funcStart]
|
||||
let columnName = expression[funcStart + 1 ..< funcEnd]
|
||||
|
||||
if columnName.len > 0:
|
||||
tmp.add(&"{funcName}(`{columnName}`) as `{alias}`")
|
||||
else:
|
||||
tmp.add(&"{funcName}() as `{alias}`")
|
||||
else:
|
||||
# Quote the expression and alias
|
||||
tmp.add(&"`{expression}` as `{alias}`")
|
||||
elif segment.contains("("):
|
||||
# Leave functions without alias as is
|
||||
tmp.add(segment)
|
||||
else:
|
||||
# Quote standalone identifiers
|
||||
tmp.add(&"`{segment}`")
|
||||
return tmp.join(".")
|
||||
|
||||
|
||||
# ==================== SELECT ====================
|
||||
proc selectSql*(self: SurrealQuery): SurrealQuery =
|
||||
var queryString = "SELECT"
|
||||
|
||||
if self.query.hasKey("select"):
|
||||
for i, item in self.query["select"].getElems():
|
||||
if i > 0: queryString.add(",")
|
||||
var column = item.getStr()
|
||||
column = quote(column)
|
||||
queryString.add(&" {column}")
|
||||
else:
|
||||
queryString.add(" *")
|
||||
|
||||
self.queryString = queryString
|
||||
return self
|
||||
|
||||
|
||||
proc fromSql*(self: SurrealQuery): SurrealQuery =
|
||||
var table = self.query["table"].getStr()
|
||||
table = quote(table)
|
||||
self.queryString.add(&" FROM {table}")
|
||||
return self
|
||||
|
||||
|
||||
proc maxFromSql*(self:SurrealQuery, column:string):SurrealQuery =
|
||||
var table = self.query["table"].getStr()
|
||||
table = quote(table)
|
||||
self.queryString.add(&" FROM array::max({column})")
|
||||
return self
|
||||
|
||||
|
||||
proc selectFirstSql*(self:SurrealQuery): SurrealQuery =
|
||||
self.queryString.add(" LIMIT 1")
|
||||
return self
|
||||
|
||||
|
||||
proc selectByIdSql*(self:SurrealQuery, id:SurrealId, key:string): SurrealQuery =
|
||||
var key = key
|
||||
key = quote(key)
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND {key} = ? LIMIT 1")
|
||||
self.placeHolder.add(%id.rawId)
|
||||
else:
|
||||
self.queryString.add(&" WHERE {key} = ? LIMIT 1")
|
||||
self.placeHolder.add(%id.rawId)
|
||||
return self
|
||||
|
||||
|
||||
proc whereSql*(self: SurrealQuery): SurrealQuery =
|
||||
if self.query.hasKey("where"):
|
||||
for i, row in self.query["where"].getElems():
|
||||
var column = row["column"].getStr()
|
||||
column = quote(column)
|
||||
var symbol = row["symbol"].getStr()
|
||||
var value = row["value"]
|
||||
|
||||
if i == 0:
|
||||
self.queryString.add(&" WHERE {column} {symbol} ?")
|
||||
self.placeHolder.add(value)
|
||||
else:
|
||||
self.queryString.add(&" AND {column} {symbol} ?")
|
||||
self.placeHolder.add(value)
|
||||
return self
|
||||
|
||||
|
||||
proc orWhereSql*(self: SurrealQuery): SurrealQuery =
|
||||
if self.query.hasKey("or_where"):
|
||||
for row in self.query["or_where"]:
|
||||
var column = row["column"].getStr()
|
||||
column = quote(column)
|
||||
var symbol = row["symbol"].getStr()
|
||||
var value = row["value"]
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" OR {column} {symbol} ?")
|
||||
self.placeHolder.add(value)
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} {symbol} ?")
|
||||
self.placeHolder.add(value)
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetweenSql*(self: SurrealQuery): SurrealQuery =
|
||||
if self.query.hasKey("where_between"):
|
||||
for row in self.query["where_between"]:
|
||||
var column = row["column"].getStr()
|
||||
column = quote(column)
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND ? <= {column} AND {column} <= ?")
|
||||
else:
|
||||
self.queryString.add(&" WHERE ? <= {column} AND {column} <= ?")
|
||||
|
||||
self.placeHolder.add(row["width"][0])
|
||||
self.placeHolder.add(row["width"][1])
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotBetweenSql*(self: SurrealQuery): SurrealQuery =
|
||||
if self.query.hasKey("where_not_between"):
|
||||
for row in self.query["where_not_between"]:
|
||||
var column = row["column"].getStr()
|
||||
column = quote(column)
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND ? > {column} AND {column} < ?")
|
||||
else:
|
||||
self.queryString.add(&" WHERE ? > {column} AND {column} < ?")
|
||||
|
||||
self.placeHolder.add(row["width"][0])
|
||||
self.placeHolder.add(row["width"][1])
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc whereInSql*(self: SurrealQuery): SurrealQuery =
|
||||
if self.query.hasKey("where_in"):
|
||||
for row in self.query["where_in"]:
|
||||
var column = row["column"].getStr()
|
||||
column = quote(column)
|
||||
|
||||
var values = ""
|
||||
var i = 0
|
||||
for row in row["width"].items:
|
||||
defer: i.inc()
|
||||
if i > 0: values.add(", ")
|
||||
values.add("?")
|
||||
self.placeHolder.add(row)
|
||||
values.add("")
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND [{values}] CONTAINS {column}")
|
||||
else:
|
||||
self.queryString.add(&" WHERE [{values}] CONTAINS {column}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotInSql*(self: SurrealQuery): SurrealQuery =
|
||||
if self.query.hasKey("where_not_in"):
|
||||
for row in self.query["where_not_in"]:
|
||||
var column = row["column"].getStr()
|
||||
column = quote(column)
|
||||
|
||||
var values = ""
|
||||
var i = 0
|
||||
for row in row["width"].items:
|
||||
defer: i.inc()
|
||||
if i > 0: values.add(", ")
|
||||
values.add("?")
|
||||
self.placeHolder.add(row)
|
||||
values.add("")
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" AND [{values}] CONTAINSNOT {column}")
|
||||
else:
|
||||
self.queryString.add(&" WHERE [{values}] CONTAINSNOT {column}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc whereNullSql*(self: SurrealQuery): SurrealQuery =
|
||||
if self.query.hasKey("where_null"):
|
||||
for row in self.query["where_null"]:
|
||||
var column = row["column"].getStr()
|
||||
column = quote(column)
|
||||
|
||||
if self.queryString.contains("WHERE"):
|
||||
self.queryString.add(&" OR {column} IS NONE")
|
||||
else:
|
||||
self.queryString.add(&" WHERE {column} IS NONE")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc groupBySql*(self:SurrealQuery): SurrealQuery =
|
||||
if self.query.hasKey("group_by"):
|
||||
for row in self.query["group_by"]:
|
||||
var column = row["column"].getStr()
|
||||
column = quote(column)
|
||||
if self.queryString.contains("GROUP BY"):
|
||||
self.queryString.add(&", {column}")
|
||||
else:
|
||||
self.queryString.add(&" GROUP BY {column}")
|
||||
return self
|
||||
|
||||
|
||||
proc orderBySql*(self:SurrealQuery): SurrealQuery =
|
||||
if self.query.hasKey("order_by"):
|
||||
for row in self.query["order_by"]:
|
||||
var column = row["column"].getStr()
|
||||
column = quote(column)
|
||||
let collation = row["collation"].getStr()
|
||||
let order = row["order"].getStr()
|
||||
|
||||
if self.queryString.contains("ORDER BY"):
|
||||
self.queryString.add(&", {column} {collation} {order}")
|
||||
else:
|
||||
self.queryString.add(&" ORDER BY {column} {collation} {order}")
|
||||
return self
|
||||
|
||||
|
||||
proc limitSql*(self: SurrealQuery): SurrealQuery =
|
||||
if self.query.hasKey("limit"):
|
||||
var num = self.query["limit"].getInt()
|
||||
self.queryString.add(&" LIMIT {num}")
|
||||
return self
|
||||
|
||||
|
||||
proc startSql*(self: SurrealQuery): SurrealQuery =
|
||||
if self.query.hasKey("start"):
|
||||
var num = self.query["start"].getInt()
|
||||
self.queryString.add(&" START {num}")
|
||||
return self
|
||||
|
||||
|
||||
proc fetchSql*(self:SurrealQuery):SurrealQuery =
|
||||
if self.query.hasKey("fetch"):
|
||||
self.queryString.add(" FETCH")
|
||||
for i, item in self.query["fetch"].getElems():
|
||||
if i > 0: self.queryString.add(",")
|
||||
var column = item.getStr()
|
||||
column = quote(column)
|
||||
self.queryString.add(&" {column}")
|
||||
return self
|
||||
|
||||
|
||||
proc parallelSql*(self:SurrealQuery):SurrealQuery =
|
||||
if self.query.hasKey("parallel") and self.query["parallel"].getBool():
|
||||
self.queryString.add(" PARALLEL")
|
||||
return self
|
||||
|
||||
|
||||
# ==================== INSERT ====================
|
||||
|
||||
proc insertSql*(self: SurrealQuery): SurrealQuery =
|
||||
var table = self.query["table"].getStr()
|
||||
table = quote(table)
|
||||
self.queryString = &"INSERT INTO {table}"
|
||||
return self
|
||||
|
||||
|
||||
proc insertValueSql*(self: SurrealQuery, items: JsonNode): SurrealQuery =
|
||||
self.queryString.add(" {")
|
||||
var i = 0
|
||||
for (key, item) in items.pairs:
|
||||
defer: i.inc()
|
||||
if i > 0: self.queryString.add(", ")
|
||||
self.queryString.add(&"{key}: ?")
|
||||
self.placeHolder.add(item)
|
||||
self.queryString.add("}")
|
||||
return self
|
||||
|
||||
|
||||
proc insertValuesSql*(self: SurrealQuery, rows: openArray[JsonNode]): SurrealQuery =
|
||||
self.queryString.add(" [")
|
||||
for i, row in rows:
|
||||
if i > 0: self.queryString.add(", ")
|
||||
var j = 0
|
||||
self.queryString.add("{")
|
||||
for (key, item) in row.pairs:
|
||||
defer: j.inc()
|
||||
if j > 0: self.queryString.add(", ")
|
||||
self.queryString.add(&"{key}: ?")
|
||||
self.placeHolder.add(item)
|
||||
self.queryString.add("}")
|
||||
self.queryString.add("]")
|
||||
return self
|
||||
|
||||
|
||||
# ==================== UPDATE ====================
|
||||
|
||||
proc updateSql*(self: SurrealQuery): SurrealQuery =
|
||||
var queryString = ""
|
||||
queryString.add("UPDATE")
|
||||
|
||||
var table = self.query["table"].getStr()
|
||||
table = quote(table)
|
||||
queryString.add(&" {table} SET ")
|
||||
self.queryString = queryString
|
||||
return self
|
||||
|
||||
|
||||
proc updateValuesSql*(self: SurrealQuery, items:JsonNode): SurrealQuery =
|
||||
var i = 0
|
||||
for key, val in items.pairs:
|
||||
defer: i.inc()
|
||||
if i > 0: self.queryString.add(", ")
|
||||
self.queryString.add(&"{key} = ?")
|
||||
self.placeHolder.add(val)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
proc updateMergeSql*(self: SurrealQuery, id:string, items:JsonNode):SurrealQuery =
|
||||
self.queryString = &"UPDATE {id} MERGE {items}"
|
||||
return self
|
||||
|
||||
|
||||
# ==================== DELETE ====================
|
||||
|
||||
proc deleteSql*(self: SurrealQuery): SurrealQuery =
|
||||
self.queryString = "DELETE"
|
||||
return self
|
||||
|
||||
|
||||
proc deleteByIdSql*(self: SurrealQuery, id: string): SurrealQuery =
|
||||
self.queryString.add(&" ?")
|
||||
self.placeHolder.add(%id)
|
||||
return self
|
||||
|
||||
|
||||
# ==================== Aggregates ====================
|
||||
|
||||
proc selectCountSql*(self: SurrealQuery): SurrealQuery =
|
||||
var queryString =
|
||||
if self.query.hasKey("select"):
|
||||
var column = self.query["select"][0].getStr
|
||||
column = quote(column)
|
||||
&"{column}"
|
||||
else:
|
||||
""
|
||||
self.queryString = &"SELECT count({queryString}) AS total"
|
||||
return self
|
||||
|
||||
|
||||
proc selectAvgSql*(self: SurrealQuery, column:string): SurrealQuery =
|
||||
var column = column
|
||||
column = quote(column)
|
||||
self.queryString = &"SELECT math::mean({column}) AS avg"
|
||||
return self
|
||||
|
||||
|
||||
proc selectSumSql*(self: SurrealQuery, column:string): SurrealQuery =
|
||||
var column = column
|
||||
column = quote(column)
|
||||
self.queryString = &"SELECT math::sum({column}) AS sum"
|
||||
return self
|
||||
|
||||
|
||||
proc groupAllSql*(self: SurrealQuery):SurrealQuery =
|
||||
self.queryString.add(" GROUP ALL")
|
||||
return self
|
||||
+982
@@ -0,0 +1,982 @@
|
||||
import std/asyncdispatch
|
||||
import std/deques
|
||||
import std/json
|
||||
import std/monotimes
|
||||
import std/options
|
||||
import std/sequtils
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
import std/tables
|
||||
import std/times
|
||||
import ../../error
|
||||
import ../../libs/surreal/surreal_lib
|
||||
import ../../libs/surreal/surreal_impl
|
||||
import ../../log
|
||||
import ../../enums
|
||||
import ../database_types
|
||||
import ../../prepared_param
|
||||
import ./query/surreal_builder
|
||||
import ./surreal_types
|
||||
import ./surreal_query
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# connection
|
||||
# ================================================================================
|
||||
|
||||
proc removePoolWaiter(pools: Connections, w: Future[void]) =
|
||||
var kept = initDeque[Future[void]]()
|
||||
while pools.waiters.len > 0:
|
||||
let x = pools.waiters.popFirst()
|
||||
if x != w:
|
||||
kept.addLast(x)
|
||||
pools.waiters = move(kept)
|
||||
|
||||
proc wakeOnePoolWaiter(pools: Connections) =
|
||||
while pools.waiters.len > 0:
|
||||
let w = pools.waiters.popFirst()
|
||||
if w.finished:
|
||||
continue
|
||||
w.complete()
|
||||
break
|
||||
|
||||
proc surrealPoolRemainingMs(deadline: MonoTime): int =
|
||||
let left = (deadline - getMonoTime()).inMilliseconds
|
||||
if left <= 0:
|
||||
return 0
|
||||
if left > int64(high(int)):
|
||||
return high(int)
|
||||
result = int(left)
|
||||
if result < 1:
|
||||
result = 1
|
||||
|
||||
proc getFreeConn(self: SurrealConnections | SurrealQuery | RawSurrealQuery): Future[int] {.async.} =
|
||||
let deadline = getMonoTime() + initDuration(seconds = self.pools.timeout)
|
||||
while true:
|
||||
for i in 0 ..< self.pools.conns.len:
|
||||
if not self.pools.conns[i].isBusy:
|
||||
self.pools.conns[i].isBusy = true
|
||||
when defined(check_pool):
|
||||
echo "=== getFreeConn ", i
|
||||
return i
|
||||
if getMonoTime() >= deadline:
|
||||
return errorConnectionNum
|
||||
let w = newFuture[void]("getFreeConn.poolWait")
|
||||
self.pools.waiters.addLast(w)
|
||||
var ms = surrealPoolRemainingMs(deadline)
|
||||
if ms < 1:
|
||||
ms = 1
|
||||
let ok = await withTimeout(w, ms)
|
||||
if not ok:
|
||||
removePoolWaiter(self.pools, w)
|
||||
return errorConnectionNum
|
||||
|
||||
|
||||
proc returnConn(self: SurrealConnections | SurrealQuery | RawSurrealQuery, i: int) {.async.} =
|
||||
if i != errorConnectionNum:
|
||||
self.pools.conns[i].isBusy = false
|
||||
wakeOnePoolWaiter(self.pools)
|
||||
|
||||
|
||||
proc raisePoolTimeout(self: SurrealConnections | SurrealQuery | RawSurrealQuery | SurrealPreparedStatement) {.noreturn.} =
|
||||
raise newException(DbError, "Timed out while waiting for a free SurrealDB connection")
|
||||
|
||||
|
||||
proc touchStmtEntry(entry: SurrealPreparedEntry) =
|
||||
entry.lastUsedAt = getTime().toUnix()
|
||||
|
||||
|
||||
proc mustBeOpen(self: SurrealPreparedStatement) =
|
||||
if self.isNil or self.owner.isNil or self.isClosed:
|
||||
raise newException(DbError, "SurrealDB prepared statement is already closed")
|
||||
|
||||
|
||||
proc hasPreparedEntry(cache: Table[string, SurrealPreparedEntry], sql: string): bool =
|
||||
for key in cache.keys:
|
||||
if key == sql:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
proc getStmtEntry(self: SurrealConnections, sql: string): SurrealPreparedEntry =
|
||||
if hasPreparedEntry(self.pools.preparedCache, sql):
|
||||
return self.pools.preparedCache[sql]
|
||||
let entry = SurrealPreparedEntry(
|
||||
sql: sql,
|
||||
normalizedSql: sql.questionToDaller(),
|
||||
nArgs: countQuestionMarks(sql),
|
||||
refCount: 0,
|
||||
lastUsedAt: getTime().toUnix(),
|
||||
)
|
||||
self.pools.preparedCache[sql] = entry
|
||||
return entry
|
||||
|
||||
|
||||
proc prepare*(self: SurrealConnections, sql: string): SurrealPreparedStatement =
|
||||
let entry = self.getStmtEntry(sql)
|
||||
entry.refCount += 1
|
||||
touchStmtEntry(entry)
|
||||
new(result)
|
||||
result.owner = self
|
||||
result.entry = entry
|
||||
result.sql = sql
|
||||
result.nArgs = entry.nArgs
|
||||
result.isClosed = false
|
||||
|
||||
|
||||
proc withConn*(
|
||||
self: SurrealConnections,
|
||||
body: proc (ctx: SurrealPreparedContext): Future[void]
|
||||
) {.async.} =
|
||||
let connI = getFreeConn(self).await
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
defer:
|
||||
self.returnConn(connI).await
|
||||
|
||||
let ctx = SurrealPreparedContext(owner: self, connI: connI)
|
||||
await body(ctx)
|
||||
|
||||
|
||||
proc verifyCtx(self: SurrealPreparedStatement, ctx: SurrealPreparedContext) =
|
||||
self.mustBeOpen()
|
||||
if ctx.isNil:
|
||||
raise newException(DbError, "SurrealDB prepared context is nil")
|
||||
if ctx.owner.isNil or ctx.owner != self.owner:
|
||||
raise newException(DbError, "SurrealDB prepared context owner mismatch")
|
||||
if ctx.connI < 0 or ctx.connI >= self.owner.pools.conns.len:
|
||||
raise newException(DbError, "SurrealDB prepared context has invalid connection index")
|
||||
|
||||
|
||||
proc toPreparedArgsJson(args: seq[string]): JsonNode =
|
||||
result = newJArray()
|
||||
for arg in args:
|
||||
if arg == "NULL" or arg == "null":
|
||||
result.add(newJNull())
|
||||
else:
|
||||
result.add(%arg)
|
||||
|
||||
|
||||
proc buildPreparedSql(self: SurrealPreparedStatement, args: JsonNode): string =
|
||||
self.mustBeOpen()
|
||||
touchStmtEntry(self.entry)
|
||||
result = dbFormatPrepared(self.entry.normalizedSql, args)
|
||||
|
||||
|
||||
proc getPreparedRowsOnConn(
|
||||
self: SurrealPreparedStatement,
|
||||
connI: int,
|
||||
args: JsonNode
|
||||
): Future[seq[JsonNode]] {.async.} =
|
||||
self.mustBeOpen()
|
||||
if connI < 0 or connI >= self.owner.pools.conns.len:
|
||||
raise newException(DbError, "SurrealDB prepared statement received an invalid connection index")
|
||||
|
||||
let sql = self.buildPreparedSql(args)
|
||||
let rows = surreal_impl.query(
|
||||
self.owner.pools.conns[connI].conn,
|
||||
sql,
|
||||
newJArray(),
|
||||
self.owner.pools.timeout
|
||||
).await
|
||||
|
||||
if rows.kind != JArray or rows.len == 0:
|
||||
return newSeq[JsonNode](0)
|
||||
return rows.getElems()
|
||||
|
||||
|
||||
proc getPreparedRowOnConn(
|
||||
self: SurrealPreparedStatement,
|
||||
connI: int,
|
||||
args: JsonNode
|
||||
): Future[Option[JsonNode]] {.async.} =
|
||||
let rows = await self.getPreparedRowsOnConn(connI, args)
|
||||
if rows.len == 0:
|
||||
return none(JsonNode)
|
||||
return rows[0].some
|
||||
|
||||
|
||||
proc execPreparedOnConn(
|
||||
self: SurrealPreparedStatement,
|
||||
connI: int,
|
||||
args: JsonNode
|
||||
) {.async.} =
|
||||
self.mustBeOpen()
|
||||
if connI < 0 or connI >= self.owner.pools.conns.len:
|
||||
raise newException(DbError, "SurrealDB prepared statement received an invalid connection index")
|
||||
|
||||
let sql = self.buildPreparedSql(args)
|
||||
await surreal_impl.exec(
|
||||
self.owner.pools.conns[connI].conn,
|
||||
sql,
|
||||
newJArray(),
|
||||
self.owner.pools.timeout
|
||||
)
|
||||
|
||||
|
||||
proc getPreparedRows(self: SurrealPreparedStatement, args: JsonNode): Future[seq[JsonNode]] {.async.} =
|
||||
let connI = await getFreeConn(self.owner)
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
defer:
|
||||
await self.owner.returnConn(connI)
|
||||
return await self.getPreparedRowsOnConn(connI, args)
|
||||
|
||||
|
||||
proc getPreparedRows(self: SurrealPreparedStatement, ctx: SurrealPreparedContext, args: JsonNode): Future[seq[JsonNode]] {.async.} =
|
||||
self.verifyCtx(ctx)
|
||||
return await self.getPreparedRowsOnConn(ctx.connI, args)
|
||||
|
||||
|
||||
proc getPreparedRow(self: SurrealPreparedStatement, args: JsonNode): Future[Option[JsonNode]] {.async.} =
|
||||
let rows = await self.getPreparedRows(args)
|
||||
if rows.len == 0:
|
||||
return none(JsonNode)
|
||||
return rows[0].some
|
||||
|
||||
|
||||
proc getPreparedRow(self: SurrealPreparedStatement, ctx: SurrealPreparedContext, args: JsonNode): Future[Option[JsonNode]] {.async.} =
|
||||
self.verifyCtx(ctx)
|
||||
return await self.getPreparedRowOnConn(ctx.connI, args)
|
||||
|
||||
|
||||
proc execPrepared(self: SurrealPreparedStatement, args: JsonNode) {.async.} =
|
||||
let connI = await getFreeConn(self.owner)
|
||||
if connI == errorConnectionNum:
|
||||
raisePoolTimeout(self)
|
||||
defer:
|
||||
await self.owner.returnConn(connI)
|
||||
await self.execPreparedOnConn(connI, args)
|
||||
|
||||
|
||||
proc execPrepared(self: SurrealPreparedStatement, ctx: SurrealPreparedContext, args: JsonNode) {.async.} =
|
||||
self.verifyCtx(ctx)
|
||||
await self.execPreparedOnConn(ctx.connI, args)
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# toJson
|
||||
# ================================================================================
|
||||
|
||||
# proc toJson(results:openArray[seq[string]], dbRows:DbRows):seq[JsonNode] =
|
||||
# var response_table = newSeq[JsonNode](results.len)
|
||||
# for index, rows in results.pairs:
|
||||
# var response_row = newJObject()
|
||||
# for i, row in rows:
|
||||
# let key = dbRows[index][i].name
|
||||
# let typ = dbRows[index][i].typ.kind
|
||||
# # let kindName = dbRows[index][i].typ.name
|
||||
# # let size = dbRows[index][i].typ.size
|
||||
|
||||
# if typ == dbNull:
|
||||
# response_row[key] = newJNull()
|
||||
# elif [dbInt, dbUInt].contains(typ):
|
||||
# response_row[key] = newJInt(row.parseInt)
|
||||
# elif [dbDecimal, dbFloat].contains(typ):
|
||||
# response_row[key] = newJFloat(row.parseFloat)
|
||||
# elif [dbBool].contains(typ):
|
||||
# if row == "f":
|
||||
# response_row[key] = newJBool(false)
|
||||
# elif row == "t":
|
||||
# response_row[key] = newJBool(true)
|
||||
# elif [dbJson].contains(typ):
|
||||
# response_row[key] = row.parseJson
|
||||
# elif [dbFixedChar, dbVarchar].contains(typ):
|
||||
# if row == "NULL":
|
||||
# response_row[key] = newJNull()
|
||||
# else:
|
||||
# response_row[key] = newJString(row)
|
||||
# else:
|
||||
# response_row[key] = newJString(row)
|
||||
|
||||
# response_table[index] = response_row
|
||||
# return response_table
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# private exec
|
||||
# ================================================================================
|
||||
|
||||
proc getAllRows(self:SurrealQuery, queryString:string):Future[seq[JsonNode]] {.async.} =
|
||||
var connI = getFreeConn(self).await
|
||||
defer:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
return
|
||||
|
||||
let rows = surreal_impl.query(
|
||||
self.pools.conns[connI].conn,
|
||||
queryString,
|
||||
self.placeHolder,
|
||||
self.pools.timeout
|
||||
).await
|
||||
|
||||
if rows.len == 0:
|
||||
self.log.echoErrorMsg(queryString)
|
||||
return newSeq[JsonNode](0)
|
||||
return rows.getElems() # seq[JsonNode]
|
||||
|
||||
|
||||
proc getRow(self:SurrealQuery, queryString:string):Future[Option[JsonNode]] {.async.} =
|
||||
var connI = getFreeConn(self).await
|
||||
defer:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
return
|
||||
|
||||
let rows = surreal_impl.query(self.pools.conns[connI].conn, queryString, self.placeHolder, self.pools.timeout).await
|
||||
if rows.len == 0:
|
||||
return none(JsonNode)
|
||||
else:
|
||||
return rows[0].some
|
||||
|
||||
|
||||
proc exec(self:SurrealQuery, queryString:string) {.async.} =
|
||||
## args is `JObject`
|
||||
var connI = getFreeConn(self).await
|
||||
defer:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
return
|
||||
|
||||
surreal_impl.exec(self.pools.conns[connI].conn, queryString, self.placeHolder, self.pools.timeout).await
|
||||
|
||||
|
||||
# proc insertId(self:SurrealQuery, queryString:string, key:string):Future[string] {.async.} =
|
||||
# var connI = self.transactionConn
|
||||
# if not self.isInTransaction:
|
||||
# connI = getFreeConn(self).await
|
||||
# defer:
|
||||
# if not self.isInTransaction:
|
||||
# self.returnConn(connI).await
|
||||
# if connI == errorConnectionNum:
|
||||
# return
|
||||
|
||||
# let table = self.query["table"].getStr
|
||||
# let columnGetQuery = &"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '{table}'"
|
||||
# let (columns, _) = surreal_impl.query(self.pools.conns[connI].conn, columnGetQuery, newJArray(), self.pools.timeout).await
|
||||
|
||||
# let (rows, _) = surreal_impl.execGetValue(self.pools.conns[connI].conn, queryString, self.placeHolder, columns, self.pools.timeout).await
|
||||
# return rows[0][0]
|
||||
|
||||
|
||||
proc getAllRows(self:RawSurrealQuery, queryString:string):Future[seq[JsonNode]] {.async.} =
|
||||
var connI = getFreeConn(self).await
|
||||
defer:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
return
|
||||
|
||||
let rows = surreal_impl.query(
|
||||
self.pools.conns[connI].conn,
|
||||
queryString,
|
||||
self.placeHolder,
|
||||
self.pools.timeout
|
||||
).await
|
||||
|
||||
if rows.len == 0:
|
||||
self.log.echoErrorMsg(queryString)
|
||||
return newSeq[JsonNode](0)
|
||||
return rows.getElems()
|
||||
|
||||
|
||||
# proc getAllRowsPlain(self:RawSurrealQuery, queryString:string, args:JsonNode):Future[seq[seq[string]]] {.async.} =
|
||||
# var connI = self.transactionConn
|
||||
# if not self.isInTransaction:
|
||||
# connI = getFreeConn(self).await
|
||||
# defer:
|
||||
# if not self.isInTransaction:
|
||||
# self.returnConn(connI).await
|
||||
# if connI == errorConnectionNum:
|
||||
# return
|
||||
|
||||
# let queryString = queryString.questionToDaller()
|
||||
|
||||
# let (rows, _) = surreal_impl.rawQuery(
|
||||
# self.pools.conns[connI].conn,
|
||||
# queryString,
|
||||
# self.placeHolder,
|
||||
# self.pools.timeout
|
||||
# ).await
|
||||
|
||||
# return rows
|
||||
|
||||
|
||||
proc getRow(self:RawSurrealQuery, queryString:string):Future[Option[JsonNode]] {.async.} =
|
||||
var connI = getFreeConn(self).await
|
||||
defer:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
return
|
||||
|
||||
let rows = surreal_impl.query(self.pools.conns[connI].conn, queryString, self.placeHolder, self.pools.timeout).await
|
||||
if rows.len == 0:
|
||||
return none(JsonNode)
|
||||
else:
|
||||
return rows[^1].some
|
||||
|
||||
|
||||
# proc getRowPlain(self:RawSurrealQuery, queryString:string, args:JsonNode):Future[seq[string]] {.async.} =
|
||||
# var connI = self.transactionConn
|
||||
# if not self.isInTransaction:
|
||||
# connI = getFreeConn(self).await
|
||||
# defer:
|
||||
# if not self.isInTransaction:
|
||||
# self.returnConn(connI).await
|
||||
# if connI == errorConnectionNum:
|
||||
# return
|
||||
|
||||
# let queryString = queryString.questionToDaller()
|
||||
|
||||
# let (rows, _) = surreal_impl.rawQuery(
|
||||
# self.pools.conns[connI].conn,
|
||||
# queryString,
|
||||
# self.placeHolder,
|
||||
# self.pools.timeout
|
||||
# ).await
|
||||
# return rows[0]
|
||||
|
||||
|
||||
proc exec(self:RawSurrealQuery, queryString:string) {.async.} =
|
||||
let connI = getFreeConn(self).await
|
||||
defer:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
return
|
||||
|
||||
surreal_impl.exec(
|
||||
self.pools.conns[connI].conn,
|
||||
queryString,
|
||||
self.placeHolder,
|
||||
self.pools.timeout
|
||||
).await
|
||||
|
||||
|
||||
proc info(self:RawSurrealQuery, queryString:string):Future[JsonNode] {.async.} =
|
||||
let connI = getFreeConn(self).await
|
||||
defer:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
return
|
||||
|
||||
return surreal_impl.info(
|
||||
self.pools.conns[connI].conn,
|
||||
queryString,
|
||||
self.placeHolder,
|
||||
self.pools.timeout
|
||||
).await
|
||||
|
||||
|
||||
proc column(self:SurrealQuery, queryString:string):Future[JsonNode] {.async.} =
|
||||
var connI = getFreeConn(self).await
|
||||
defer:
|
||||
self.returnConn(connI).await
|
||||
if connI == errorConnectionNum:
|
||||
return
|
||||
|
||||
var strArgs:seq[string]
|
||||
for arg in self.placeHolder.items:
|
||||
case arg["value"].kind
|
||||
of JBool:
|
||||
if arg["value"].getBool:
|
||||
strArgs.add("1")
|
||||
else:
|
||||
strArgs.add("0")
|
||||
of JInt:
|
||||
strArgs.add($arg["value"].getInt)
|
||||
of JFloat:
|
||||
strArgs.add($arg["value"].getFloat)
|
||||
of JString:
|
||||
strArgs.add($arg["value"].getStr)
|
||||
of JNull:
|
||||
strArgs.add("NULL")
|
||||
else:
|
||||
strArgs.add(arg["value"].pretty)
|
||||
|
||||
return surreal_impl.info(self.pools.conns[connI].conn, queryString, strArgs, self.pools.timeout).await
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# prepared statement cache
|
||||
# ================================================================================
|
||||
|
||||
proc close*(self: SurrealPreparedStatement) {.async.} =
|
||||
if self.isNil or self.isClosed:
|
||||
return
|
||||
self.isClosed = true
|
||||
if not self.entry.isNil and self.entry.refCount > 0:
|
||||
self.entry.refCount -= 1
|
||||
touchStmtEntry(self.entry)
|
||||
|
||||
|
||||
proc removePreparedEntry(self: SurrealConnections, sql: string) =
|
||||
if not self.pools.preparedCache.hasKey(sql):
|
||||
return
|
||||
self.pools.preparedCache.del(sql)
|
||||
|
||||
|
||||
proc flushStmt*(self: SurrealConnections, stmt: SurrealPreparedStatement) {.async.} =
|
||||
if stmt.isNil:
|
||||
return
|
||||
let sql = stmt.sql
|
||||
await stmt.close()
|
||||
self.removePreparedEntry(sql)
|
||||
|
||||
|
||||
proc clearStmtCache*(self: SurrealConnections) {.async.} =
|
||||
let keys = toSeq(self.pools.preparedCache.keys)
|
||||
for sql in keys:
|
||||
self.removePreparedEntry(sql)
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# public prepared exec
|
||||
# ================================================================================
|
||||
|
||||
proc get*(self: SurrealPreparedStatement, args: seq[string]): Future[seq[JsonNode]] {.async.} =
|
||||
try:
|
||||
self.owner.log.logger(self.sql)
|
||||
return await self.getPreparedRows(toPreparedArgsJson(args))
|
||||
except CatchableError:
|
||||
self.owner.log.echoErrorMsg(self.sql)
|
||||
self.owner.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc get*(self: SurrealPreparedStatement, ctx: SurrealPreparedContext, args: seq[string]): Future[seq[JsonNode]] {.async.} =
|
||||
try:
|
||||
self.owner.log.logger(self.sql)
|
||||
return await self.getPreparedRows(ctx, toPreparedArgsJson(args))
|
||||
except CatchableError:
|
||||
self.owner.log.echoErrorMsg(self.sql)
|
||||
self.owner.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc get*(self: SurrealPreparedStatement, args: JsonNode): Future[seq[JsonNode]] {.async.} =
|
||||
try:
|
||||
self.owner.log.logger(self.sql)
|
||||
return await self.getPreparedRows(args)
|
||||
except CatchableError:
|
||||
self.owner.log.echoErrorMsg(self.sql)
|
||||
self.owner.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc get*(self: SurrealPreparedStatement, ctx: SurrealPreparedContext, args: JsonNode): Future[seq[JsonNode]] {.async.} =
|
||||
try:
|
||||
self.owner.log.logger(self.sql)
|
||||
return await self.getPreparedRows(ctx, args)
|
||||
except CatchableError:
|
||||
self.owner.log.echoErrorMsg(self.sql)
|
||||
self.owner.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc first*(self: SurrealPreparedStatement, args: seq[string]): Future[Option[JsonNode]] {.async.} =
|
||||
try:
|
||||
self.owner.log.logger(self.sql)
|
||||
return await self.getPreparedRow(toPreparedArgsJson(args))
|
||||
except CatchableError:
|
||||
self.owner.log.echoErrorMsg(self.sql)
|
||||
self.owner.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc first*(self: SurrealPreparedStatement, ctx: SurrealPreparedContext, args: seq[string]): Future[Option[JsonNode]] {.async.} =
|
||||
try:
|
||||
self.owner.log.logger(self.sql)
|
||||
return await self.getPreparedRow(ctx, toPreparedArgsJson(args))
|
||||
except CatchableError:
|
||||
self.owner.log.echoErrorMsg(self.sql)
|
||||
self.owner.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc first*(self: SurrealPreparedStatement, args: JsonNode): Future[Option[JsonNode]] {.async.} =
|
||||
try:
|
||||
self.owner.log.logger(self.sql)
|
||||
return await self.getPreparedRow(args)
|
||||
except CatchableError:
|
||||
self.owner.log.echoErrorMsg(self.sql)
|
||||
self.owner.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc first*(self: SurrealPreparedStatement, ctx: SurrealPreparedContext, args: JsonNode): Future[Option[JsonNode]] {.async.} =
|
||||
try:
|
||||
self.owner.log.logger(self.sql)
|
||||
return await self.getPreparedRow(ctx, args)
|
||||
except CatchableError:
|
||||
self.owner.log.echoErrorMsg(self.sql)
|
||||
self.owner.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc exec*(self: SurrealPreparedStatement, args: seq[string]) {.async.} =
|
||||
try:
|
||||
self.owner.log.logger(self.sql)
|
||||
await self.execPrepared(toPreparedArgsJson(args))
|
||||
except CatchableError:
|
||||
self.owner.log.echoErrorMsg(self.sql)
|
||||
self.owner.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc exec*(self: SurrealPreparedStatement, ctx: SurrealPreparedContext, args: seq[string]) {.async.} =
|
||||
try:
|
||||
self.owner.log.logger(self.sql)
|
||||
await self.execPrepared(ctx, toPreparedArgsJson(args))
|
||||
except CatchableError:
|
||||
self.owner.log.echoErrorMsg(self.sql)
|
||||
self.owner.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc exec*(self: SurrealPreparedStatement, args: JsonNode) {.async.} =
|
||||
try:
|
||||
self.owner.log.logger(self.sql)
|
||||
await self.execPrepared(args)
|
||||
except CatchableError:
|
||||
self.owner.log.echoErrorMsg(self.sql)
|
||||
self.owner.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
proc exec*(self: SurrealPreparedStatement, ctx: SurrealPreparedContext, args: JsonNode) {.async.} =
|
||||
try:
|
||||
self.owner.log.logger(self.sql)
|
||||
await self.execPrepared(ctx, args)
|
||||
except CatchableError:
|
||||
self.owner.log.echoErrorMsg(self.sql)
|
||||
self.owner.log.echoErrorMsg(getCurrentExceptionMsg())
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
# proc transactionStart(self:SurrealConnections) {.async.} =
|
||||
# let connI = getFreeConn(self).await
|
||||
# if connI == errorConnectionNum:
|
||||
# return
|
||||
# self.isInTransaction = true
|
||||
# self.transactionConn = connI
|
||||
|
||||
# surreal_impl.exec(self.pools.conns[connI].conn, "BEGIN", newJArray(), newSeq[Row](), self.pools.timeout).await
|
||||
|
||||
|
||||
# proc transactionEnd(self:SurrealConnections, query:string) {.async.} =
|
||||
# defer:
|
||||
# self.returnConn(self.transactionConn).await
|
||||
# self.transactionConn = 0
|
||||
# self.isInTransaction = false
|
||||
|
||||
# surreal_impl.exec(self.pools[self.transactionConn].conn, query, newJArray(), newSeq[Row](), self.pools.timeout).await
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# public exec
|
||||
# ================================================================================
|
||||
|
||||
proc get*(self: SurrealQuery):Future[seq[JsonNode]] {.async.} =
|
||||
## https://surrealdb.com/docs/surrealql/statements/select
|
||||
let sql = self.selectBuilder()
|
||||
try:
|
||||
self.log.logger(sql)
|
||||
return self.getAllRows(sql).await
|
||||
except CatchableError:
|
||||
self.log.echoErrorMsg(sql)
|
||||
self.log.echoErrorMsg( getCurrentExceptionMsg() )
|
||||
return newSeq[JsonNode]()
|
||||
|
||||
|
||||
proc first*(self: SurrealQuery):Future[Option[JsonNode]] {.async.} =
|
||||
var sql = self.selectFirstBuilder()
|
||||
try:
|
||||
self.log.logger(sql)
|
||||
return self.getRow(sql).await
|
||||
except CatchableError:
|
||||
self.log.echoErrorMsg(sql)
|
||||
self.log.echoErrorMsg( getCurrentExceptionMsg() )
|
||||
return none(JsonNode)
|
||||
|
||||
|
||||
proc find*(self: SurrealQuery, id:SurrealId, key="id"):Future[Option[JsonNode]] {.async.} =
|
||||
var sql = self.selectFindBuilder(id, key)
|
||||
sql = questionToDaller(sql)
|
||||
try:
|
||||
self.log.logger(sql)
|
||||
return self.getRow(sql).await
|
||||
except CatchableError:
|
||||
self.log.echoErrorMsg(sql)
|
||||
self.log.echoErrorMsg( getCurrentExceptionMsg() )
|
||||
raise getCurrentException()
|
||||
|
||||
|
||||
# ==================== insert JsonNode ====================
|
||||
proc insert*(self:SurrealQuery, items:JsonNode) {.async.} =
|
||||
## https://surrealdb.com/docs/surrealql/statements/insert
|
||||
let sql = self.insertValueBuilder(items)
|
||||
self.log.logger(sql)
|
||||
self.exec(sql).await
|
||||
|
||||
|
||||
proc insert*(self:SurrealQuery, items:seq[JsonNode]) {.async.} =
|
||||
## https://surrealdb.com/docs/surrealql/statements/insert
|
||||
var sql = self.insertValuesBuilder(items)
|
||||
self.log.logger(sql)
|
||||
self.exec(sql).await
|
||||
|
||||
|
||||
proc insertId*(self:SurrealQuery, items:JsonNode, key="id"):Future[SurrealId] {.async.} =
|
||||
## https://surrealdb.com/docs/surrealql/statements/insert
|
||||
var sql = self.insertValueBuilder(items) & " RETURN AFTER"
|
||||
self.log.logger(sql)
|
||||
let res = self.getRow(sql).await
|
||||
return SurrealId.new(res.get()[key].getStr)
|
||||
|
||||
|
||||
proc insertId*(self: SurrealQuery, items: seq[JsonNode], key="id"):Future[seq[SurrealId]] {.async.} =
|
||||
result = newSeq[SurrealId](items.len)
|
||||
var sql = self.insertValuesBuilder(items) & " RETURN AFTER"
|
||||
self.log.logger(sql)
|
||||
let res = self.getAllRows(sql).await
|
||||
var i = 0
|
||||
for row in res.items:
|
||||
defer: i.inc()
|
||||
result[i] = SurrealId.new(row[key].getStr)
|
||||
|
||||
|
||||
# ==================== insert Object ====================
|
||||
proc insert*[T](self:SurrealQuery, items:T) {.async.} =
|
||||
## https://surrealdb.com/docs/surrealql/statements/insert
|
||||
let sql = self.insertValueBuilder(%items)
|
||||
self.log.logger(sql)
|
||||
self.exec(sql).await
|
||||
|
||||
|
||||
proc insert*[T](self:SurrealQuery, items:seq[T]) {.async.} =
|
||||
## https://surrealdb.com/docs/surrealql/statements/insert
|
||||
var jsonItems = newSeq[JsonNode](items.len)
|
||||
for i, item in items:
|
||||
jsonItems[i] = %item
|
||||
var sql = self.insertValuesBuilder(jsonItems)
|
||||
self.log.logger(sql)
|
||||
self.exec(sql).await
|
||||
|
||||
|
||||
proc insertId*[T](self:SurrealQuery, items:T, key="id"):Future[SurrealId] {.async.} =
|
||||
## https://surrealdb.com/docs/surrealql/statements/insert
|
||||
var sql = self.insertValueBuilder(%items) & " RETURN AFTER"
|
||||
self.log.logger(sql)
|
||||
let res = self.getRow(sql).await
|
||||
return SurrealId.new(res.get()[key].getStr)
|
||||
|
||||
|
||||
proc insertId*[T](self: SurrealQuery, items: seq[T], key="id"):Future[seq[SurrealId]] {.async.} =
|
||||
result = newSeq[SurrealId](items.len)
|
||||
var jsonItems = newSeq[JsonNode](items.len)
|
||||
for i, item in items:
|
||||
jsonItems[i] = %item
|
||||
var sql = self.insertValuesBuilder(jsonItems) & " RETURN AFTER"
|
||||
self.log.logger(sql)
|
||||
let res = self.getAllRows(sql).await
|
||||
var i = 0
|
||||
for row in res.items:
|
||||
defer: i.inc()
|
||||
result[i] = SurrealId.new(row[key].getStr)
|
||||
|
||||
|
||||
proc update*(self: SurrealQuery, items: JsonNode){.async.} =
|
||||
## https://surrealdb.com/docs/surrealql/statements/update
|
||||
var sql = self.updateBuilder(items)
|
||||
self.log.logger(sql)
|
||||
self.exec(sql).await
|
||||
|
||||
|
||||
proc update*(self:SurrealConnections, id:SurrealId, items:JsonNode) {.async.} =
|
||||
## https://surrealdb.com/docs/surrealql/statements/update
|
||||
let surrealQuery = SurrealQuery.new(
|
||||
self.log,
|
||||
self.pools,
|
||||
newJObject()
|
||||
)
|
||||
let sql = surrealQuery.updateMergeBuilder(id.rawid, items)
|
||||
surrealQuery.log.logger(sql)
|
||||
surrealQuery.exec(sql).await
|
||||
|
||||
|
||||
proc update*[T](self: SurrealQuery, items: T){.async.} =
|
||||
## https://surrealdb.com/docs/surrealql/statements/update
|
||||
var sql = self.updateBuilder(%items)
|
||||
self.log.logger(sql)
|
||||
self.exec(sql).await
|
||||
|
||||
|
||||
proc update*[T](self:SurrealConnections, id:SurrealId, items:T) {.async.} =
|
||||
## https://surrealdb.com/docs/surrealql/statements/update
|
||||
let surrealQuery = SurrealQuery.new(
|
||||
self.log,
|
||||
self.pools,
|
||||
newJObject()
|
||||
)
|
||||
let sql = surrealQuery.updateMergeBuilder(id.rawid, %items)
|
||||
surrealQuery.log.logger(sql)
|
||||
surrealQuery.exec(sql).await
|
||||
|
||||
|
||||
proc delete*(self: SurrealQuery){.async.} =
|
||||
## https://surrealdb.com/docs/surrealql/statements/delete
|
||||
let sql = self.deleteBuilder()
|
||||
self.log.logger(sql)
|
||||
self.exec(sql).await
|
||||
|
||||
|
||||
proc delete*(self: SurrealQuery, id: SurrealId){.async.} =
|
||||
## https://surrealdb.com/docs/surrealql/statements/delete
|
||||
let sql = self.deleteByIdBuilder(id.rawId)
|
||||
self.log.logger(sql)
|
||||
self.exec(sql).await
|
||||
|
||||
|
||||
proc columns*(self: SurrealQuery):Future[seq[string]] {.async.} =
|
||||
let tableName = self.query["table"].getStr
|
||||
let sql = &"INFO FOR TABLE `{tableName}`"
|
||||
try:
|
||||
self.log.logger(sql)
|
||||
let resp = self.column(sql).await
|
||||
var columns:seq[string]
|
||||
for (key, value) in resp[0]["result"]["fields"].pairs:
|
||||
columns.add(key)
|
||||
return columns
|
||||
except CatchableError:
|
||||
self.log.echoErrorMsg(sql)
|
||||
self.log.echoErrorMsg( getCurrentExceptionMsg() )
|
||||
return @[]
|
||||
|
||||
|
||||
proc count*(self:SurrealQuery):Future[int] {.async.} =
|
||||
let sql = self.countBuilder()
|
||||
self.log.logger(sql)
|
||||
let response = self.getRow(sql).await
|
||||
if response.isSome:
|
||||
return response.get["total"].getInt()
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
proc min*(self:SurrealQuery, column:string, collaction:Collation=None):Future[string] {.async.} =
|
||||
## = `ORDER BY {column} {collaction} ASC LIMIT 1`
|
||||
let self = self.orderBy(column, collaction, Asc).limit(1)
|
||||
let sql = self.selectFirstBuilder()
|
||||
self.log.logger(sql)
|
||||
let response = self.getRow(sql).await
|
||||
if response.isSome():
|
||||
let column = if column.contains("."): column.split(".")[^1] else: column
|
||||
let value = response.get()[column]
|
||||
case value.kind
|
||||
of JString:
|
||||
return $value.getStr()
|
||||
else:
|
||||
return $value
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
proc max*(self:SurrealQuery, column:string, collaction:Collation=None):Future[string]{.async.} =
|
||||
## = `ORDER BY {column} {collaction} DESC LIMIT 1`
|
||||
let self = self.orderBy(column, collaction, Desc).limit(1)
|
||||
let sql = self.selectFirstBuilder()
|
||||
self.log.logger(sql)
|
||||
let response = self.getRow(sql).await
|
||||
if response.isSome():
|
||||
let column = if column.contains("."): column.split(".")[^1] else: column
|
||||
let value = response.get()[column]
|
||||
case value.kind
|
||||
of JString:
|
||||
return $value.getStr()
|
||||
else:
|
||||
return $value
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
proc avg*(self:SurrealQuery, column:string):Future[float]{.async.} =
|
||||
var sql = self.selectAvgBuilder(column)
|
||||
self.log.logger(sql)
|
||||
let response = await self.getRow(sql)
|
||||
if response.isSome:
|
||||
let value = response.get["avg"]
|
||||
case value.kind
|
||||
of JInt:
|
||||
return value.getInt.float
|
||||
of JFloat:
|
||||
return value.getFloat()
|
||||
of JString:
|
||||
return value.getStr().parseFloat()
|
||||
else:
|
||||
return 0.0
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
|
||||
proc sum*(self:SurrealQuery, column:string):Future[float]{.async.} =
|
||||
var sql = self.selectSumBuilder(column)
|
||||
self.log.logger(sql)
|
||||
let response = await self.getRow(sql)
|
||||
if response.isSome:
|
||||
return response.get["sum"].getFloat()
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
|
||||
proc get*(self: RawSurrealQuery):Future[seq[JsonNode]] {.async.} =
|
||||
## It is only used with raw()
|
||||
self.log.logger(self.queryString)
|
||||
return self.getAllRows(self.queryString).await
|
||||
|
||||
|
||||
proc exec*(self: RawSurrealQuery) {.async.} =
|
||||
## It is only used with raw()
|
||||
##
|
||||
## https://surrealdb.com/docs/integration/http#sql
|
||||
##
|
||||
## https://surrealdb.com/docs/surrealql
|
||||
try:
|
||||
self.log.logger(self.queryString)
|
||||
self.exec(self.queryString).await
|
||||
except CatchableError:
|
||||
self.log.echoErrorMsg(self.queryString)
|
||||
self.log.echoErrorMsg( getCurrentExceptionMsg() )
|
||||
|
||||
|
||||
proc info*(self: RawSurrealQuery):Future[JsonNode] {.async.} =
|
||||
## Get all response.
|
||||
##
|
||||
## https://surrealdb.com/docs/integration/http#sql
|
||||
##
|
||||
## https://surrealdb.com/docs/surrealql
|
||||
try:
|
||||
self.log.logger(self.queryString)
|
||||
return self.info(self.queryString).await
|
||||
except CatchableError:
|
||||
self.log.echoErrorMsg(self.queryString)
|
||||
self.log.echoErrorMsg( getCurrentExceptionMsg() )
|
||||
|
||||
|
||||
proc first*(self: RawSurrealQuery):Future[Option[JsonNode]] {.async.} =
|
||||
## It is only used with raw()
|
||||
self.log.logger(self.queryString)
|
||||
return self.getRow(self.queryString).await
|
||||
|
||||
|
||||
template seeder*(rdb:SurrealConnections, tableName:string, body:untyped):untyped =
|
||||
## The `seeder` block allows the code in the block to work only when the table is empty.
|
||||
block:
|
||||
if rdb.table(tableName).count().waitFor == 0:
|
||||
body
|
||||
|
||||
|
||||
template seeder*(rdb:SurrealConnections, tableName, column:string, body:untyped):untyped =
|
||||
## The `seeder` block allows the code in the block to work only when the table or specified column is empty.
|
||||
block:
|
||||
if rdb.table(tableName).select(column).count().waitFor == 0:
|
||||
body
|
||||
@@ -0,0 +1,85 @@
|
||||
import std/asyncdispatch
|
||||
import std/deques
|
||||
import std/httpclient
|
||||
import std/httpcore
|
||||
import std/strformat
|
||||
import std/base64
|
||||
import std/tables
|
||||
import std/times
|
||||
import ../../libs/surreal/surreal_rdb
|
||||
import ../../error
|
||||
import ../../log
|
||||
import ./surreal_types
|
||||
|
||||
|
||||
proc surrealTimeoutMs(timeout: int): int =
|
||||
if timeout <= 0:
|
||||
return 0
|
||||
if timeout > high(int) div 1000:
|
||||
return high(int)
|
||||
result = timeout * 1000
|
||||
|
||||
|
||||
proc awaitWithTimeout[T](fut: Future[T], timeoutMs: int, timeoutMsg: string): Future[T] {.async.} =
|
||||
if timeoutMs <= 0:
|
||||
return await fut
|
||||
|
||||
let ok = await withTimeout(fut, timeoutMs)
|
||||
if not ok:
|
||||
dbError(timeoutMsg)
|
||||
result = fut.read
|
||||
|
||||
|
||||
proc buildSurrealHeaders(namespace, database, authHeader: string): HttpHeaders =
|
||||
result = newHttpHeaders(true)
|
||||
result["Surreal-NS"] = namespace
|
||||
result["Surreal-DB"] = database
|
||||
result["Accept"] = "application/json"
|
||||
result["Authorization"] = authHeader
|
||||
|
||||
|
||||
proc dbOpen*(_:type SurrealDB, namespace:string = "", database: string = "", user: string = "", password: string = "",
|
||||
host: string = "", port: int = 0, maxConnections: int = 1, timeout=30,
|
||||
shouldDisplayLog=false, shouldOutputLogFile=false, logDir=""): Future[SurrealConnections] {.async.} =
|
||||
let timeoutMs = surrealTimeoutMs(timeout)
|
||||
let statusUrl = &"{host}:{port}/status"
|
||||
let sqlUrl = &"{host}:{port}/sql"
|
||||
let bootstrapSql = &"USE NS `{namespace}` DB `{database}`"
|
||||
let authHeader = "Basic " & base64.encode(user & ":" & password)
|
||||
var conns = newSeq[Connection](maxConnections)
|
||||
for i in 0..<maxConnections:
|
||||
let client = newAsyncHttpClient()
|
||||
client.headers = buildSurrealHeaders(namespace, database, authHeader)
|
||||
|
||||
let statusResp = await awaitWithTimeout(client.get(statusUrl), timeoutMs,
|
||||
&"Cannot connect to SurrealDb {host}:{port} (status request timed out)")
|
||||
if statusResp.status != $Http200:
|
||||
dbError(&"Cannot connect to SurrealDb {host}:{port}")
|
||||
|
||||
if i == 0:
|
||||
let bootstrapResp = await awaitWithTimeout(client.post(sqlUrl, bootstrapSql), timeoutMs,
|
||||
&"Cannot connect to SurrealDb {host}:{port} (bootstrap request timed out)")
|
||||
if bootstrapResp.status != $Http200:
|
||||
dbError(&"Cannot connect to SurrealDb {host}:{port}")
|
||||
|
||||
let conn = SurrealConn(
|
||||
client: client,
|
||||
host: host,
|
||||
port: port.int32
|
||||
)
|
||||
|
||||
conns[i] = Connection(
|
||||
conn: conn,
|
||||
isBusy: false,
|
||||
createdAt: getTime().toUnix(),
|
||||
)
|
||||
let pools = Connections(
|
||||
conns: conns,
|
||||
timeout: timeout,
|
||||
waiters: initDeque[Future[void]](),
|
||||
preparedCache: initTable[string, SurrealPreparedEntry](),
|
||||
)
|
||||
return SurrealConnections(
|
||||
pools: pools,
|
||||
log: LogSetting(shouldDisplayLog:shouldDisplayLog, shouldOutputLogFile:shouldOutputLogFile, logDir:logDir)
|
||||
)
|
||||
+452
@@ -0,0 +1,452 @@
|
||||
import std/asyncdispatch
|
||||
import std/json
|
||||
import std/options
|
||||
import std/strutils
|
||||
import std/times
|
||||
import ../../libs/surreal/surreal_impl
|
||||
import ../../enums
|
||||
import ./surreal_types
|
||||
|
||||
|
||||
proc select*(self:SurrealConnections, columnsArg:varargs[string]):SurrealQuery =
|
||||
let query = newJObject()
|
||||
|
||||
if columnsArg.len == 0:
|
||||
query["select"] = %["*"]
|
||||
else:
|
||||
query["select"] = %columnsArg
|
||||
|
||||
let surrealQuery = SurrealQuery.new(
|
||||
self.log,
|
||||
self.pools,
|
||||
query
|
||||
)
|
||||
return surrealQuery
|
||||
|
||||
|
||||
proc table*(self:SurrealConnections, tableArg: string): SurrealQuery =
|
||||
let query = newJObject()
|
||||
query["table"] = %tableArg
|
||||
|
||||
let surrealQuery = SurrealQuery.new(
|
||||
self.log,
|
||||
self.pools,
|
||||
query,
|
||||
)
|
||||
return surrealQuery
|
||||
|
||||
|
||||
proc table*(self:SurrealQuery, tableArg: string): SurrealQuery =
|
||||
self.query["table"] = %tableArg
|
||||
return self
|
||||
|
||||
|
||||
proc `distinct`*(self: SurrealQuery): SurrealQuery =
|
||||
self.query["distinct"] = %true
|
||||
return self
|
||||
|
||||
# # ============================== Conditions ==============================
|
||||
|
||||
# proc join*(self: SurrealQuery, table: string, column1: string, symbol: string,
|
||||
# column2: string): SurrealQuery =
|
||||
# if self.query.hasKey("join") == false:
|
||||
# self.query["join"] = %*[{
|
||||
# "table": table,
|
||||
# "column1": column1,
|
||||
# "symbol": symbol,
|
||||
# "column2": column2
|
||||
# }]
|
||||
# else:
|
||||
# self.query["join"].add(%*{
|
||||
# "table": table,
|
||||
# "column1": column1,
|
||||
# "symbol": symbol,
|
||||
# "column2": column2
|
||||
# })
|
||||
# return self
|
||||
|
||||
|
||||
# proc leftJoin*(self: SurrealQuery, table: string, column1: string, symbol: string,
|
||||
# column2: string): SurrealQuery =
|
||||
# if self.query.hasKey("left_join") == false:
|
||||
# self.query["left_join"] = %*[{
|
||||
# "table": table,
|
||||
# "column1": column1,
|
||||
# "symbol": symbol,
|
||||
# "column2": column2
|
||||
# }]
|
||||
# else:
|
||||
# self.query["left_join"].add(%*{
|
||||
# "table": table,
|
||||
# "column1": column1,
|
||||
# "symbol": symbol,
|
||||
# "column2": column2
|
||||
# })
|
||||
# return self
|
||||
|
||||
|
||||
## https://surrealdb.com/docs/surrealql/operators
|
||||
const whereSymbols = ["is", "is not", "=", "!=", "<", "<=", ">=", ">", "<>", "CONTAINS", "CONTAINSNOT", "@@"]
|
||||
const whereSymbolsError = """Arg position 3 is only allowed of ["is", "is not", "=", "!=", "<", "<=", ">=", ">", "<>", "CONTAINS", "CONTAINSNOT", "@@"]"""
|
||||
|
||||
proc where*(self: SurrealQuery, column: string, symbol: string,
|
||||
value: bool|int|float|string|SurrealId): SurrealQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("where") == false:
|
||||
self.query["where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": value
|
||||
}]
|
||||
else:
|
||||
self.query["where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": value
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc where*(self: SurrealQuery, column: string, symbol: string, value: nil.type): SurrealQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("where_null") == false:
|
||||
self.query["where_null"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": value
|
||||
}]
|
||||
else:
|
||||
self.query["where_null"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": value
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc orWhere*(self: SurrealQuery, column: string, symbol: string,
|
||||
value: bool|int|float|string|SurrealId): SurrealQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("or_where") == false:
|
||||
self.query["or_where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": value
|
||||
}]
|
||||
else:
|
||||
self.query["or_where"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": value
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc orWhere*(self: SurrealQuery, column: string, symbol: string, value: nil.type): SurrealQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("or_where") == false:
|
||||
self.query["or_where"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": nil
|
||||
}]
|
||||
else:
|
||||
self.query["or_where"].add(%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": nil
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereBetween*(self:SurrealQuery, column:string, width:array[2, int|float]): SurrealQuery =
|
||||
## `rdb.table("user").whereBetween("index", [1, 3]).get()`
|
||||
##
|
||||
## `SELECT * FROM user WHERE 1 <= index AND index <= 3`
|
||||
##
|
||||
## https://surrealdb.com/docs/surrealql/operators#lessthanorequal
|
||||
if self.query.hasKey("where_between") == false:
|
||||
self.query["where_between"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_between"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
# proc whereBetween*(self:SurrealQuery, column:string, width:array[2, string]): SurrealQuery =
|
||||
# ## rdb.table("user").whereBetween("name", ["user1", "user3"]).get()
|
||||
# ##
|
||||
# ## `SELECT * FROM user WHERE "user1" <= index <= "user3`
|
||||
# ##
|
||||
# ## https://surrealdb.com/docs/surrealql/operators#lessthanorequal
|
||||
# if self.query.hasKey("where_between_string") == false:
|
||||
# self.query["where_between_string"] = %*[{
|
||||
# "column": column,
|
||||
# "width": width
|
||||
# }]
|
||||
# else:
|
||||
# self.query["where_between_string"].add(%*{
|
||||
# "column": column,
|
||||
# "width": width
|
||||
# })
|
||||
# return self
|
||||
|
||||
|
||||
proc whereNotBetween*(self:SurrealQuery, column:string, width:array[2, int|float]): SurrealQuery =
|
||||
## `rdb.table("user").whereNotBetween("index", [1, 3]).get()`
|
||||
##
|
||||
## `SELECT * FROM user WHERE index > 1 AND 3 < index`
|
||||
##
|
||||
## https://surrealdb.com/docs/surrealql/operators#greaterthan
|
||||
if self.query.hasKey("where_not_between") == false:
|
||||
self.query["where_not_between"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_between"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
# proc whereNotBetween*(self:SurrealQuery, column:string, width:array[2, string]): SurrealQuery =
|
||||
# self.placeHolder.add(%*{"key": column, "value": width[0]})
|
||||
# self.placeHolder.add(%*{"key": column, "value": width[1]})
|
||||
|
||||
# if self.query.hasKey("where_not_between_string") == false:
|
||||
# self.query["where_not_between_string"] = %*[{
|
||||
# "column": column,
|
||||
# "width": width
|
||||
# }]
|
||||
# else:
|
||||
# self.query["where_not_between_string"].add(%*{
|
||||
# "column": column,
|
||||
# "width": width
|
||||
# })
|
||||
# return self
|
||||
|
||||
|
||||
proc whereIn*(self:SurrealQuery, column:string, width:seq[int|float|string]): SurrealQuery =
|
||||
## `rdb.table("user").whereIn("index", [2, 3]).get()`
|
||||
##
|
||||
## `SELECT * FROM user WHERE [2, 3] CONTAINS index`
|
||||
##
|
||||
## https://surrealdb.com/docs/surrealql/operators#contains
|
||||
if self.query.hasKey("where_in") == false:
|
||||
self.query["where_in"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_in"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNotIn*(self:SurrealQuery, column:string, width:seq[int|float|string]): SurrealQuery =
|
||||
## `rdb.table("user").whereNotIn("index", [2, 3]).get()`
|
||||
##
|
||||
## `SELECT * FROM user WHERE [2, 3] CONTAINSNOT index`
|
||||
##
|
||||
## https://surrealdb.com/docs/surrealql/operators#contains-not
|
||||
if self.query.hasKey("where_not_in") == false:
|
||||
self.query["where_not_in"] = %*[{
|
||||
"column": column,
|
||||
"width": width
|
||||
}]
|
||||
else:
|
||||
self.query["where_not_in"].add(%*{
|
||||
"column": column,
|
||||
"width": width
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc whereNull*(self:SurrealQuery, column:string): SurrealQuery =
|
||||
## https://surrealdb.com/docs/surrealql/operators#equal
|
||||
if self.query.hasKey("where_null") == false:
|
||||
self.query["where_null"] = %*[{
|
||||
"column": column,
|
||||
"symbol": "is",
|
||||
}]
|
||||
else:
|
||||
self.query["where_null"].add(%*{
|
||||
"column": column,
|
||||
"symbol": "is",
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc groupBy*(self:SurrealQuery, column:string): SurrealQuery =
|
||||
if self.query.hasKey("group_by") == false:
|
||||
self.query["group_by"] = %*[{"column": column}]
|
||||
else:
|
||||
self.query["group_by"].add(%*{"column": column})
|
||||
return self
|
||||
|
||||
|
||||
proc having*(self: SurrealQuery, column: string, symbol: string,
|
||||
value: bool|int|float|string): SurrealQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("having") == false:
|
||||
self.query["having"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": value
|
||||
}]
|
||||
else:
|
||||
self.query["having"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": value
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc having*(self: SurrealQuery, column: string, symbol: string, value: nil.type): SurrealQuery =
|
||||
if not whereSymbols.contains(symbol):
|
||||
raise newException(
|
||||
CatchableError,
|
||||
whereSymbolsError
|
||||
)
|
||||
|
||||
if self.query.hasKey("having") == false:
|
||||
self.query["having"] = %*[{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": nil
|
||||
}]
|
||||
else:
|
||||
self.query["having"].add(
|
||||
%*{
|
||||
"column": column,
|
||||
"symbol": symbol,
|
||||
"value": nil
|
||||
}
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
proc fetch*(self:SurrealQuery, columnsArg: varargs[string]):SurrealQuery =
|
||||
self.query["fetch"] = %columnsArg
|
||||
return self
|
||||
|
||||
|
||||
proc orderBy*(self:SurrealQuery, column:string, order:Order): SurrealQuery =
|
||||
if self.query.hasKey("order_by") == false:
|
||||
self.query["order_by"] = %*[{
|
||||
"column": column,
|
||||
"collation": "",
|
||||
"order": $order
|
||||
}]
|
||||
else:
|
||||
self.query["order_by"].add(%*{
|
||||
"column": column,
|
||||
"collation": "",
|
||||
"order": $order
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc orderBy*(self:SurrealQuery, column:string, collation:Collation, order:Order): SurrealQuery =
|
||||
if self.query.hasKey("order_by") == false:
|
||||
self.query["order_by"] = %*[{
|
||||
"column": column,
|
||||
"collation": $collation,
|
||||
"order": $order
|
||||
}]
|
||||
else:
|
||||
self.query["order_by"].add(%*{
|
||||
"column": column,
|
||||
"collation": $collation,
|
||||
"order": $order
|
||||
})
|
||||
return self
|
||||
|
||||
|
||||
proc limit*(self: SurrealQuery, num: int): SurrealQuery =
|
||||
self.query["limit"] = %num
|
||||
return self
|
||||
|
||||
|
||||
proc start*(self: SurrealQuery, num: int): SurrealQuery =
|
||||
self.query["start"] = %num
|
||||
return self
|
||||
|
||||
|
||||
proc parallel*(self: SurrealQuery): SurrealQuery =
|
||||
self.query["parallel"] = %true
|
||||
return self
|
||||
|
||||
|
||||
|
||||
# proc offset*(self: SurrealQuery, num: int): SurrealQuery =
|
||||
# self.query["offset"] = %num
|
||||
# return self
|
||||
|
||||
|
||||
# proc inTransaction*(self:SurrealQuery, connI:int) =
|
||||
# ## Only used in transation block
|
||||
# self.isInTransaction = true
|
||||
# self.transactionConn = connI
|
||||
|
||||
|
||||
# proc freeTransactionConn*(self:SurrealQuery, connI:int) =
|
||||
# ## Only used in transation block
|
||||
# self.isInTransaction = false
|
||||
|
||||
|
||||
proc raw*(self:SurrealConnections, sql:string, arges=newJArray()): RawSurrealQuery =
|
||||
## arges is `JArray`
|
||||
##
|
||||
## can't use BLOB data.
|
||||
let rawQueryRdb = RawSurrealQuery(
|
||||
log: self.log,
|
||||
pools: self.pools,
|
||||
query: newJObject(),
|
||||
queryString: sql.strip(),
|
||||
placeHolder: arges,
|
||||
)
|
||||
return rawQueryRdb
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
import std/asyncdispatch
|
||||
import std/deques
|
||||
import std/json
|
||||
import std/httpclient
|
||||
import std/tables
|
||||
import ../../log
|
||||
import ../../libs/surreal/surreal_rdb
|
||||
import ../../error
|
||||
|
||||
|
||||
type SurrealDB* = object
|
||||
|
||||
|
||||
type Connection* = object
|
||||
conn*:SurrealConn
|
||||
isBusy*:bool
|
||||
createdAt*:int64
|
||||
|
||||
|
||||
type SurrealPreparedEntry* = ref object
|
||||
sql*: string
|
||||
normalizedSql*: string
|
||||
nArgs*: int
|
||||
refCount*: int
|
||||
lastUsedAt*: int64
|
||||
|
||||
|
||||
type Connections* = ref object
|
||||
conns*: seq[Connection]
|
||||
timeout*: int
|
||||
## `getFreeConn` が接続を待つときに積む Future。`returnConn` が先頭から 1 件だけ完了させる。
|
||||
waiters*: Deque[Future[void]]
|
||||
preparedCache*: Table[string, SurrealPreparedEntry]
|
||||
|
||||
|
||||
## created by `let rdb = dbOpen(SurrealDB, "ns", "database", "user", "pass", "http://surreal", 8000)`
|
||||
type SurrealConnections* = ref object
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
|
||||
|
||||
type SurrealPreparedContext* = ref object
|
||||
owner*: SurrealConnections
|
||||
connI*: int
|
||||
|
||||
|
||||
type SurrealPreparedStatement* = ref object
|
||||
owner*: SurrealConnections
|
||||
entry*: SurrealPreparedEntry
|
||||
sql*: string
|
||||
nArgs*: int
|
||||
isClosed*: bool
|
||||
|
||||
|
||||
type SurrealQuery* = ref object
|
||||
## created by `rdb.select("columnName")` or `rdb.table("tableName")`
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
query*: JsonNode
|
||||
queryString*: string
|
||||
# placeHolder*: JsonNode # JArray [{"key":"user", "value":"user1"}]
|
||||
placeHolder*: JsonNode # JArray [true, 1, 1.1, "str"]
|
||||
|
||||
|
||||
proc new*(_:type SurrealQuery, log:LogSetting, pools:Connections, query:JsonNode):SurrealQuery =
|
||||
return SurrealQuery(
|
||||
log:log,
|
||||
pools:pools,
|
||||
query:query,
|
||||
queryString:"",
|
||||
placeHolder: newJArray()
|
||||
)
|
||||
|
||||
|
||||
type RawSurrealQuery* = ref object
|
||||
log*: LogSetting
|
||||
pools*:Connections
|
||||
query*: JsonNode
|
||||
queryString*: string
|
||||
placeHolder*: JsonNode # JArray ["user1", "user1@example.com"]
|
||||
|
||||
|
||||
proc `$`*(self:SurrealConnections|SurrealQuery|RawSurrealQuery):string =
|
||||
return "SurrealDB"
|
||||
|
||||
|
||||
type SurrealId* = object
|
||||
table*:string
|
||||
id:string
|
||||
|
||||
|
||||
proc new*(_:type SurrealId):SurrealId =
|
||||
## create empty surreal id
|
||||
return SurrealId(table:"", id:"")
|
||||
|
||||
|
||||
proc new*(_:type SurrealId, table, id:string):SurrealId =
|
||||
## .. code-block:: Nim
|
||||
## let rawId = "user:z7cr4mz474h4ab7rcd6d"
|
||||
## let table = "user"
|
||||
## let id = "z7cr4mz474h4ab7rcd6d"
|
||||
## let surrealId = SureealId.new(table, id)
|
||||
if table.len == 0:
|
||||
dbError("table cannot be empty")
|
||||
if id.len == 0:
|
||||
dbError("id cannot be empty")
|
||||
return SurrealId(table:table, id:id)
|
||||
|
||||
|
||||
proc new*(_:type SurrealId, rawId:string):SurrealId =
|
||||
## .. code-block:: Nim
|
||||
## let rawId = "user:z7cr4mz474h4ab7rcd6d"
|
||||
## let surrealId = SureealId.new(rawId)
|
||||
if rawId.len == 0:
|
||||
dbError("rawId cannot be empty")
|
||||
let split = rawId.split(":")
|
||||
let table = split[0]
|
||||
let id = split[1]
|
||||
return SurrealId(table:table, id:id)
|
||||
|
||||
|
||||
proc rawId*(self:SurrealId):string =
|
||||
## .. code-block:: Nim
|
||||
## let rawId = "user:z7cr4mz474h4ab7rcd6d"
|
||||
## let surrealId = SureealId.new(rawId)
|
||||
## surrealId.rawId() == "user:z7cr4mz474h4ab7rcd6d"
|
||||
return self.table & ":" & self.id
|
||||
|
||||
|
||||
proc `$`*(self:SurrealId):string =
|
||||
return self.id
|
||||
|
||||
|
||||
proc `%`*(self:SurrealId):JsonNode =
|
||||
return %(self.rawId())
|
||||
@@ -0,0 +1,115 @@
|
||||
import std/json
|
||||
import std/options
|
||||
import std/macros
|
||||
|
||||
|
||||
type PreparedParam* = object
|
||||
value*: string
|
||||
isNull*: bool
|
||||
|
||||
|
||||
proc nullPreparedParam*(): PreparedParam =
|
||||
result.isNull = true
|
||||
|
||||
|
||||
proc toPreparedParam*(v: string): PreparedParam =
|
||||
result.value = v
|
||||
|
||||
|
||||
proc toPreparedParam*(v: cstring): PreparedParam =
|
||||
if v.isNil:
|
||||
return nullPreparedParam()
|
||||
result.value = $v
|
||||
|
||||
|
||||
proc toPreparedParam*(v: bool): PreparedParam =
|
||||
result.value = if v: "1" else: "0"
|
||||
|
||||
|
||||
proc toPreparedParam*[T: SomeInteger](v: T): PreparedParam =
|
||||
result.value = $v
|
||||
|
||||
|
||||
proc toPreparedParam*[T: SomeFloat](v: T): PreparedParam =
|
||||
result.value = $v
|
||||
|
||||
|
||||
proc toPreparedParam*(v: JsonNode): PreparedParam =
|
||||
if v.isNil or v.kind == JNull:
|
||||
return nullPreparedParam()
|
||||
|
||||
case v.kind
|
||||
of JBool:
|
||||
result.value = if v.getBool: "1" else: "0"
|
||||
of JInt:
|
||||
result.value = $v.getInt
|
||||
of JFloat:
|
||||
result.value = $v.getFloat
|
||||
of JString:
|
||||
result.value = v.getStr
|
||||
of JArray, JObject:
|
||||
result.value = v.pretty
|
||||
of JNull:
|
||||
discard
|
||||
|
||||
|
||||
proc toPreparedParam*[T](v: Option[T]): PreparedParam =
|
||||
if v.isSome:
|
||||
return toPreparedParam(v.get)
|
||||
return nullPreparedParam()
|
||||
|
||||
|
||||
proc toPreparedParam*[T](v: T): PreparedParam =
|
||||
result.value = $v
|
||||
|
||||
|
||||
proc toPreparedParams*(args: seq[string]): seq[PreparedParam] =
|
||||
result = newSeq[PreparedParam](args.len)
|
||||
for i, arg in args:
|
||||
if arg == "NULL" or arg == "null":
|
||||
result[i] = nullPreparedParam()
|
||||
else:
|
||||
result[i] = toPreparedParam(arg)
|
||||
|
||||
|
||||
proc toPreparedParams*(args: JsonNode): seq[PreparedParam] =
|
||||
if args.isNil or args.kind == JNull:
|
||||
return
|
||||
|
||||
if args.kind == JArray:
|
||||
result = newSeq[PreparedParam](args.len)
|
||||
for i in 0 ..< args.len:
|
||||
result[i] = toPreparedParam(args[i])
|
||||
return
|
||||
|
||||
result = @[toPreparedParam(args)]
|
||||
|
||||
|
||||
proc preparedText*(param: PreparedParam): string =
|
||||
if param.isNull:
|
||||
return "NULL"
|
||||
return param.value
|
||||
|
||||
|
||||
proc preparedTextSeq*(args: openArray[PreparedParam]): seq[string] =
|
||||
result = newSeq[string](args.len)
|
||||
for i, arg in args:
|
||||
result[i] = arg.preparedText
|
||||
|
||||
|
||||
proc countQuestionMarks*(s: string): int =
|
||||
for ch in s:
|
||||
if ch == '?':
|
||||
inc result
|
||||
|
||||
|
||||
proc buildPreparedArgsExpr*(args: NimNode): NimNode =
|
||||
let toPreparedParamSym = bindSym("toPreparedParam")
|
||||
let nullPreparedParamSym = bindSym("nullPreparedParam")
|
||||
var arr = nnkBracket.newTree()
|
||||
for arg in args:
|
||||
if arg.kind == nnkNilLit:
|
||||
arr.add(newCall(nullPreparedParamSym))
|
||||
else:
|
||||
arr.add(newCall(toPreparedParamSym, arg))
|
||||
result = newTree(nnkPrefix, ident("@"), arr)
|
||||
@@ -0,0 +1,28 @@
|
||||
query_builder/models/xx/xx_exec.nim
|
||||
|
||||
```nim
|
||||
# public exec
|
||||
|
||||
# return json
|
||||
get() / first() / find() / insert() / insertId() / update() / delete()
|
||||
|
||||
# return string
|
||||
getPlain() / firstPlain() / findPlain()
|
||||
|
||||
# aggregate
|
||||
count() / min() / max() / avg() / sum()
|
||||
```
|
||||
|
||||
```nim
|
||||
# private exec
|
||||
getAllRows() / getRow() / exec() / insertId()
|
||||
getAllRowsPlain() / getRowPlain()
|
||||
```
|
||||
|
||||
query_builder/libs/xx/xx_impl.nim
|
||||
```nim
|
||||
query() / exec() / execGetValue()
|
||||
rawQuery() / rawExec()
|
||||
```
|
||||
|
||||
---
|
||||
@@ -0,0 +1,48 @@
|
||||
import ./env
|
||||
|
||||
|
||||
import ./schema_builder/enums; export enums
|
||||
import ./schema_builder/models/table; export table
|
||||
import ./schema_builder/models/column; export column
|
||||
|
||||
when isExistsSqlite:
|
||||
import ./query_builder/models/sqlite/sqlite_types; export SQLite3, SqliteConnections
|
||||
import ./schema_builder/usecases/sqlite/create as sqlite_create; export sqlite_create
|
||||
import ./schema_builder/usecases/sqlite/alter as sqlite_alter; export sqlite_alter
|
||||
import ./schema_builder/usecases/sqlite/drop as sqlite_drop; export sqlite_drop
|
||||
import ./schema_builder/usecases/sqlite/create_schema as sqlite_create_schema; export sqlite_create_schema
|
||||
|
||||
when isExistsPostgres:
|
||||
import ./query_builder/models/postgres/postgres_types; export PostgreSQL, PostgresConnections
|
||||
import ./schema_builder/usecases/postgres/create as postgres_create; export postgres_create
|
||||
import ./schema_builder/usecases/postgres/alter as postgres_alter; export postgres_alter
|
||||
import ./schema_builder/usecases/postgres/drop as postgres_drop; export postgres_drop
|
||||
import ./schema_builder/usecases/postgres/create_schema as postgres_create_schema; export postgres_create_schema
|
||||
|
||||
when isExistsMariadb:
|
||||
import ./query_builder/models/mariadb/mariadb_types; export MariaDB, MariadbConnections
|
||||
import ./schema_builder/usecases/mariadb/create as mariadb_create; export mariadb_create
|
||||
import ./schema_builder/usecases/mariadb/alter as mariadb_alter; export mariadb_alter
|
||||
import ./schema_builder/usecases/mariadb/drop as mariadb_drop; export mariadb_drop
|
||||
import ./schema_builder/usecases/mariadb/create_schema as mariadb_create_schema; export mariadb_create_schema
|
||||
|
||||
when isExistsMysql:
|
||||
import ./query_builder/models/mysql/mysql_types; export MySql, MysqlConnections
|
||||
import ./schema_builder/usecases/mysql/create as mysql_create; export mysql_create
|
||||
import ./schema_builder/usecases/mysql/alter as mysql_alter; export mysql_alter
|
||||
import ./schema_builder/usecases/mysql/drop as mysql_drop; export mysql_drop
|
||||
import ./schema_builder/usecases/mysql/create_schema as mysql_create_schema; export mysql_create_schema
|
||||
|
||||
when isExistsSurrealdb:
|
||||
import ./query_builder/models/surreal/surreal_types; export SurrealDB, SurrealConnections
|
||||
import ./schema_builder/usecases/surreal/create as surreal_create; export surreal_create
|
||||
import ./schema_builder/usecases/surreal/alter as surreal_alter; export surreal_alter
|
||||
import ./schema_builder/usecases/surreal/drop as surreal_drop; export surreal_drop
|
||||
import ./schema_builder/usecases/surreal/create_schema as surreal_create_schema; export surreal_create_schema
|
||||
|
||||
when isExistsBaradb:
|
||||
import ./query_builder/models/baradb/baradb_types; export Baradb, BaradbConnections
|
||||
import ./schema_builder/usecases/baradb/create as baradb_create; export baradb_create
|
||||
import ./schema_builder/usecases/baradb/alter as baradb_alter; export baradb_alter
|
||||
import ./schema_builder/usecases/baradb/drop as baradb_drop; export baradb_drop
|
||||
import ./schema_builder/usecases/baradb/create_schema as baradb_create_schema; export baradb_create_schema
|
||||
Binary file not shown.
@@ -0,0 +1,62 @@
|
||||
type RdbTypeKind* = enum
|
||||
# int
|
||||
rdbIncrements = "rdbIncrements"
|
||||
rdbInteger = "rdbInteger"
|
||||
rdbSmallInteger = "rdbSmallInteger"
|
||||
rdbMediumInteger = "rdbMediumInteger"
|
||||
rdbBigInteger = "rdbBigInteger"
|
||||
# float
|
||||
rdbDecimal = "rdbDecimal"
|
||||
rdbDouble = "rdbDouble"
|
||||
rdbFloat = "rdbFloat"
|
||||
# char
|
||||
rdbUuid = "rdbUuid"
|
||||
rdbChar = "rdbChar"
|
||||
rdbString = "rdbString"
|
||||
# text
|
||||
rdbText = "rdbText"
|
||||
rdbMediumText = "rdbMediumText"
|
||||
rdbLongText = "rdbLongText"
|
||||
# date
|
||||
rdbDate = "rdbDate"
|
||||
rdbDatetime = "rdbDatetime"
|
||||
rdbTime = "rdbTime"
|
||||
rdbTimestamp = "rdbTimestamp"
|
||||
rdbTimestamps = "rdbTimestamps"
|
||||
rdbSoftDelete = "rdbSoftDelete"
|
||||
# others
|
||||
rdbBinary = "rdbBinary"
|
||||
rdbBoolean = "rdbBoolean"
|
||||
rdbEnumField = "rdbEnumField"
|
||||
rdbJson = "rdbJson"
|
||||
rdbForeign = "rdbForeign"
|
||||
rdbStrForeign = "rdbStrForeign"
|
||||
|
||||
type ForeignOnDelete* = enum
|
||||
RESTRICT
|
||||
CASCADE
|
||||
SET_NULL
|
||||
NO_ACTION
|
||||
|
||||
|
||||
type DefaultDateTime* = enum
|
||||
None
|
||||
Current
|
||||
CurrentOnUpdate
|
||||
|
||||
type UsecaseType* = enum
|
||||
Create
|
||||
Alter
|
||||
Drop
|
||||
|
||||
type TableMigrationType* = enum
|
||||
CreateTable
|
||||
ChangeTable
|
||||
RenameTable
|
||||
DropTable
|
||||
|
||||
type ColumnMigrationType* = enum
|
||||
AddColumn
|
||||
ChangeColumn
|
||||
RenameColumn
|
||||
DropColumn
|
||||
@@ -0,0 +1,421 @@
|
||||
import std/json
|
||||
import ../enums
|
||||
|
||||
|
||||
type Column* = ref object
|
||||
name*: string
|
||||
typ*: RdbTypeKind
|
||||
isIndex*: bool
|
||||
isNullable*: bool
|
||||
isUnsigned*: bool
|
||||
isUnique*: bool
|
||||
isAutoIncrement*:bool
|
||||
isDefault*: bool
|
||||
isUpdatedAt*: bool
|
||||
defaultBool*: bool
|
||||
defaultInt*: int
|
||||
defaultFloat*: float
|
||||
defaultString*: string
|
||||
defaultJson*: JsonNode
|
||||
defaultDatetime*: DefaultDateTime
|
||||
foreignOnDelete*: ForeignOnDelete
|
||||
commentContent*:string
|
||||
isCommentNull*:bool
|
||||
info*: JsonNode
|
||||
checksum*:string
|
||||
# alter table
|
||||
previousName*:string
|
||||
migrationType*:ColumnMigrationType
|
||||
usecaseType*:UsecaseType
|
||||
|
||||
proc new(_:type Column):Column =
|
||||
return Column(
|
||||
defaultJson: newJNull(),
|
||||
info: newJNull(),
|
||||
)
|
||||
|
||||
proc toSchema*(self:Column):JsonNode =
|
||||
return %*{
|
||||
"name": self.name,
|
||||
"typ": self.typ,
|
||||
"isIndex": self.isIndex,
|
||||
"isNullable": self.isNullable,
|
||||
"isUnsigned": self.isUnsigned,
|
||||
"isUnique": self.isUnique,
|
||||
"isAutoIncrement": self.isAutoIncrement,
|
||||
"isDefault": self.isDefault,
|
||||
"isUpdatedAt": self.isUpdatedAt,
|
||||
"defaultBool": self.defaultBool,
|
||||
"defaultInt": self.defaultInt,
|
||||
"defaultFloat": self.defaultFloat,
|
||||
"defaultString": self.defaultString,
|
||||
"defaultJson": self.defaultJson,
|
||||
"foreignOnDelete": self.foreignOnDelete,
|
||||
"comment": self.commentContent,
|
||||
"isCommentNull": self.isCommentNull,
|
||||
"info": self.info,
|
||||
"previousName":self.previousName,
|
||||
"migrationType":self.migrationType,
|
||||
"usecaseType": self.usecaseType,
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# int
|
||||
# =============================================================================
|
||||
proc increments*(_:type Column, name:string): Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbIncrements
|
||||
return column
|
||||
|
||||
|
||||
proc integer*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbInteger
|
||||
return column
|
||||
|
||||
|
||||
proc smallInteger*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbSmallInteger
|
||||
return column
|
||||
|
||||
|
||||
proc mediumInteger*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbMediumInteger
|
||||
return column
|
||||
|
||||
|
||||
proc bigInteger*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbBigInteger
|
||||
return column
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# float
|
||||
# =============================================================================
|
||||
proc decimal*(_:type Column, name:string, maximum:int, digit:int): Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbDecimal
|
||||
column.info = %*{
|
||||
"maximum": maximum,
|
||||
"digit": digit
|
||||
}
|
||||
return column
|
||||
|
||||
|
||||
proc double*(_:type Column, name:string, maximum:int, digit:int):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbDouble
|
||||
column.info = %*{
|
||||
"maximum": maximum,
|
||||
"digit": digit
|
||||
}
|
||||
return column
|
||||
|
||||
|
||||
proc float*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbFloat
|
||||
return column
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# char
|
||||
# =============================================================================
|
||||
proc char*(_:type Column, name:string, maxLength:int): Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbChar
|
||||
column.info = %*{
|
||||
"maxLength": maxLength
|
||||
}
|
||||
return column
|
||||
|
||||
|
||||
proc string*(_:type Column, name:string, length=256):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbString
|
||||
column.info = %*{"maxLength": length}
|
||||
return column
|
||||
|
||||
|
||||
proc uuid*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbUuid
|
||||
column.isUnique = true
|
||||
column.info = %*{"maxLength": 256}
|
||||
return column
|
||||
|
||||
|
||||
proc text*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbText
|
||||
return column
|
||||
|
||||
|
||||
proc mediumText*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbMediumText
|
||||
return column
|
||||
|
||||
|
||||
proc longText*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbLongText
|
||||
return column
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# date
|
||||
# =============================================================================
|
||||
proc date*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbDate
|
||||
return column
|
||||
|
||||
|
||||
proc datetime*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbDatetime
|
||||
return column
|
||||
|
||||
|
||||
proc time*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbTime
|
||||
return column
|
||||
|
||||
|
||||
proc timestamp*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbTimestamp
|
||||
return column
|
||||
|
||||
|
||||
proc timestamps*(_:type Column):Column =
|
||||
let column = Column.new()
|
||||
column.typ = rdbTimestamps
|
||||
column.isUpdatedAt = true
|
||||
return column
|
||||
|
||||
|
||||
proc softDelete*(_:type Column):Column =
|
||||
let column = Column.new()
|
||||
column.typ = rdbSoftDelete
|
||||
return column
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# others
|
||||
# =============================================================================
|
||||
proc binary*(_:type Column, name:string): Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbBinary
|
||||
return column
|
||||
|
||||
|
||||
proc boolean*(_:type Column, name:string): Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbBoolean
|
||||
return column
|
||||
|
||||
|
||||
proc enumField*(_:type Column, name:string, options:openArray[string]):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbEnumField
|
||||
column.info = %*{
|
||||
"options": options
|
||||
}
|
||||
return column
|
||||
|
||||
|
||||
proc json*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.typ = rdbJson
|
||||
return column
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Foreign
|
||||
# =============================================================================
|
||||
proc foreign*(_:type Column, name:string):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.previousName = name
|
||||
column.typ = rdbForeign
|
||||
column.isIndex = false
|
||||
return column
|
||||
|
||||
|
||||
proc strForeign*(_:type Column, name:string, length=256):Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.previousName = name
|
||||
column.typ = rdbStrForeign
|
||||
column.isIndex = false
|
||||
column.info = %*{"maxLength": length}
|
||||
return column
|
||||
|
||||
|
||||
proc reference*(self:Column, column:string):Column =
|
||||
if self.info.kind == JNull:
|
||||
self.info = %*{"column": column}
|
||||
else:
|
||||
self.info["column"] = %column
|
||||
return self
|
||||
|
||||
|
||||
proc onTable*(self:Column, table:string):Column =
|
||||
if self.info.kind == JNull:
|
||||
self.info = %*{"table": table}
|
||||
else:
|
||||
self.info["table"] = %table
|
||||
return self
|
||||
|
||||
|
||||
when NimMajor == 1:
|
||||
proc on*(self:Column, table:string):Column {.deprecated: "Consider using onTable instead".} =
|
||||
self.info["table"] = %*table
|
||||
return self
|
||||
|
||||
|
||||
proc onDelete*(self:Column, kind:ForeignOnDelete):Column =
|
||||
self.foreignOnDelete = kind
|
||||
return self
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# options
|
||||
# =============================================================================
|
||||
proc autoIncrement*(c: Column): Column =
|
||||
c.isAutoIncrement = true
|
||||
c.isDefault = false
|
||||
c.isUnique = true
|
||||
return c
|
||||
|
||||
|
||||
proc default*(c: Column, value:bool): Column =
|
||||
if not c.isAutoIncrement:
|
||||
c.isDefault = true
|
||||
c.defaultBool = value
|
||||
return c
|
||||
|
||||
|
||||
proc default*(c: Column, value:int): Column =
|
||||
if not c.isAutoIncrement:
|
||||
c.isDefault = true
|
||||
c.defaultInt = value
|
||||
return c
|
||||
|
||||
|
||||
proc default*(c: Column, value:float): Column =
|
||||
if not c.isAutoIncrement:
|
||||
c.isDefault = true
|
||||
c.defaultFloat = value
|
||||
return c
|
||||
|
||||
|
||||
proc default*(c: Column, value:string): Column =
|
||||
if not c.isAutoIncrement:
|
||||
c.isDefault = true
|
||||
c.defaultString = value
|
||||
return c
|
||||
|
||||
|
||||
proc default*(c: Column, value:JsonNode): Column =
|
||||
if not c.isAutoIncrement:
|
||||
c.isDefault = true
|
||||
c.defaultJson = value
|
||||
return c
|
||||
|
||||
|
||||
proc default*(c: Column, value:DefaultDateTime): Column =
|
||||
if not c.isAutoIncrement:
|
||||
c.isDefault = true
|
||||
c.defaultDatetime = value
|
||||
return c
|
||||
|
||||
|
||||
proc default*(c: Column):Column =
|
||||
if not c.isAutoIncrement:
|
||||
c.isDefault = true
|
||||
return c
|
||||
|
||||
|
||||
proc index*(c: Column):Column =
|
||||
if not c.isUnique:
|
||||
c.isIndex = true
|
||||
return c
|
||||
|
||||
|
||||
proc nullable*(c: Column): Column =
|
||||
c.isNullable = true
|
||||
return c
|
||||
|
||||
|
||||
proc unique*(c: Column): Column =
|
||||
c.isUnique = true
|
||||
c.isIndex = false
|
||||
return c
|
||||
|
||||
|
||||
proc unsigned*(c: Column): Column =
|
||||
c.isUnsigned = true
|
||||
return c
|
||||
|
||||
|
||||
proc comment*(c: Column, value:string): Column =
|
||||
c.commentContent = value
|
||||
return c
|
||||
|
||||
proc comment*(c: Column, arg:type nil): Column =
|
||||
c.isCommentNull = true
|
||||
return c
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# alter table
|
||||
# =============================================================================
|
||||
proc add*(c: Column): Column =
|
||||
c.migrationType = AddColumn
|
||||
return c
|
||||
|
||||
proc change*(c: Column): Column =
|
||||
c.migrationType = ChangeColumn
|
||||
return c
|
||||
|
||||
proc renameColumn*(_:type Column, src, dest:string): Column =
|
||||
let column = Column.new()
|
||||
column.name = dest
|
||||
column.previousName = src
|
||||
column.migrationType = RenameColumn
|
||||
return column
|
||||
|
||||
proc dropColumn*(_:type Column, name:string): Column =
|
||||
let column = Column.new()
|
||||
column.name = name
|
||||
column.migrationType = DropColumn
|
||||
return column
|
||||
@@ -0,0 +1,72 @@
|
||||
import std/json
|
||||
import std/strutils
|
||||
import std/sequtils
|
||||
import ../enums
|
||||
from ./column import Column, toSchema
|
||||
|
||||
|
||||
type Table* = ref object
|
||||
name*: string
|
||||
columns*: seq[Column]
|
||||
primary*: seq[string]
|
||||
query*:seq[string]
|
||||
commentContent*:string
|
||||
checksum*:string
|
||||
previousName*:string
|
||||
migrationType*: TableMigrationType
|
||||
usecaseType*:UsecaseType
|
||||
|
||||
|
||||
proc table*(name:string):Table =
|
||||
return Table(
|
||||
name: name,
|
||||
query: newSeq[string](),
|
||||
migrationType: CreateTable,
|
||||
)
|
||||
|
||||
|
||||
proc table*(name:string, columns:openArray[Column], comment=""):Table =
|
||||
return Table(
|
||||
name: name,
|
||||
commentContent: comment,
|
||||
columns: @columns,
|
||||
query: newSeq[string](),
|
||||
migrationType: CreateTable,
|
||||
)
|
||||
|
||||
proc table*(name:string, columns:seq[Column], primary:seq[string] = @[], comment=""):Table =
|
||||
return Table(
|
||||
name: name,
|
||||
commentContent: comment,
|
||||
columns: @columns,
|
||||
primary: @primary,
|
||||
query: newSeq[string](),
|
||||
migrationType: CreateTable,
|
||||
)
|
||||
|
||||
|
||||
proc toSchema*(self:Table):JsonNode =
|
||||
let columns = self.columns.map(
|
||||
proc(column:Column):JsonNode =
|
||||
return column.toSchema()
|
||||
)
|
||||
return %*{
|
||||
"name":self.name,
|
||||
"comment": self.commentContent,
|
||||
"columns": columns,
|
||||
"primary": self.primary,
|
||||
"previousName": self.previousName,
|
||||
"migrationType": self.migrationType,
|
||||
"usecaseType": self.usecaseType,
|
||||
}
|
||||
|
||||
proc smallName*(self:Table):string =
|
||||
## TableName -> tablename
|
||||
return self.name.toLowerAscii()
|
||||
|
||||
proc renameTo*(self:Table, name:string):Table =
|
||||
let previousName = self.name
|
||||
self.name = name
|
||||
self.previousName = previousName
|
||||
self.migrationType = RenameTable
|
||||
return self
|
||||
@@ -0,0 +1,8 @@
|
||||
import std/strformat
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
import ./baradb_query_type
|
||||
|
||||
proc addColumn*(self: BaradbSchema, isReset: bool) =
|
||||
let sql = &"ALTER TABLE `{self.table.name}` ADD COLUMN `{self.column.name}` {self.column.typ}"
|
||||
discard waitFor self.rdb.raw(sql).exec()
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import ../../../query_builder/models/baradb/baradb_types
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
|
||||
type BaradbSchema* = ref object
|
||||
rdb*: BaradbConnections
|
||||
table*: Table
|
||||
column*: Column
|
||||
|
||||
proc new*(_: type BaradbSchema, rdb: BaradbConnections, table: Table): BaradbSchema =
|
||||
return BaradbSchema(rdb: rdb, table: table)
|
||||
|
||||
proc new*(_: type BaradbSchema, rdb: BaradbConnections, table: Table, column: Column): BaradbSchema =
|
||||
return BaradbSchema(rdb: rdb, table: table, column: column)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import std/strformat
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
import ./baradb_query_type
|
||||
|
||||
proc changeColumn*(self: BaradbSchema, isReset: bool) =
|
||||
let sql = &"ALTER TABLE `{self.table.name}` ALTER COLUMN `{self.column.name}` TYPE {self.column.typ}"
|
||||
discard waitFor self.rdb.raw(sql).exec()
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import std/strformat
|
||||
import ../../models/table
|
||||
import ./baradb_query_type
|
||||
|
||||
proc createMigrationTable*(self: BaradbSchema) =
|
||||
let sql = """CREATE TABLE IF NOT EXISTS "schema_migrations" (
|
||||
"id" SERIAL PRIMARY KEY,
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"checksum" VARCHAR(64) NOT NULL,
|
||||
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)"""
|
||||
discard waitFor self.rdb.raw(sql).exec()
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import std/strformat
|
||||
import std/sequtils
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
import ./baradb_query_type
|
||||
|
||||
proc createTable*(self: BaradbSchema, isReset: bool) =
|
||||
var query = ""
|
||||
for i, column in self.table.columns:
|
||||
if query.len > 0: query.add(", ")
|
||||
query.add(&"`{column.name}` {column.typ}")
|
||||
if self.table.primary.len > 0:
|
||||
let primary = self.table.primary.map(proc(row: string): string = &"`{row}`").join(", ")
|
||||
query.add(&", PRIMARY KEY({primary})")
|
||||
let sql = &"CREATE TABLE IF NOT EXISTS `{self.table.name}` ({query})"
|
||||
discard waitFor self.rdb.raw(sql).exec()
|
||||
@@ -0,0 +1,8 @@
|
||||
import std/strformat
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
import ./baradb_query_type
|
||||
|
||||
proc dropColumn*(self: BaradbSchema, isReset: bool) =
|
||||
let sql = &"ALTER TABLE `{self.table.name}` DROP COLUMN `{self.column.name}`"
|
||||
discard waitFor self.rdb.raw(sql).exec()
|
||||
@@ -0,0 +1,7 @@
|
||||
import std/strformat
|
||||
import ../../models/table
|
||||
import ./baradb_query_type
|
||||
|
||||
proc dropTable*(self: BaradbSchema, isReset: bool) =
|
||||
let sql = &"DROP TABLE `{self.table.name}`"
|
||||
discard waitFor self.rdb.raw(sql).exec()
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import std/strformat
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
import ./baradb_query_type
|
||||
|
||||
proc renameColumn*(self: BaradbSchema, isReset: bool) =
|
||||
let sql = &"ALTER TABLE `{self.table.name}` RENAME COLUMN `{self.column.name}` TO `{self.column.changeTo}`"
|
||||
discard waitFor self.rdb.raw(sql).exec()
|
||||
@@ -0,0 +1,7 @@
|
||||
import std/strformat
|
||||
import ../../models/table
|
||||
import ./baradb_query_type
|
||||
|
||||
proc renameTable*(self: BaradbSchema, isReset: bool) =
|
||||
let sql = &"ALTER TABLE `{self.table.name}` RENAME TO `{self.table.changeTo}`"
|
||||
discard waitFor self.rdb.raw(sql).exec()
|
||||
@@ -0,0 +1,7 @@
|
||||
import std/strformat
|
||||
import ../../models/table
|
||||
import ./baradb_query_type
|
||||
|
||||
proc resetTable*(self: BaradbSchema) =
|
||||
let sql = &"DELETE FROM `{self.table.name}`"
|
||||
discard waitFor self.rdb.raw(sql).exec()
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import std/strformat
|
||||
import ../../../query_builder/models/baradb/baradb_types
|
||||
import ../../models/table
|
||||
|
||||
proc execThenSaveHistory*(rdb: BaradbConnections, name: string, queries: seq[string], checksum: string) =
|
||||
for sql in queries:
|
||||
discard waitFor rdb.raw(sql).exec()
|
||||
let hist = &"INSERT INTO `schema_migrations` (`name`, `checksum`) VALUES ('{name}', '{checksum}')"
|
||||
discard waitFor rdb.raw(hist).exec()
|
||||
|
||||
proc shouldRun*(rdb: BaradbConnections, table: Table, checksum: string, isReset: bool): bool =
|
||||
return true
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import std/strformat
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
|
||||
proc addColumnString*(table: Table, column: Column): string =
|
||||
result = &"`{column.name}` {column.typ}"
|
||||
if not column.isNullable:
|
||||
result.add(" NOT NULL")
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import std/strformat
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
|
||||
proc changeColumnString*(table: Table, column: Column): string =
|
||||
result = &"`{column.name}` {column.typ}"
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import std/strformat
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
|
||||
proc createColumnString*(table: Table, column: Column): string =
|
||||
result = &"`{column.name}` {column.typ}"
|
||||
if not column.isNullable:
|
||||
result.add(" NOT NULL")
|
||||
if column.isUnique:
|
||||
result.add(" UNIQUE")
|
||||
if column.isDefault:
|
||||
result.add(&" DEFAULT {column.default}")
|
||||
|
||||
proc createForeignString*(table: Table, column: Column): string =
|
||||
result = &"FOREIGN KEY (`{column.name}`) REFERENCES `{column.info}`"
|
||||
|
||||
proc createIndexString*(table: Table, column: Column): string =
|
||||
result = &"CREATE INDEX `{table.name}_{column.name}_idx` ON `{table.name}` (`{column.name}`)"
|
||||
|
||||
proc createUpdatedAtString*(table: Table, column: Column): string =
|
||||
result = ""
|
||||
|
||||
proc createCommentColumn*(table: Table, column: Column): string =
|
||||
result = ""
|
||||
@@ -0,0 +1,21 @@
|
||||
when NimMajor >= 2:
|
||||
import checksums/sha1
|
||||
else:
|
||||
import std/sha1
|
||||
|
||||
import std/json
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
import ./schema_utils
|
||||
import ./mariadb_query_type
|
||||
import ./sub/add_column_query
|
||||
|
||||
|
||||
proc addColumn*(self:MariadbSchema, isReset:bool) =
|
||||
let queries = addColumnString(self.table, self.column)
|
||||
|
||||
let schema = $self.column.toSchema()
|
||||
let checksum = $schema.secureHash()
|
||||
|
||||
if shouldRun(self.rdb, self.table, checksum, isReset):
|
||||
execThenSaveHistory(self.rdb, self.table.name, queries, checksum)
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
when NimMajor >= 2:
|
||||
import checksums/sha1
|
||||
else:
|
||||
import std/sha1
|
||||
|
||||
import std/strformat
|
||||
import std/json
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
import ./schema_utils
|
||||
import ./mariadb_query_type
|
||||
import ./sub/change_column_query
|
||||
|
||||
|
||||
proc changeColumn*(self:MariadbSchema, isReset:bool) =
|
||||
let schema = $self.column.toSchema()
|
||||
let checksum = $schema.secureHash()
|
||||
|
||||
var queries:seq[string]
|
||||
queries.add(&"ALTER TABLE `{self.table.name}` DROP INDEX IF EXISTS `{self.column.name}`") # unique
|
||||
queries.add(&"ALTER TABLE `{self.table.name}` DROP INDEX IF EXISTS `{self.table.name}_{self.column.name}_index`")
|
||||
queries.add(
|
||||
changeColumnString(self.table, self.column)
|
||||
)
|
||||
|
||||
if self.column.isIndex:
|
||||
let indexQuery = addIndexString(self.column, self.table)
|
||||
queries.add(indexQuery)
|
||||
|
||||
if shouldRun(self.rdb, self.table, checksum, isReset):
|
||||
execThenSaveHistory(self.rdb, self.table.name, queries, checksum)
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import std/strformat
|
||||
import ../../enums
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
import ./schema_utils
|
||||
import ./mariadb_query_type
|
||||
import ./sub/create_column_query
|
||||
|
||||
|
||||
proc createMigrationTable*(self: MariadbSchema) =
|
||||
var queries:seq[string] = @[]
|
||||
var query = ""
|
||||
var foreignQuery = ""
|
||||
var indexQuery:seq[string] = @[]
|
||||
for i, column in self.table.columns:
|
||||
if query.len > 0: query.add(", ")
|
||||
query.add(createColumnString(self.table, column))
|
||||
|
||||
if column.typ == rdbForeign or column.typ == rdbStrForeign:
|
||||
if foreignQuery.len > 0: foreignQuery.add(", ")
|
||||
foreignQuery.add(createForeignKey(self.table, column))
|
||||
|
||||
if column.isIndex:
|
||||
indexQuery.add(createIndexString(self.table, column))
|
||||
|
||||
if foreignQuery.len > 0:
|
||||
queries.add(
|
||||
&"CREATE TABLE IF NOT EXISTS `{self.table.name}` ({query}, {foreignQuery})"
|
||||
)
|
||||
else:
|
||||
queries.add(
|
||||
&"CREATE TABLE IF NOT EXISTS `{self.table.name}` ({query})"
|
||||
)
|
||||
|
||||
if indexQuery.len > 0:
|
||||
queries.add(indexQuery)
|
||||
|
||||
exec(self.rdb, queries)
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
when NimMajor >= 2:
|
||||
import checksums/sha1
|
||||
else:
|
||||
import std/sha1
|
||||
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
import std/sequtils
|
||||
import std/json
|
||||
import ../../enums
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
import ./schema_utils
|
||||
import ./mariadb_query_type
|
||||
import ./sub/create_column_query
|
||||
|
||||
|
||||
proc createTable*(self: MariadbSchema, isReset:bool) =
|
||||
var queries:seq[string] = @[]
|
||||
var query = ""
|
||||
var foreignQuery = ""
|
||||
var indexQuery:seq[string] = @[]
|
||||
|
||||
for i, column in self.table.columns:
|
||||
if query.len > 0: query.add(", ")
|
||||
query.add(createColumnString(self.table, column))
|
||||
|
||||
if column.typ == rdbForeign or column.typ == rdbStrForeign:
|
||||
if foreignQuery.len > 0: foreignQuery.add(", ")
|
||||
foreignQuery.add(createForeignKey(self.table, column))
|
||||
|
||||
if column.isIndex:
|
||||
indexQuery.add(createIndexString(self.table, column))
|
||||
|
||||
if self.table.primary.len > 0:
|
||||
let primary = self.table.primary.map(
|
||||
proc(row:string):string =
|
||||
return &"`{row}`"
|
||||
)
|
||||
.join(", ")
|
||||
query.add(&", PRIMARY KEY({primary})")
|
||||
|
||||
if foreignQuery.len > 0:
|
||||
queries.add(
|
||||
&"CREATE TABLE IF NOT EXISTS `{self.table.name}` ({query}, {foreignQuery})"
|
||||
)
|
||||
else:
|
||||
queries.add(
|
||||
&"CREATE TABLE IF NOT EXISTS `{self.table.name}` ({query})"
|
||||
)
|
||||
|
||||
if self.table.commentContent.len > 0:
|
||||
queries[^1].add(
|
||||
&" COMMENT = '{self.table.commentContent}'"
|
||||
)
|
||||
|
||||
if indexQuery.len > 0:
|
||||
queries.add(indexQuery)
|
||||
|
||||
let schema = $self.table.toSchema()
|
||||
let checksum = $schema.secureHash()
|
||||
|
||||
if shouldRun(self.rdb, self.table, checksum, isReset):
|
||||
execThenSaveHistory(self.rdb, self.table.name, queries, checksum)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
when NimMajor >= 2:
|
||||
import checksums/sha1
|
||||
else:
|
||||
import std/sha1
|
||||
|
||||
import std/strformat
|
||||
import std/json
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
import ./schema_utils
|
||||
import ./mariadb_query_type
|
||||
|
||||
|
||||
proc dropColumn*(self:MariadbSchema, isReset:bool) =
|
||||
let query = &"ALTER TABLE `{self.table.name}` DROP COLUMN `{self.column.name}`"
|
||||
let schema = $self.column.toSchema()
|
||||
let checksum = $schema.secureHash()
|
||||
if shouldRun(self.rdb, self.table, checksum, isReset):
|
||||
execThenSaveHistory(self.rdb, self.table.name, query, checksum)
|
||||
@@ -0,0 +1,18 @@
|
||||
when NimMajor >= 2:
|
||||
import checksums/sha1
|
||||
else:
|
||||
import std/sha1
|
||||
|
||||
import std/strformat
|
||||
import std/json
|
||||
import ../../models/table
|
||||
import ./schema_utils
|
||||
import ./mariadb_query_type
|
||||
|
||||
|
||||
proc dropTable*(self:MariadbSchema, isReset:bool) =
|
||||
let query = &"DROP TABLE IF EXISTS `{self.table.name}`"
|
||||
let schema = $self.table.toSchema()
|
||||
let checksum = $schema.secureHash()
|
||||
if shouldRun(self.rdb, self.table, checksum, isReset):
|
||||
execThenSaveHistory(self.rdb, self.table.name, query, checksum)
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import ../../../query_builder/models/mariadb/mariadb_types
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
|
||||
|
||||
type MariadbSchema* = ref object
|
||||
rdb*:MariadbConnections
|
||||
table*:Table
|
||||
column*:Column
|
||||
|
||||
proc new*(_:type MariadbSchema, rdb:MariadbConnections, table:Table):MariadbSchema =
|
||||
return MariadbSchema(rdb:rdb, table:table)
|
||||
|
||||
proc new*(_:type MariadbSchema, rdb:MariadbConnections, table:Table, column:Column):MariadbSchema =
|
||||
return MariadbSchema(rdb:rdb, table:table, column:column)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
when NimMajor >= 2:
|
||||
import checksums/sha1
|
||||
else:
|
||||
import std/sha1
|
||||
|
||||
import std/json
|
||||
import std/strformat
|
||||
import ../../models/table
|
||||
import ../../models/column
|
||||
import ./schema_utils
|
||||
import ./mariadb_query_type
|
||||
|
||||
|
||||
proc renameColumn*(self:MariadbSchema, isReset:bool) =
|
||||
let query = &"ALTER TABLE `{self.table.name}` RENAME COLUMN `{self.column.previousName}` TO `{self.column.name}`"
|
||||
let schema = $self.column.toSchema()
|
||||
let checksum = $schema.secureHash()
|
||||
if shouldRun(self.rdb, self.table, checksum, isReset):
|
||||
execThenSaveHistory(self.rdb, self.table.name, query, checksum)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
when NimMajor >= 2:
|
||||
import checksums/sha1
|
||||
else:
|
||||
import std/sha1
|
||||
|
||||
import std/strformat
|
||||
import std/json
|
||||
import ../../models/table
|
||||
import ./mariadb_query_type
|
||||
import ./schema_utils
|
||||
|
||||
|
||||
proc renameTable*(self:MariadbSchema, isReset:bool) =
|
||||
let query = &"RENAME TABLE `{self.table.previousName}` TO `{self.table.name}`"
|
||||
let schema = $self.table.toSchema()
|
||||
let checksum = $schema.secureHash()
|
||||
|
||||
if shouldRun(self.rdb, self.table, checksum, isReset):
|
||||
execThenSaveHistory(self.rdb, self.table.name, query, checksum)
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import std/asyncdispatch
|
||||
import std/strformat
|
||||
import ../../../query_builder/models/mariadb/mariadb_query
|
||||
import ../../../query_builder/models/mariadb/mariadb_exec
|
||||
import ../../models/table
|
||||
import ./mariadb_query_type
|
||||
|
||||
|
||||
proc resetMigrationTable*(self:MariadbSchema) =
|
||||
self.rdb.table("_allographer_migrations").where("name", "=", self.table.name).delete.waitFor
|
||||
|
||||
proc resetTable*(self:MariadbSchema) =
|
||||
self.rdb.raw(&"DROP TABLE IF EXISTS `{self.table.name}`").exec.waitFor
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user