summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--compiler/seminst.nim4
-rw-r--r--lib/impure/db_postgres.nim112
-rw-r--r--lib/pure/times.nim4
-rw-r--r--tests/generics/tvarseq_caching.nim48
-rw-r--r--tests/testament/categories.nim3
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)