diff options
-rw-r--r-- | .travis.yml | 1 | ||||
-rw-r--r-- | compiler/seminst.nim | 4 | ||||
-rw-r--r-- | lib/impure/db_postgres.nim | 112 | ||||
-rw-r--r-- | lib/pure/times.nim | 4 | ||||
-rw-r--r-- | tests/generics/tvarseq_caching.nim | 48 | ||||
-rw-r--r-- | tests/testament/categories.nim | 3 |
6 files changed, 159 insertions, 13 deletions
diff --git a/.travis.yml b/.travis.yml index 4c3dfe1f8..f68375e93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,4 +31,5 @@ script: - nimble install niminst - nim c --taintMode:on tests/testament/tester - tests/testament/tester --pedantic all + - ./koch csource - ./koch xz diff --git a/compiler/seminst.nim b/compiler/seminst.nim index f9a137740..2c767ffc6 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -243,14 +243,14 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, # generic[void](), generic[int]() # see ttypeor.nim test. var i = 0 - newSeq(entry.concreteTypes, fn.typ.len+gp.len) + newSeq(entry.concreteTypes, fn.typ.len+gp.len-1) for s in instantiateGenericParamList(c, gp, pt): addDecl(c, s) entry.concreteTypes[i] = s.typ inc i pushProcCon(c, result) instantiateProcType(c, pt, result, info) - for j in 0 .. result.typ.len-1: + for j in 1 .. result.typ.len-1: entry.concreteTypes[i] = result.typ.sons[j] inc i if tfTriggersCompileTime in result.typ.flags: diff --git a/lib/impure/db_postgres.nim b/lib/impure/db_postgres.nim index b75915a72..7e6219465 100644 --- a/lib/impure/db_postgres.nim +++ b/lib/impure/db_postgres.nim @@ -8,8 +8,58 @@ # ## A higher level `PostgreSQL`:idx: database wrapper. This interface -## is implemented for other databases too. - +## is implemented for other databases also. +## +## Parameter substitution +## ---------------------- +## +## All ``db_*`` modules support the same form of parameter substitution. +## That is, using the ``?`` (question mark) to signify the place where a +## value should be placed. For example: +## +## .. code-block:: Nim +## sql"INSERT INTO myTable (colA, colB, colC) VALUES (?, ?, ?)" +## +## **Note**: There are two approaches to parameter substitution support by +## this module. +## +## 1. ``SqlQuery`` using ``?, ?, ?, ...`` (same as all the ``db_*`` modules) +## +## 2. ``SqlPrepared`` using ``$1, $2, $3, ...`` +## +## .. code-block:: Nim +## prepare(db, "myExampleInsert", +## sql"""INSERT INTO myTable +## (colA, colB, colC) +## VALUES ($1, $2, $3)""", +## 3) +## +## Examples +## -------- +## +## Opening a connection to a database +## ================================== +## +## .. code-block:: Nim +## import db_postgres +## let db = open("localhost", "user", "password", "dbname") +## db.close() +## +## Creating a table +## ================ +## +## .. code-block:: Nim +## db.exec(sql"DROP TABLE IF EXISTS myTable") +## db.exec(sql("""CREATE TABLE myTable ( +## id integer, +## name varchar(50) not null)""")) +## +## Inserting data +## ============== +## +## .. code-block:: Nim +## db.exec(sql"INSERT INTO myTable (id, name) VALUES (0, ?)", +## "Dominik") import strutils, postgres type @@ -64,6 +114,8 @@ proc dbQuote*(s: string): string = proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string = result = "" var a = 0 + if args.len > 0 and not string(formatstr).contains("?"): + dbError("""parameter substitution expects "?" """) for c in items(string(formatstr)): if c == '?': if args[a] == nil: @@ -77,9 +129,17 @@ proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string = proc tryExec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): bool {.tags: [FReadDB, FWriteDb].} = ## tries to execute the query and returns true if successful, false otherwise. - var arr = allocCStringArray(args) - var res = pqexecParams(db, query.string, int32(args.len), nil, arr, + var res = pqexecParams(db, dbFormat(query, args), 0, nil, nil, nil, nil, 0) + result = pqresultStatus(res) == PGRES_COMMAND_OK + pqclear(res) + +proc tryExec*(db: DbConn, stmtName: SqlPrepared, + args: varargs[string, `$`]): bool {.tags: [FReadDB, FWriteDb].} = + ## tries to execute the query and returns true if successful, false otherwise. + var arr = allocCStringArray(args) + var res = pqexecPrepared(db, stmtName.string, int32(args.len), arr, + nil, nil, 0) deallocCStringArray(arr) result = pqresultStatus(res) == PGRES_COMMAND_OK pqclear(res) @@ -87,10 +147,8 @@ proc tryExec*(db: DbConn, query: SqlQuery, proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) {. tags: [FReadDB, FWriteDb].} = ## executes the query and raises EDB if not successful. - var arr = allocCStringArray(args) - var res = pqexecParams(db, query.string, int32(args.len), nil, arr, + var res = pqexecParams(db, dbFormat(query, args), 0, nil, nil, nil, nil, 0) - deallocCStringArray(arr) if pqresultStatus(res) != PGRES_COMMAND_OK: dbError(db) pqclear(res) @@ -109,10 +167,11 @@ proc newRow(L: int): Row = proc setupQuery(db: DbConn, query: SqlQuery, args: varargs[string]): PPGresult = - var arr = allocCStringArray(args) - result = pqexecParams(db, query.string, int32(args.len), nil, arr, + # s is a dummy unique id str for each setupQuery query + let s = "setupQuery_Query_" & string(query) + var res = pqprepare(db, s, dbFormat(query, args), 0, nil) + result = pqexecPrepared(db, s, 0, nil, nil, nil, 0) - deallocCStringArray(arr) if pqResultStatus(result) != PGRES_TUPLES_OK: dbError(db) proc setupQuery(db: DbConn, stmtName: SqlPrepared, @@ -125,6 +184,8 @@ proc setupQuery(db: DbConn, stmtName: SqlPrepared, proc prepare*(db: DbConn; stmtName: string, query: SqlQuery; nParams: int): SqlPrepared = + if nParams > 0 and not string(query).contains("$1"): + dbError("""parameter substitution expects "$1" """) var res = pqprepare(db, stmtName, query.string, int32(nParams), nil) if pqResultStatus(res) != PGRES_COMMAND_OK: dbError(db) return SqlPrepared(stmtName) @@ -172,6 +233,16 @@ iterator instantRows*(db: DbConn, query: SqlQuery, yield (res: res, line: i) pqClear(res) +iterator instantRows*(db: DbConn, stmtName: SqlPrepared, + args: varargs[string, `$`]): InstantRow + {.tags: [FReadDb].} = + ## same as fastRows but returns a handle that can be used to get column text + ## on demand using []. Returned handle is valid only within interator body. + var res = setupQuery(db, stmtName, args) + for i in 0..pqNtuples(res)-1: + yield (res: res, line: i) + pqClear(res) + proc `[]`*(row: InstantRow, col: int32): string {.inline.} = ## returns text for given column of the row $pqgetvalue(row.res, row.line, col) @@ -217,6 +288,11 @@ iterator rows*(db: DbConn, query: SqlQuery, ## same as `fastRows`, but slower and safe. for r in items(getAllRows(db, query, args)): yield r +iterator rows*(db: DbConn, stmtName: SqlPrepared, + args: varargs[string, `$`]): Row {.tags: [FReadDB].} = + ## same as `fastRows`, but slower and safe. + for r in items(getAllRows(db, stmtName, args)): yield r + proc getValue*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): string {.tags: [FReadDB].} = ## executes the query and returns the first column of the first row of the @@ -258,6 +334,19 @@ proc execAffectedRows*(db: DbConn, query: SqlQuery, result = parseBiggestInt($pqcmdTuples(res)) pqclear(res) +proc execAffectedRows*(db: DbConn, stmtName: SqlPrepared, + args: varargs[string, `$`]): int64 {.tags: [ + FReadDB, FWriteDb].} = + ## executes the query (typically "UPDATE") and returns the + ## number of affected rows. + var arr = allocCStringArray(args) + var res = pqexecPrepared(db, stmtName.string, int32(args.len), arr, + nil, nil, 0) + deallocCStringArray(arr) + if pqresultStatus(res) != PGRES_COMMAND_OK: dbError(db) + result = parseBiggestInt($pqcmdTuples(res)) + pqclear(res) + proc close*(db: DbConn) {.tags: [FDb].} = ## closes the database connection. if db != nil: pqfinish(db) @@ -289,3 +378,6 @@ proc setEncoding*(connection: DbConn, encoding: string): bool {. ## sets the encoding of a database connection, returns true for ## success, false for failure. return pqsetClientEncoding(connection, encoding) == 0 + + +# Tests are in ../../tests/untestable/tpostgres. \ No newline at end of file diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 3142952e7..a478b9d65 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -439,7 +439,9 @@ when not defined(JS): proc getTime(): Time = return timec(nil) proc getLocalTime(t: Time): TimeInfo = var a = t - result = tmToTimeInfo(localtime(addr(a))[], true) + let lt = localtime(addr(a)) + assert(not lt.isNil) + result = tmToTimeInfo(lt[], true) # copying is needed anyway to provide reentrancity; thus # the conversion is not expensive diff --git a/tests/generics/tvarseq_caching.nim b/tests/generics/tvarseq_caching.nim new file mode 100644 index 000000000..f617b9335 --- /dev/null +++ b/tests/generics/tvarseq_caching.nim @@ -0,0 +1,48 @@ +discard """ + output: '''@[1, 2, 3] +@[4.0, 5.0, 6.0] +@[1, 2, 3] +@[4.0, 5.0, 6.0] +@[1, 2, 3] +@[4, 5, 6]''' +""" + +# bug #3476 + +proc foo[T]: var seq[T] = + ## Problem! Bug with generics makes every call to this proc generate + ## a new seq[T] instead of retrieving the `items {.global.}` variable. + var items {.global.}: seq[T] + return items + +proc foo2[T]: ptr seq[T] = + ## Workaround! By returning by `ptr` instead of `var` we can get access to + ## the `items` variable, but that means we have to explicitly deref at callsite. + var items {.global.}: seq[T] + return addr items + +proc bar[T]: var seq[int] = + ## Proof. This proc correctly retrieves the `items` variable. Notice the only thing + ## that's changed from `foo` is that it returns `seq[int]` instead of `seq[T]`. + var items {.global.}: seq[int] + return items + + +foo[int]() = @[1, 2, 3] +foo[float]() = @[4.0, 5.0, 6.0] + +foo2[int]()[] = @[1, 2, 3] +foo2[float]()[] = @[4.0, 5.0, 6.0] + +bar[int]() = @[1, 2, 3] +bar[float]() = @[4, 5, 6] + + +echo foo[int]() # prints 'nil' - BUG! +echo foo[float]() # prints 'nil' - BUG! + +echo foo2[int]()[] # prints '@[1, 2, 3]' +echo foo2[float]()[] # prints '@[4.0, 5.0, 6.0]' + +echo bar[int]() # prints '@[1, 2, 3]' +echo bar[float]() # prints '@[4, 5, 6]' diff --git a/tests/testament/categories.nim b/tests/testament/categories.nim index 526ca4d45..762c92792 100644 --- a/tests/testament/categories.nim +++ b/tests/testament/categories.nim @@ -394,6 +394,9 @@ proc processCategory(r: var TResults, cat: Category, options: string, fileGlob: testNimblePackages(r, cat, pfExtraOnly) of "nimble-all": testNimblePackages(r, cat, pfAll) + of "untestable": + # We can't test it because it depends on a third party. + discard # TODO: Move untestable tests to someplace else, i.e. nimble repo. else: for name in os.walkFiles("tests" & DirSep &.? cat.string / fileGlob): testSpec r, makeTest(name, options, cat) |