This readme is generated with Nim to Markdown
SQL builder for INSERT
, UPDATE
, SELECT
and DELETE
queries.
The builder will check for NULL values and build a query with them.
After Nim's update to 0.19.0, the check for NULL values was removed
due to the removal of nil
.
This library allows the user, to insert NULL values into queries and ease the creating of queries.
- General
- Main examples
- Utilities
- Examples
import sqlbuilder
import sqlbuilder/select
const tablesWithDeleteMarkerInit = ["table_with_deletemarker"]
include src/sqlbuilder_include
import src/sqlbuilder/sqlbuilderpkg/insert
export insert
import src/sqlbuilder/sqlbuilderpkg/update
export update
import src/sqlbuilder/sqlbuilderpkg/delete
export delete
import src/sqlbuilder/sqlbuilderpkg/utils
export utils
# This enables the softdelete columns for the legacy selector
const tablesWithDeleteMarker = ["tasks", "persons"]
# Notice the include instead of import
include src/sqlbuilderpkg/select
The library supports generating some queries with a macro which can improve the performance due to query being generated on compile time.
After Nim's update to 0.19.0, the check for NULL values was removed
due to the removal of nil
.
You can use NULL
values in different ways. See the examples.
sqlSelect(
table = "tasks",
tableAs = "t",
select = @["id", "name", "description", "created", "updated", "completed"],
where = @["id =", "name != NULL", "description = NULL"],
useDeleteMarker = false
)
check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks AS t WHERE id = ? AND name != NULL AND description = NULL "))
sqlUpdate(
"table",
["name", "age", "info = NULL"],
["id ="],
)
check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = NULL WHERE id = ?"))
The global const dbNullVal
represents a NULL value. Use dbNullVal
in your args if you need to insert/update to a NULL value.
The global proc dbValOrNull()
will check, if it contains a value
or is empty. If it contains a value, the value will be used in the args,
otherwise a NULL value (dbNullVal
) will be used.
dbValOrNull()
accepts all types due to value: auto
.
There are two generators, which can generate the NULL
values for you.
genArgs
does only set a field toNULL
ifdbNullVal
/dbValOrNull()
is passed.genArgsSetNull
sets empty field (""
/c.len() == 0
) toNULL
.
test = sqlInsert("my-table", ["name", "age"])
check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (?, ?)"))
test = sqlInsert("my-table", ["name", "age = NULL"])
check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (?, NULL)"))
let vals = @["thomas", ""]
test = sqlInsert("my-table", ["name", "age"], vals)
check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (?, NULL)"))
test = sqlInsertMacro("my-table", ["name", "age = NULL"])
check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (?, NULL)"))
let a = genArgs("[email protected]", dbNullVal)
exec(db, sqlInsert("myTable", ["email", "age"], a.query), a.args)
# ==> INSERT INTO myTable (email) VALUES (?)
let q = sqlUpdate(
"table",
["name", "age", "info"],
["id ="],
)
check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = ? WHERE id = ?"))
let q = sqlUpdate(
"table",
["name", "age", "info = NULL"],
["id ="],
)
check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = NULL WHERE id = ?"))
let q = sqlUpdate(
"table",
["name = NULL", "age", "info = NULL"],
["id =", "epoch >", "parent IS NULL", "name IS NOT NULL", "age != 22", "age !="],
)
check querycompare(q, sql("UPDATE table SET name = NULL, age = ?, info = NULL WHERE id = ? AND epoch > ? AND parent IS NULL AND name IS NOT NULL AND age != 22 AND age != ?"))
let q = sqlUpdate(
"table",
["parents = ARRAY_APPEND(id, ?)", "age = ARRAY_REMOVE(id, ?)", "info = NULL"],
["last_name NOT IN ('Anderson', 'Johnson', 'Smith')"],
)
check querycompare(q, sql("UPDATE table SET parents = ARRAY_APPEND(id, ?), age = ARRAY_REMOVE(id, ?), info = NULL WHERE last_name NOT IN ('Anderson', 'Johnson', 'Smith')"))
# sqlUpdate or sqlUpdateMacro
let q = sqlUpdate(
"table",
["name = NULL", "age", "info = NULL"],
["id =", "epoch >", "parent IS NULL", "name IS NOT NULL", "age != 22", "age !="],
)
# ==> UPDATE table SET name = NULL, age = ?, info = NULL WHERE id = ? AND epoch > ? AND parent IS NULL AND name IS NOT NULL AND age != 22 AND age != ?
let a2 = genArgsSetNull("hje", "")
let q = sqlUpdate("my-table", ["name", "age"], ["id"], a2.query)
check querycompare(q, sql("UPDATE my-table SET name = ?, age = NULL WHERE id = ?"))
test = sqlSelect(
table = "tasks",
tableAs = "t",
select = @["id", "name"],
where = @["id ="],
joinargs = @[(table: "projects", tableAs: "", on: @["projects.id = t.project_id", "projects.status = 1"])],
jointype = INNER
)
check querycompare(test, sql("SELECT id, name FROM tasks AS t INNER JOIN projects ON (projects.id = t.project_id AND projects.status = 1) WHERE id = ? "))
test = sqlSelect(
table = "tasksitems",
tableAs = "tasks",
select = @[
"tasks.id",
"tasks.name",
"tasks.status",
"tasks.created",
"his.id",
"his.name",
"his.status",
"his.created",
"projects.id",
"projects.name",
"person.id",
"person.name",
"person.email"
],
where = @[
"projects.id =",
"tasks.status >"
],
joinargs = @[
(table: "history", tableAs: "his", on: @["his.id = tasks.hid", "his.status = 1"]),
(table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"]),
(table: "person", tableAs: "", on: @["person.id = tasks.person_id"])
],
whereInField = "tasks.id",
whereInValue = @["1", "2", "3"],
customSQL = "ORDER BY tasks.created DESC",
tablesWithDeleteMarker = tableWithDeleteMarker
)
check querycompare(test, (sql("""
SELECT
tasks.id,
tasks.name,
tasks.status,
tasks.created,
his.id,
his.name,
his.status,
his.created,
projects.id,
projects.name,
person.id,
person.name,
person.email
FROM
tasksitems AS tasks
LEFT JOIN history AS his ON
(his.id = tasks.hid AND his.status = 1 AND his.is_deleted IS NULL)
LEFT JOIN projects ON
(projects.id = tasks.project_id AND projects.status = 1)
LEFT JOIN person ON
(person.id = tasks.person_id)
WHERE
projects.id = ?
AND tasks.status > ?
AND tasks.id in (1,2,3)
AND tasks.is_deleted IS NULL
ORDER BY
tasks.created DESC
""")))
The legacy SELECT builder is deprecated and will be removed in the future. It is commented out in the source code, and a converter has been added to convert the legacy query to the new builder.
That means, you don't have to worry, but you should definitely convert your legacy queries to the new builder.
# Legacy builder
test = sqlSelect("tasks AS t", ["t.id", "t.name", "p.id"], ["project AS p ON p.id = t.project_id"], ["t.id ="], "", "", "")
check querycompare(test, sql("""
SELECT
t.id,
t.name,
p.id
FROM
tasks AS t
LEFT JOIN project AS p ON
(p.id = t.project_id)
WHERE
t.id = ?
"""))
Required if NULL values could be expected
let a = genArgs("[email protected]", 20, "John")
exec(db, sqlUpdate("myTable", ["email", "age"], ["name"], a.query), a.args)
# ==> string, int
# ==> UPDATE myTable SET email = ?, age = ? WHERE name = ?
exec(db, sqlUpdate("myTable", ["email", "age"], ["name"]), "[email protected]", 20, "John")
# ==> string, int
# ==> UPDATE myTable SET email = ?, age = ? WHERE name = ?
let a = genArgsSetNull("[email protected]", "", "John")
exec(db, sqlUpdate("myTable", ["email", "age"], ["name"], a.query), a.args)
# ==> string, NULL
# ==> UPDATE myTable SET email = ?, age = NULL WHERE name = ?
let a = genArgs("", 20, "John")
exec(db, sqlUpdate("myTable", ["email", "age"], ["name"], a.query), a.args)
# ==> NULL, int
# ==> UPDATE myTable SET email = NULL, age = ? WHERE name = ?
a = genArgs("[email protected]", dbNullVal, "John")
exec(db, sqlUpdate("myTable", ["email", "age"], ["name"], a.query), a.args)
# ==> string, NULL
# ==> UPDATE myTable SET email = ?, age = NULL WHERE name = ?
An empty string, "", will be inserted into the database as NULL.
Empty string cannot be used for an INTEGER column. You therefore
need to use the dbValOrNull()
or dbNullVal
for int-values
.
This is due to, that the library does not know you DB-architecture, so it is your responsibility to respect the columns.
a = genArgs("[email protected]", "", "John")
exec(db, sqlUpdate("myTable", ["email", "age"], ["name"], a.query), a.args)
# ==> string, ERROR
# ==> UPDATE myTable SET email = ?, age = ? WHERE name = ?
# ==> To insert a NULL into a int-field, it is required to use dbValOrNull()
# or dbNullVal, it is only possible to pass and empty string.
let cc = ""
a = genArgs(dbValOrNull(cc), dbValOrNull(cc), "John")
exec(db, sqlUpdate("myTable", ["email", "age"], ["name"], a.query), a.args)
# ==> NULL, NULL
# ==> UPDATE myTable SET email = NULL, age = NULL WHERE name = ?
a = genArgs(dbValOrNull(var1), dbValOrNull(var2), "John")
exec(db, sqlUpdate("myTable", ["email", "age"], ["name"], a.query), a.args)
# ==> AUTO or NULL, AUTO or NULL, string
Select which columns to include.
Lets say, that you are importing data from a spreadsheet with static columns, and you need to update your DB with the new values.
If the spreadsheet contains an empty field, you can update your DB with the NULL value. But sometimes the spreadsheet only contains 5 out of the 10 static columns. Now you don't know the values of the last 5 columns, which will result in updating the DB with NULL values.
The template genArgsColumns()
allows you to use the same query, but selecting
only the columns which shall be updated. When importing your spreadsheet, check
if the column exists (bool), and pass that as the use: bool
param. If
the column does not exists, it will be skipped in the query.
let (s, a) = genArgsColumns((true, "name", "Thomas"), (true, "age", 30), (false, "nim", "never"))
# We are using the column `name` and `age` and ignoring the column `nim`.
echo $a.args
# ==> Args: @["Thomas", "30"]
let a1 = sqlInsert("my-table", s, a.query)
# ==> INSERT INTO my-table (name, age) VALUES (?, ?)
let a2 = sqlDelete("my-table", s, a.query)
# ==> DELETE FROM my-table WHERE name = ? AND age = ?
let (s, a) = genArgsColumns((true, "name", "Thomas"), (true, "age", 30), (false, "nim", ""), (true, "", "154"))
# We are using the column `name` and `age` and ignoring the column `nim`. We
# are using the value `154` as our identifier, therefor the column is not
# specified
echo $a.args
# ==> Args: @["Thomas", "30", "154"]
let a3 = sqlUpdate("my-table", s, ["id"], a.query)
# ==> UPDATE my-table SET name = ?, age = ? WHERE id = ?
let a4 = sqlSelect("my-table", s, [""], ["id ="], "", "", "", a.query)
# ==> SELECT name, age FROM my-table WHERE id = ?
These are procs to catch DB errors and return a default value to move on. This should only be used if:
- It is not critical data
- You can live with a default value in case of an error
- You have no other way to catch the error
- You are to lazy to write the try-except procs yourself
!! These are not available if you use external libraries, e.g. waterpark
,
!! since they rely on defaultDbConn
.
import sqlbuilder/query_calls
proc getValueTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): string =
proc getAllRowsTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): seq[Row] =
proc getRowTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): Row =
proc tryExecTry*(db: DbConn, query: SqlQuery; args: varargs[string, `$`]): bool =
proc execTry*(db: DbConn, query: SqlQuery; args: varargs[string, `$`]) =
proc execAffectedRowsTry*(db: DbConn, query: SqlQuery; args: varargs[string, `$`]): int64 =
iterator fastRowsTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): Row =
The totypes
module contains procs to convert the result to types.
type
Person = ref object
id: int
name: string
age: int
ident: string
is_nimmer: bool
let
columns = @["name","id","ident"]
val = db.getRow(sql("SELECT " & columns.join(",") & " FROM my_table WHERE id = 1"))
res = sqlToType(Person, columns, val)
See the test files in tests/
for more examples.
Inspiration for builder: Nim Forum