Skip to content

Commit f722475

Browse files
committed
surreal: normalize nulls/dates/ids, refactor schema & autoincrement, and update tests
- Add helpers to detect Surreal record IDs and ISO-like datetimes; avoid quoting record IDs and emit datetime literals consistently. - Use NONE as SurrealDB null representation (VALUE/ASSERT/WHERE emit NONE), and adjust query generator (IS NONE). - Rename migration/history table to `_allographer_migrations`, add createMigrationTable(isReset) to allow reset/removal, and store created_at in ISO8601 'T...Z' format. - Rework autoincrement flow: centralize _autoincrement_sequences usage, compute next-index expression, update event/field definitions to use the sequence, and change migration primary column from `id` to `index`. - Append "RETURN AFTER" to INSERT builders and simplify insertId paths to reliably obtain returned IDs. - Refactor create_column_query: introduce helpers (addAssertPart/addValueClause/addAssertClause/autoIncrementValueExpr) to centralize value/default/assert logic and improve nullable/default handling across types (string, int, decimal, datetime, json, record). - Fix/adjust query behaviors (use math::mean for avg), improve avg parsing for multiple JSON numeric kinds, and tighten JSON/record field syntax. - Add test utilities and env defaults (envIntDefault/envStringDefault, isNullish, normalizeInfoDef) and update tests to match new behavior (table/index renames, null checks, function name changes, datetime formats). - Miscellaneous SurrealDB compatibility and normalization fixes across impl, exec, schema utils, and tests.
1 parent e5b9629 commit f722475

17 files changed

Lines changed: 561 additions & 366 deletions

File tree

src/allographer/query_builder/libs/surreal/surreal_lib.nim

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,64 @@ import std/strutils
22
import std/json
33

44

5+
proc looksLikeRecordId(s: string): bool =
6+
let colonPos = s.find(':')
7+
if colonPos <= 0 or colonPos >= s.high:
8+
return false
9+
if s.find(':', colonPos + 1) >= 0:
10+
return false
11+
if s.contains(' ') or s.contains('"') or s.contains('\''):
12+
return false
13+
if not (s[0].isAlphaAscii or s[0] == '_'):
14+
return false
15+
for ch in s[1 ..< colonPos]:
16+
if not (ch.isAlphaNumeric or ch == '_'):
17+
return false
18+
for ch in s[colonPos + 1 .. ^1]:
19+
if ch.isSpaceAscii:
20+
return false
21+
return true
22+
23+
24+
proc looksLikeDateTimeValue(s: string): bool =
25+
if s.len < 10:
26+
return false
27+
28+
for idx in [0, 1, 2, 3, 5, 6, 8, 9]:
29+
if idx >= s.len or not s[idx].isDigit:
30+
return false
31+
32+
if s[4] != '-' or s[7] != '-':
33+
return false
34+
35+
if s.len == 10:
36+
return true
37+
38+
if s.len < 19:
39+
return false
40+
41+
if s[10] notin {'T', ' '}:
42+
return false
43+
44+
for idx in [11, 12, 14, 15, 17, 18]:
45+
if idx >= s.len or not s[idx].isDigit:
46+
return false
47+
48+
if s[13] != ':' or s[16] != ':':
49+
return false
50+
51+
if s.len == 19:
52+
return true
53+
54+
return s[19] in {'.', 'Z', '+', '-'}
55+
56+
557
proc dbQuote(s:string):string =
658
## DB quotes the string.
759
if s == "null":
860
return "NULL"
61+
if looksLikeRecordId(s):
62+
return s
963
result = newStringOfCap(s.len + 2)
1064
result.add "'"
1165
for c in items(s):
@@ -93,14 +147,20 @@ proc appendJsonLetClause(result: var string, idx: int, arg: JsonNode, quoteStrin
93147
result.add($arg.getFloat)
94148
of JString:
95149
let val = arg.getStr().replace("\"", "\\\"")
96-
if quoteString:
150+
if looksLikeRecordId(val):
151+
result.add(val)
152+
elif looksLikeDateTimeValue(val):
153+
result.add("<datetime>\"")
154+
result.add(val)
155+
result.add("\"")
156+
elif quoteString:
97157
result.add('"')
98158
result.add(val)
99159
result.add('"')
100160
else:
101161
result.add(val)
102162
of JNull:
103-
result.add("null")
163+
result.add("NONE")
104164
of JArray, JObject:
105165
result.add($arg)
106166
result.add("; ")

src/allographer/query_builder/models/surreal/query/surreal_generator.nim

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,9 @@ proc whereNullSql*(self: SurrealQuery): SurrealQuery =
214214
column = quote(column)
215215

216216
if self.queryString.contains("WHERE"):
217-
self.queryString.add(&" OR {column} IS NULL")
217+
self.queryString.add(&" OR {column} IS NONE")
218218
else:
219-
self.queryString.add(&" WHERE {column} IS NULL")
219+
self.queryString.add(&" WHERE {column} IS NONE")
220220

221221
return self
222222

@@ -375,7 +375,7 @@ proc selectCountSql*(self: SurrealQuery): SurrealQuery =
375375
proc selectAvgSql*(self: SurrealQuery, column:string): SurrealQuery =
376376
var column = column
377377
column = quote(column)
378-
self.queryString = &"SELECT math::trimean({column}) AS avg"
378+
self.queryString = &"SELECT math::mean({column}) AS avg"
379379
return self
380380

381381

src/allographer/query_builder/models/surreal/surreal_exec.nim

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -391,18 +391,15 @@ proc insert*(self:SurrealQuery, items:seq[JsonNode]) {.async.} =
391391

392392
proc insertId*(self:SurrealQuery, items:JsonNode, key="id"):Future[SurrealId] {.async.} =
393393
## https://surrealdb.com/docs/surrealql/statements/insert
394-
let sql = self.insertValueBuilder(items)
394+
var sql = self.insertValueBuilder(items) & " RETURN AFTER"
395395
self.log.logger(sql)
396396
let res = self.getRow(sql).await
397-
if res.isSome():
398-
return SurrealId.new(res.get()[key].getStr())
399-
else:
400-
return SurrealId.new()
397+
return SurrealId.new(res.get()[key].getStr)
401398

402399

403400
proc insertId*(self: SurrealQuery, items: seq[JsonNode], key="id"):Future[seq[SurrealId]] {.async.} =
404401
result = newSeq[SurrealId](items.len)
405-
var sql = self.insertValuesBuilder(items)
402+
var sql = self.insertValuesBuilder(items) & " RETURN AFTER"
406403
self.log.logger(sql)
407404
let res = self.getAllRows(sql).await
408405
var i = 0
@@ -431,21 +428,18 @@ proc insert*[T](self:SurrealQuery, items:seq[T]) {.async.} =
431428

432429
proc insertId*[T](self:SurrealQuery, items:T, key="id"):Future[SurrealId] {.async.} =
433430
## https://surrealdb.com/docs/surrealql/statements/insert
434-
let sql = self.insertValueBuilder(%items)
431+
var sql = self.insertValueBuilder(%items) & " RETURN AFTER"
435432
self.log.logger(sql)
436433
let res = self.getRow(sql).await
437-
if res.isSome():
438-
return SurrealId.new(res.get()[key].getStr())
439-
else:
440-
return SurrealId.new()
434+
return SurrealId.new(res.get()[key].getStr)
441435

442436

443437
proc insertId*[T](self: SurrealQuery, items: seq[T], key="id"):Future[seq[SurrealId]] {.async.} =
444438
result = newSeq[SurrealId](items.len)
445439
var jsonItems = newSeq[JsonNode](items.len)
446440
for i, item in items:
447441
jsonItems[i] = %item
448-
var sql = self.insertValuesBuilder(jsonItems)
442+
var sql = self.insertValuesBuilder(jsonItems) & " RETURN AFTER"
449443
self.log.logger(sql)
450444
let res = self.getAllRows(sql).await
451445
var i = 0
@@ -573,7 +567,16 @@ proc avg*(self:SurrealQuery, column:string):Future[float]{.async.} =
573567
self.log.logger(sql)
574568
let response = await self.getRow(sql)
575569
if response.isSome:
576-
return response.get["avg"].getStr().parseFloat()
570+
let value = response.get["avg"]
571+
case value.kind
572+
of JInt:
573+
return value.getInt.float
574+
of JFloat:
575+
return value.getFloat()
576+
of JString:
577+
return value.getStr().parseFloat()
578+
else:
579+
return 0.0
577580
else:
578581
return 0.0
579582

src/allographer/schema_builder/queries/surreal/create_migration_table.nim

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import ./surreal_query_type
1313
import ./sub/create_column_query
1414

1515

16-
proc createMigrationTable*(self: SurrealSchema) =
16+
proc createMigrationTable*(self: SurrealSchema, isReset: bool = false) =
1717
let logDisplay = self.rdb.log.shouldDisplayLog
1818
let logFile = self.rdb.log.shouldOutputLogFile
1919
self.rdb.log.shouldDisplayLog = false
@@ -23,7 +23,12 @@ proc createMigrationTable*(self: SurrealSchema) =
2323
self.rdb.log.shouldOutputLogFile = logFile
2424

2525
let info = self.rdb.raw("INFO FOR DB").info().waitFor()
26-
if not info[0]["result"]["tables"].contains("_autoincrement_migrations"):
26+
var hasMigrationTable = info[0]["result"]["tables"].contains("_allographer_migrations")
27+
if isReset and hasMigrationTable:
28+
self.rdb.raw("REMOVE TABLE `_allographer_migrations`").exec().waitFor()
29+
hasMigrationTable = false
30+
31+
if not hasMigrationTable:
2732
var queries:seq[string]
2833
queries.add(&"DEFINE TABLE `{self.table.name}` SCHEMAFULL")
2934

src/allographer/schema_builder/queries/surreal/schema_utils.nim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ proc execThenSaveHistory*(rdb:SurrealConnections, tableName:string, queries:seq[
7575
rdb.log.shouldOutputLogFile = logFile
7676

7777
let tableQuery = queries.join("; ")
78-
let createdAt = now().utc.format("yyyy-MM-dd HH:mm:ss'.'fff")
78+
let createdAt = now().utc.format("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'")
7979
rdb.table("_allographer_migrations").insert(%*{
8080
"name": tableName,
8181
"query": tableQuery,
@@ -107,7 +107,7 @@ proc execThenSaveHistory*(rdb:SurrealConnections, tableName:string, query:string
107107
rdb.log.shouldDisplayLog = logDisplay
108108
rdb.log.shouldOutputLogFile = logFile
109109

110-
let createdAt = now().utc.format("yyyy-MM-dd HH:mm:ss'.'fff")
110+
let createdAt = now().utc.format("yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'")
111111
rdb.table("_allographer_migrations").insert(%*{
112112
"name": tableName,
113113
"query": query,

0 commit comments

Comments
 (0)