├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── sqlbuilder.nimble ├── src ├── config.nims ├── sqlbuilder.nim ├── sqlbuilder_include.nim └── sqlbuilderpkg │ ├── delete.nim │ ├── insert.nim │ ├── query_calls.nim │ ├── select.nim │ ├── select_legacy.nim │ ├── totypes.nim │ ├── update.nim │ ├── utils.nim │ └── utils_private.nim └── tests ├── config.nims ├── create_db.nim ├── custom_args └── test_args.nim ├── delete └── test_delete.nim ├── importpackage ├── test_import1.nim ├── test_import2.nim ├── test_sql_import_with_deletemarkers.nim └── test_sql_import_with_deletemarkers2.nim ├── insert ├── test_insert.nim └── test_insert_db.nim ├── legacy_convert ├── test_legacy.nim ├── test_legacy_with_softdelete.nim └── test_legacy_with_softdelete2.nim ├── query_calls └── test_query_calls.nim ├── select ├── test_select.nim ├── test_select_arrays.nim ├── test_select_const.nim ├── test_select_const_deletemarker.nim ├── test_select_const_where.nim ├── test_select_deletemarker.nim └── test_select_is.nim ├── totypes └── test_result_to_types.nim └── update ├── test_update.nim └── test_update_arrays.nim /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build sqlbuilder 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | nim: 17 | - '1.6.14' 18 | - 'stable' 19 | name: Nim ${{ matrix.nim }} sample 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Setup nim 23 | uses: jiro4989/setup-nim-action@v1 24 | with: 25 | nim-version: ${{ matrix.nim }} 26 | - run: nimble test -Y -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src/sqlbuilder 2 | .vscode 3 | 4 | tests/db_general.db 5 | tests/mytest.db 6 | tests/test_string 7 | 8 | !tests/t*.nim 9 | tests/*/* 10 | !tests/*/t*.nim 11 | 12 | !tests/config.nims 13 | !tests/create_db.nim 14 | 15 | tests/test_string/test_dead.nim -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Thomas T. Jarløv 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *This readme is generated with [Nim to Markdown](https://github.com/ThomasTJdev/nimtomd)* 2 | 3 | 4 | # SQL builder 5 | 6 | SQL builder for ``INSERT``, ``UPDATE``, ``SELECT`` and ``DELETE`` queries. 7 | The builder will check for NULL values and build a query with them. 8 | 9 | After Nim's update to 0.19.0, the check for NULL values was removed 10 | due to the removal of ``nil``. 11 | 12 | This library allows the user, to insert NULL values into queries and 13 | ease the creating of queries. 14 | 15 | 16 | # TOC 17 | 18 | - General 19 | - [Importing and use](#importing) 20 | - [Macro generated queries](#macro-generated-queries) 21 | - [NULL values](#null-values) 22 | - Main examples 23 | - [Examples (INSERT)](#examples-insert) 24 | - [Examples (UPDATE)](#examples-update) 25 | - [Examples (SELECT)](#examples-select) 26 | - Utilities 27 | - [Custom args](#custom-args) 28 | - [Dynamic selection of columns](#dynamic-selection-of-columns) 29 | - [Query calls for the lazy](#query-calls-for-the-lazy) 30 | - [Convert result to types](#convert-result-to-types) 31 | - [Examples](#examples) 32 | 33 | 34 | # Importing 35 | 36 | ## Import all 37 | ```nim 38 | import sqlbuilder 39 | ``` 40 | 41 | ## Import only the SELECT builder 42 | ```nim 43 | import sqlbuilder/select 44 | ``` 45 | 46 | ## Import and set global soft delete marker 47 | ```nim 48 | const tablesWithDeleteMarkerInit = ["table_with_deletemarker"] 49 | include src/sqlbuilder_include 50 | ``` 51 | 52 | ## Import all but with legacy softdelete fix 53 | ```nim 54 | import src/sqlbuilder/sqlbuilderpkg/insert 55 | export insert 56 | 57 | import src/sqlbuilder/sqlbuilderpkg/update 58 | export update 59 | 60 | import src/sqlbuilder/sqlbuilderpkg/delete 61 | export delete 62 | 63 | import src/sqlbuilder/sqlbuilderpkg/utils 64 | export utils 65 | 66 | # This enables the softdelete columns for the legacy selector 67 | const tablesWithDeleteMarker = ["tasks", "persons"] 68 | # Notice the include instead of import 69 | include src/sqlbuilderpkg/select 70 | ``` 71 | 72 | 73 | 74 | # Macro generated queries 75 | 76 | The library supports generating some queries with a macro which can improve the 77 | performance due to query being generated on compile time. 78 | 79 | 80 | 81 | # NULL values 82 | 83 | After Nim's update to 0.19.0, the check for NULL values was removed 84 | due to the removal of `nil`. 85 | 86 | You can use `NULL` values in different ways. See the examples. 87 | 88 | ## Inline in query 89 | 90 | ```nim 91 | sqlSelect( 92 | table = "tasks", 93 | tableAs = "t", 94 | select = @["id", "name", "description", "created", "updated", "completed"], 95 | where = @["id =", "name != NULL", "description = NULL"], 96 | useDeleteMarker = false 97 | ) 98 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks AS t WHERE id = ? AND name != NULL AND description = NULL ")) 99 | ``` 100 | 101 | ```nim 102 | sqlUpdate( 103 | "table", 104 | ["name", "age", "info = NULL"], 105 | ["id ="], 106 | ) 107 | check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = NULL WHERE id = ?")) 108 | ``` 109 | 110 | ## Custom args 111 | 112 | ### A NULL value 113 | 114 | The global ``const dbNullVal`` represents a NULL value. Use ``dbNullVal`` 115 | in your args if you need to insert/update to a NULL value. 116 | 117 | ### Insert value or NULL 118 | 119 | The global ``proc dbValOrNull()`` will check, if it contains a value 120 | or is empty. If it contains a value, the value will be used in the args, 121 | otherwise a NULL value (``dbNullVal``) will be used. 122 | 123 | ``dbValOrNull()`` accepts all types due to `value: auto`. 124 | 125 | ### Auto NULL-values 126 | 127 | There are two generators, which can generate the `NULL` values for you. 128 | 129 | * `genArgs` does only set a field to `NULL` if `dbNullVal`/`dbValOrNull()` is passed. 130 | * `genArgsSetNull` sets empty field (`""` / `c.len() == 0`) to `NULL`. 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | # Examples (INSERT) 140 | 141 | ## Insert default 142 | 143 | ```nim 144 | test = sqlInsert("my-table", ["name", "age"]) 145 | check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (?, ?)")) 146 | ``` 147 | 148 | ```nim 149 | test = sqlInsert("my-table", ["name", "age = NULL"]) 150 | check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (?, NULL)")) 151 | ``` 152 | 153 | ```nim 154 | let vals = @["thomas", ""] 155 | test = sqlInsert("my-table", ["name", "age"], vals) 156 | check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (?, NULL)")) 157 | ``` 158 | 159 | ```nim 160 | test = sqlInsertMacro("my-table", ["name", "age = NULL"]) 161 | check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (?, NULL)")) 162 | ``` 163 | 164 | ```nim 165 | let a = genArgs("em@em.com", dbNullVal) 166 | exec(db, sqlInsert("myTable", ["email", "age"], a.query), a.args) 167 | # ==> INSERT INTO myTable (email) VALUES (?) 168 | ``` 169 | 170 | 171 | # Examples (UPDATE) 172 | 173 | ```nim 174 | let q = sqlUpdate( 175 | "table", 176 | ["name", "age", "info"], 177 | ["id ="], 178 | ) 179 | check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = ? WHERE id = ?")) 180 | ``` 181 | 182 | ```nim 183 | let q = sqlUpdate( 184 | "table", 185 | ["name", "age", "info = NULL"], 186 | ["id ="], 187 | ) 188 | check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = NULL WHERE id = ?")) 189 | ``` 190 | 191 | ```nim 192 | let q = sqlUpdate( 193 | "table", 194 | ["name = NULL", "age", "info = NULL"], 195 | ["id =", "epoch >", "parent IS NULL", "name IS NOT NULL", "age != 22", "age !="], 196 | ) 197 | 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 != ?")) 198 | ``` 199 | 200 | ```nim 201 | let q = sqlUpdate( 202 | "table", 203 | ["parents = ARRAY_APPEND(id, ?)", "age = ARRAY_REMOVE(id, ?)", "info = NULL"], 204 | ["last_name NOT IN ('Anderson', 'Johnson', 'Smith')"], 205 | ) 206 | 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')")) 207 | ``` 208 | 209 | ```nim 210 | # sqlUpdate or sqlUpdateMacro 211 | let q = sqlUpdate( 212 | "table", 213 | ["name = NULL", "age", "info = NULL"], 214 | ["id =", "epoch >", "parent IS NULL", "name IS NOT NULL", "age != 22", "age !="], 215 | ) 216 | # ==> 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 != ? 217 | ``` 218 | 219 | ```nim 220 | let a2 = genArgsSetNull("hje", "") 221 | let q = sqlUpdate("my-table", ["name", "age"], ["id"], a2.query) 222 | check querycompare(q, sql("UPDATE my-table SET name = ?, age = NULL WHERE id = ?")) 223 | ``` 224 | 225 | 226 | 227 | # Examples (SELECT) 228 | 229 | ## Example on builder 230 | ```nim 231 | test = sqlSelect( 232 | table = "tasks", 233 | tableAs = "t", 234 | select = @["id", "name"], 235 | where = @["id ="], 236 | joinargs = @[(table: "projects", tableAs: "", on: @["projects.id = t.project_id", "projects.status = 1"])], 237 | jointype = INNER 238 | ) 239 | 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 = ? ")) 240 | ``` 241 | 242 | ```nim 243 | test = sqlSelect( 244 | table = "tasksitems", 245 | tableAs = "tasks", 246 | select = @[ 247 | "tasks.id", 248 | "tasks.name", 249 | "tasks.status", 250 | "tasks.created", 251 | "his.id", 252 | "his.name", 253 | "his.status", 254 | "his.created", 255 | "projects.id", 256 | "projects.name", 257 | "person.id", 258 | "person.name", 259 | "person.email" 260 | ], 261 | where = @[ 262 | "projects.id =", 263 | "tasks.status >" 264 | ], 265 | joinargs = @[ 266 | (table: "history", tableAs: "his", on: @["his.id = tasks.hid", "his.status = 1"]), 267 | (table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"]), 268 | (table: "person", tableAs: "", on: @["person.id = tasks.person_id"]) 269 | ], 270 | whereInField = "tasks.id", 271 | whereInValue = @["1", "2", "3"], 272 | customSQL = "ORDER BY tasks.created DESC", 273 | tablesWithDeleteMarker = tableWithDeleteMarker 274 | ) 275 | check querycompare(test, (sql(""" 276 | SELECT 277 | tasks.id, 278 | tasks.name, 279 | tasks.status, 280 | tasks.created, 281 | his.id, 282 | his.name, 283 | his.status, 284 | his.created, 285 | projects.id, 286 | projects.name, 287 | person.id, 288 | person.name, 289 | person.email 290 | FROM 291 | tasksitems AS tasks 292 | LEFT JOIN history AS his ON 293 | (his.id = tasks.hid AND his.status = 1 AND his.is_deleted IS NULL) 294 | LEFT JOIN projects ON 295 | (projects.id = tasks.project_id AND projects.status = 1) 296 | LEFT JOIN person ON 297 | (person.id = tasks.person_id) 298 | WHERE 299 | projects.id = ? 300 | AND tasks.status > ? 301 | AND tasks.id in (1,2,3) 302 | AND tasks.is_deleted IS NULL 303 | ORDER BY 304 | tasks.created DESC 305 | """))) 306 | ``` 307 | 308 | 309 | ## Convert legacy 310 | 311 | The legacy SELECT builder is deprecated and will be removed in the future. It 312 | is commented out in the source code, and a converter has been added to convert 313 | the legacy query to the new builder. 314 | 315 | That means, you don't have to worry, but you should definitely convert your 316 | legacy queries to the new builder. 317 | 318 | ```nim 319 | # Legacy builder 320 | test = sqlSelect("tasks AS t", ["t.id", "t.name", "p.id"], ["project AS p ON p.id = t.project_id"], ["t.id ="], "", "", "") 321 | 322 | check querycompare(test, sql(""" 323 | SELECT 324 | t.id, 325 | t.name, 326 | p.id 327 | FROM 328 | tasks AS t 329 | LEFT JOIN project AS p ON 330 | (p.id = t.project_id) 331 | WHERE 332 | t.id = ? 333 | """)) 334 | ``` 335 | 336 | 337 | 338 | # Custom args 339 | ## Update string & int 340 | 341 | ### Version 1 342 | *Required if NULL values could be expected* 343 | ```nim 344 | let a = genArgs("em@em.com", 20, "John") 345 | exec(db, sqlUpdate("myTable", ["email", "age"], ["name"], a.query), a.args) 346 | # ==> string, int 347 | # ==> UPDATE myTable SET email = ?, age = ? WHERE name = ? 348 | ``` 349 | 350 | 351 | ### Version 2 352 | ```nim 353 | exec(db, sqlUpdate("myTable", ["email", "age"], ["name"]), "em@em.com", 20, "John") 354 | # ==> string, int 355 | # ==> UPDATE myTable SET email = ?, age = ? WHERE name = ? 356 | ``` 357 | 358 | 359 | ### Version 3 360 | ```nim 361 | let a = genArgsSetNull("em@em.com", "", "John") 362 | exec(db, sqlUpdate("myTable", ["email", "age"], ["name"], a.query), a.args) 363 | # ==> string, NULL 364 | # ==> UPDATE myTable SET email = ?, age = NULL WHERE name = ? 365 | ``` 366 | 367 | 368 | ## Update NULL & int 369 | 370 | ```nim 371 | let a = genArgs("", 20, "John") 372 | exec(db, sqlUpdate("myTable", ["email", "age"], ["name"], a.query), a.args) 373 | # ==> NULL, int 374 | # ==> UPDATE myTable SET email = NULL, age = ? WHERE name = ? 375 | ``` 376 | 377 | 378 | ## Update string & NULL 379 | 380 | ```nim 381 | a = genArgs("aa@aa.aa", dbNullVal, "John") 382 | exec(db, sqlUpdate("myTable", ["email", "age"], ["name"], a.query), a.args) 383 | # ==> string, NULL 384 | # ==> UPDATE myTable SET email = ?, age = NULL WHERE name = ? 385 | ``` 386 | 387 | 388 | ## Error: Update string & NULL into an integer column 389 | 390 | An empty string, "", will be inserted into the database as NULL. 391 | Empty string cannot be used for an INTEGER column. You therefore 392 | need to use the ``dbValOrNull()`` or ``dbNullVal`` for ``int-values``. 393 | 394 | This is due to, that the library does not know you DB-architecture, so it 395 | is your responsibility to respect the columns. 396 | 397 | ```nim 398 | a = genArgs("aa@aa.aa", "", "John") 399 | exec(db, sqlUpdate("myTable", ["email", "age"], ["name"], a.query), a.args) 400 | # ==> string, ERROR 401 | # ==> UPDATE myTable SET email = ?, age = ? WHERE name = ? 402 | # ==> To insert a NULL into a int-field, it is required to use dbValOrNull() 403 | # or dbNullVal, it is only possible to pass and empty string. 404 | ``` 405 | 406 | 407 | ## Update NULL & NULL 408 | 409 | ```nim 410 | let cc = "" 411 | a = genArgs(dbValOrNull(cc), dbValOrNull(cc), "John") 412 | exec(db, sqlUpdate("myTable", ["email", "age"], ["name"], a.query), a.args) 413 | # ==> NULL, NULL 414 | # ==> UPDATE myTable SET email = NULL, age = NULL WHERE name = ? 415 | ``` 416 | 417 | 418 | 419 | ## Update unknow value - maybe NULL 420 | 421 | ```nim 422 | a = genArgs(dbValOrNull(var1), dbValOrNull(var2), "John") 423 | exec(db, sqlUpdate("myTable", ["email", "age"], ["name"], a.query), a.args) 424 | # ==> AUTO or NULL, AUTO or NULL, string 425 | ``` 426 | 427 | 428 | 429 | # Dynamic selection of columns 430 | Select which columns to include. 431 | 432 | Lets say, that you are importing data from a spreadsheet with static 433 | columns, and you need to update your DB with the new values. 434 | 435 | If the spreadsheet contains an empty field, you can update your DB with the 436 | NULL value. But sometimes the spreadsheet only contains 5 out of the 10 437 | static columns. Now you don't know the values of the last 5 columns, 438 | which will result in updating the DB with NULL values. 439 | 440 | The template `genArgsColumns()` allows you to use the same query, but selecting 441 | only the columns which shall be updated. When importing your spreadsheet, check 442 | if the column exists (bool), and pass that as the `use: bool` param. If 443 | the column does not exists, it will be skipped in the query. 444 | 445 | ## Insert & Delete 446 | ```nim 447 | let (s, a) = genArgsColumns((true, "name", "Thomas"), (true, "age", 30), (false, "nim", "never")) 448 | # We are using the column `name` and `age` and ignoring the column `nim`. 449 | 450 | echo $a.args 451 | # ==> Args: @["Thomas", "30"] 452 | 453 | let a1 = sqlInsert("my-table", s, a.query) 454 | # ==> INSERT INTO my-table (name, age) VALUES (?, ?) 455 | 456 | let a2 = sqlDelete("my-table", s, a.query) 457 | # ==> DELETE FROM my-table WHERE name = ? AND age = ? 458 | ``` 459 | 460 | ## Update & Select 461 | ```nim 462 | let (s, a) = genArgsColumns((true, "name", "Thomas"), (true, "age", 30), (false, "nim", ""), (true, "", "154")) 463 | # We are using the column `name` and `age` and ignoring the column `nim`. We 464 | # are using the value `154` as our identifier, therefor the column is not 465 | # specified 466 | 467 | echo $a.args 468 | # ==> Args: @["Thomas", "30", "154"] 469 | 470 | let a3 = sqlUpdate("my-table", s, ["id"], a.query) 471 | # ==> UPDATE my-table SET name = ?, age = ? WHERE id = ? 472 | 473 | let a4 = sqlSelect("my-table", s, [""], ["id ="], "", "", "", a.query) 474 | # ==> SELECT name, age FROM my-table WHERE id = ? 475 | ``` 476 | 477 | 478 | 479 | # Query calls for the lazy 480 | 481 | These are procs to catch DB errors and return a default value to move on. 482 | This should only be used if: 483 | - It is not critical data 484 | - You can live with a default value in case of an error 485 | - You have no other way to catch the error 486 | - You are to lazy to write the try-except procs yourself 487 | 488 | !! These are not available if you use external libraries, e.g. `waterpark`, 489 | !! since they rely on default`DbConn`. 490 | 491 | ## Import 492 | 493 | ```nim 494 | import sqlbuilder/query_calls 495 | ``` 496 | 497 | ## Procs 498 | 499 | ```nim 500 | proc getValueTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): string = 501 | ``` 502 | ____ 503 | 504 | 505 | ```nim 506 | proc getAllRowsTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): seq[Row] = 507 | ``` 508 | 509 | ____ 510 | 511 | 512 | ```nim 513 | proc getRowTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): Row = 514 | ``` 515 | 516 | ____ 517 | 518 | 519 | ```nim 520 | proc tryExecTry*(db: DbConn, query: SqlQuery; args: varargs[string, `$`]): bool = 521 | ``` 522 | 523 | ____ 524 | 525 | 526 | ```nim 527 | proc execTry*(db: DbConn, query: SqlQuery; args: varargs[string, `$`]) = 528 | ``` 529 | 530 | ____ 531 | 532 | 533 | ```nim 534 | proc execAffectedRowsTry*(db: DbConn, query: SqlQuery; args: varargs[string, `$`]): int64 = 535 | ``` 536 | 537 | ____ 538 | 539 | ```nim 540 | iterator fastRowsTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): Row = 541 | ``` 542 | 543 | ____ 544 | 545 | 546 | # Convert result to types 547 | 548 | The `totypes` module contains procs to convert the result to types. 549 | 550 | ```nim 551 | type 552 | Person = ref object 553 | id: int 554 | username: string 555 | age: int 556 | secretIdent: string 557 | is_nimmer: bool 558 | 559 | let 560 | columns = @["name AS username","id","ident AS secretIdent"] 561 | val = db.getRow(sql("SELECT " & columns.join(",") & " FROM my_table WHERE id = 1")) 562 | res = sqlToTypeAs(Person, columns, val) 563 | ``` 564 | 565 | ```nim 566 | type 567 | Person = ref object 568 | id: int 569 | name: string 570 | age: int 571 | ident: string 572 | is_nimmer: bool 573 | 574 | let 575 | columns = @["name","id","ident"] 576 | val = db.getRow(sql("SELECT " & columns.join(",") & " FROM my_table WHERE id = 1")) 577 | res = sqlToType(Person, columns, val) 578 | ``` 579 | 580 | 581 | # Examples 582 | 583 | See the test files in `tests/` for more examples. 584 | 585 | 586 | 587 | # Credit 588 | Inspiration for builder: [Nim Forum](https://github.com/nim-lang/nimforum) 589 | -------------------------------------------------------------------------------- /sqlbuilder.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "1.1.3" 4 | author = "ThomasTJdev" 5 | description = "SQL builder" 6 | license = "MIT" 7 | srcDir = "src" 8 | 9 | 10 | # Dependencies 11 | 12 | requires "nim >= 0.20.2" 13 | when NimMajor >= 2: 14 | requires "db_connector >= 0.1.0" 15 | 16 | 17 | 18 | 19 | 20 | proc runLegacy() = 21 | exec "nim c -d:dev -r tests/legacy_convert/test_legacy.nim" 22 | exec "nim c -d:dev -r tests/legacy_convert/test_legacy_with_softdelete.nim" 23 | exec "nim c -d:dev -r tests/legacy_convert/test_legacy_with_softdelete2.nim" 24 | 25 | task testlegacy, "Test legacy": 26 | runLegacy() 27 | 28 | 29 | proc runSelect() = 30 | exec "nim c -d:dev -r tests/select/test_select.nim" 31 | exec "nim c -d:dev -r tests/select/test_select_arrays.nim" 32 | exec "nim c -d:dev -r tests/select/test_select_is.nim" 33 | exec "nim c -d:dev -r tests/select/test_select_deletemarker.nim" 34 | exec "nim c -d:dev -r tests/select/test_select_const.nim" 35 | exec "nim c -d:dev -r tests/select/test_select_const_deletemarker.nim" 36 | exec "nim c -d:dev -r tests/select/test_select_const_where.nim" 37 | 38 | task testselect, "Test select statement": 39 | runSelect() 40 | 41 | 42 | proc runInsert() = 43 | exec "nim c -d:dev -r tests/insert/test_insert_db.nim" 44 | exec "nim c -d:dev -r tests/insert/test_insert.nim" 45 | 46 | task testinsert, "Test insert statement": 47 | runInsert() 48 | 49 | 50 | proc runUpdate() = 51 | exec "nim c -d:dev -r tests/update/test_update.nim" 52 | exec "nim c -d:dev -r tests/update/test_update_arrays.nim" 53 | 54 | task testupdate, "Test update statement": 55 | runUpdate() 56 | 57 | 58 | proc runDelete() = 59 | exec "nim c -d:dev -r tests/delete/test_delete.nim" 60 | 61 | task testdelete, "Test delete statement": 62 | runDelete() 63 | 64 | 65 | proc runQueryCalls() = 66 | exec "nim c -d:dev -r tests/query_calls/test_query_calls.nim" 67 | 68 | task testquerycalls, "Test query calls": 69 | runQueryCalls() 70 | 71 | 72 | proc runToTypes() = 73 | exec "nim c -d:dev -r tests/totypes/test_result_to_types.nim" 74 | 75 | task testresulttotypes, "Test result to types": 76 | runToTypes() 77 | 78 | 79 | proc runArgs() = 80 | exec "nim c -d:dev -r tests/custom_args/test_args.nim" 81 | 82 | task testargs, "Test args": 83 | runArgs() 84 | 85 | 86 | proc runImport() = 87 | exec "nim c -d:dev -r tests/importpackage/test_import1.nim" 88 | exec "nim c -d:dev -r tests/importpackage/test_import2.nim" 89 | 90 | task testimport, "Test import": 91 | runImport() 92 | 93 | 94 | task test, "Test": 95 | runLegacy() 96 | runSelect() 97 | runInsert() 98 | runUpdate() 99 | runDelete() 100 | runQueryCalls() 101 | runToTypes() 102 | runArgs() 103 | runImport() -------------------------------------------------------------------------------- /src/config.nims: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasTJdev/nim_sqlbuilder/3ba3bb2d51714df31990987a9554ed106765be6b/src/config.nims -------------------------------------------------------------------------------- /src/sqlbuilder.nim: -------------------------------------------------------------------------------- 1 | # Copyright 2019 - Thomas T. Jarløv 2 | 3 | 4 | 5 | 6 | 7 | import sqlbuilderpkg/insert 8 | export insert 9 | 10 | import sqlbuilderpkg/update 11 | export update 12 | 13 | import sqlbuilderpkg/delete 14 | export delete 15 | 16 | import sqlbuilderpkg/select 17 | export select 18 | 19 | # import sqlbuilderpkg/select_legacy 20 | # export select_legacy 21 | 22 | import sqlbuilderpkg/totypes 23 | export totypes 24 | 25 | import sqlbuilderpkg/utils 26 | export utils 27 | 28 | 29 | 30 | #[ 31 | when isMainModule: 32 | from times import epochTime 33 | let a = epochTime() 34 | for i in countup(0,100000): 35 | let a = sqlSelectMacro("myTable", ["id", "name", "j"], [""], ["email =", "name ="], "", "", "") 36 | 37 | let b = epochTime() 38 | 39 | let c = epochTime() 40 | for i in countup(0,100000): 41 | let a = sqlSelect("myTable", ["id", "name", "j"], [""], ["email =", "name ="], "", "", "") 42 | 43 | let d = epochTime() 44 | 45 | echo "Select:" 46 | echo "Macro: " & $(b-a) 47 | echo "Normal: " & $(d-c) 48 | echo "Diff: " & $((d-c) - (b-a)) 49 | 50 | echo "\n\n" 51 | 52 | 53 | let im1 = epochTime() 54 | for i in countup(0,100000): 55 | let a = sqlInsertMacro("myTable", ["id", "name", "j"]) 56 | 57 | let im2 = epochTime() 58 | 59 | let ip1 = epochTime() 60 | for i in countup(0,100000): 61 | let a = sqlInsert("myTable", ["id", "name", "j"]) 62 | 63 | let ip2 = epochTime() 64 | 65 | echo "Update:" 66 | echo "Macro: " & $(im2-im1) 67 | echo "Normal: " & $(ip2-ip1) 68 | echo "Diff: " & $((ip2-ip1) - (im2-im1)) 69 | 70 | echo "\n\n" 71 | 72 | 73 | let um1 = epochTime() 74 | for i in countup(0,100000): 75 | let a = sqlUpdateMacro("myTable", ["id", "name", "j"], ["id", "session"]) 76 | 77 | let um2 = epochTime() 78 | 79 | let up1 = epochTime() 80 | for i in countup(0,100000): 81 | let a = sqlUpdate("myTable", ["id", "name", "j"], ["id", "session"]) 82 | 83 | let up2 = epochTime() 84 | 85 | echo "Update:" 86 | echo "Macro: " & $(um2-um1) 87 | echo "Normal: " & $(up2-up1) 88 | echo "Diff: " & $((up2-up1) - (um2-um1)) 89 | 90 | echo "\n\n" 91 | 92 | 93 | let dm1 = epochTime() 94 | for i in countup(0,100000): 95 | let a = sqlDeleteMacro("myTable", ["id", "name", "j"]) 96 | 97 | let dm2 = epochTime() 98 | 99 | let dp1 = epochTime() 100 | for i in countup(0,100000): 101 | let a = sqlDelete("myTable", ["id", "name", "j"]) 102 | 103 | let dp2 = epochTime() 104 | 105 | echo "Delete:" 106 | echo "Macro: " & $(dm2-dm1) 107 | echo "Normal: " & $(dp2-dp1) 108 | echo "Diff: " & $((dp2-dp1) - (dm2-dm1)) 109 | 110 | ]# -------------------------------------------------------------------------------- /src/sqlbuilder_include.nim: -------------------------------------------------------------------------------- 1 | 2 | import sqlbuilderpkg/utils 3 | export utils 4 | 5 | include sqlbuilderpkg/insert 6 | 7 | include sqlbuilderpkg/update 8 | 9 | include sqlbuilderpkg/delete 10 | 11 | include sqlbuilderpkg/select 12 | 13 | include sqlbuilderpkg/totypes 14 | -------------------------------------------------------------------------------- /src/sqlbuilderpkg/delete.nim: -------------------------------------------------------------------------------- 1 | # Copyright 2020 - Thomas T. Jarløv 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/macros 10 | 11 | import 12 | ./utils_private 13 | 14 | from ./utils import ArgsContainer 15 | 16 | 17 | proc sqlDelete*(table: string, where: varargs[string]): SqlQuery = 18 | ## SQL builder for DELETE queries 19 | ## Does NOT check for NULL values 20 | 21 | var res = "DELETE FROM " & table 22 | if where.len > 0: 23 | res.add sqlWhere(where) 24 | result = sql(res) 25 | 26 | 27 | proc sqlDelete*(table: string, where: varargs[string], args: ArgsContainer.query): SqlQuery = 28 | ## SQL builder for DELETE queries 29 | ## Checks for NULL values 30 | 31 | var res = "DELETE FROM " & table 32 | if where.len > 0: 33 | res.add(sqlWhere(where)) 34 | result = sql(res) 35 | 36 | 37 | macro sqlDeleteMacro*(table: string, where: varargs[string]): SqlQuery = 38 | ## SQL builder for SELECT queries 39 | ## Does NOT check for NULL values 40 | 41 | var res = "DELETE FROM " & $table 42 | if where.len > 0: 43 | res.add sqlWhere(where) 44 | result = parseStmt("sql(\"" & res & "\")") 45 | -------------------------------------------------------------------------------- /src/sqlbuilderpkg/insert.nim: -------------------------------------------------------------------------------- 1 | # Copyright 2020 - Thomas T. Jarløv 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/macros, 10 | std/strutils 11 | 12 | from ./utils import ArgsContainer 13 | 14 | proc sqlInsert*(table: string, data: varargs[string], args: ArgsContainer.query): SqlQuery = 15 | ## SQL builder for INSERT queries 16 | ## Checks for NULL values 17 | 18 | var fields = "INSERT INTO " & table & " (" 19 | var vals = "" 20 | for i, d in data: 21 | if args[i].isNull: 22 | continue 23 | if i > 0: 24 | fields.add(", ") 25 | vals.add(", ") 26 | fields.add(d) 27 | vals.add('?') 28 | 29 | result = sql(fields & ") VALUES (" & vals & ")") 30 | 31 | 32 | proc sqlInsert*(table: string, data: varargs[string], args: seq[string] = @[]): SqlQuery = 33 | ## SQL builder for INSERT queries 34 | ## 35 | ## Can check for NULL values manually typed or by comparing 36 | ## the length of the data and args sequences. 37 | ## 38 | ## data = @["id", "name", "age"] 39 | ## args = @["1", "Thomas", "NULL"] 40 | ## => INSERT INTO table (id, name, age) VALUES (?, ?, NULL) 41 | ## 42 | ## data = @["id", "name", "age"] 43 | ## args = @["1", "Thomas"] or @[] 44 | ## => INSERT INTO table (id, name, age) VALUES (?, ?, ?) 45 | 46 | var 47 | fields = "INSERT INTO " & table & " (" 48 | vals = "" 49 | 50 | let 51 | checkArgs = data.len() == args.len() 52 | 53 | 54 | for i, d in data: 55 | if i > 0: 56 | fields.add(", ") 57 | vals.add(", ") 58 | 59 | # 60 | # Check for manual null and then short circuit 61 | # 62 | if d.endsWith(" = NULL"): 63 | fields.add(d.split(" = NULL")[0]) 64 | vals.add("NULL") 65 | continue 66 | 67 | # 68 | # Insert field name 69 | # 70 | fields.add(d) 71 | 72 | # 73 | # Insert value parameter 74 | # 75 | # Check corresponding args 76 | if checkArgs: 77 | if args[i].len() == 0 or args[i] == "NULL": 78 | vals.add("NULL") 79 | else: 80 | vals.add('?') 81 | # 82 | # No args, just add parameter 83 | # 84 | else: 85 | vals.add('?') 86 | 87 | result = sql(fields & ") VALUES (" & vals & ")") 88 | 89 | 90 | macro sqlInsertMacro*(table: string, data: varargs[string]): SqlQuery = 91 | ## SQL builder for INSERT queries 92 | ## Does NOT check for NULL values 93 | 94 | var fields = "INSERT INTO " & $table & " (" 95 | var vals = "" 96 | for i, d in data: 97 | if i > 0: 98 | fields.add(", ") 99 | vals.add(", ") 100 | if ($d).endsWith(" = NULL"): 101 | fields.add(($d).split(" = NULL")[0]) 102 | vals.add("NULL") 103 | continue 104 | fields.add($d) 105 | vals.add('?') 106 | result = parseStmt("sql(\"" & $fields & ") VALUES (" & $vals & ")\")") 107 | -------------------------------------------------------------------------------- /src/sqlbuilderpkg/query_calls.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | ## 4 | ## These are procs to catch DB errors and return a default value to move on. 5 | ## This should only be used if: 6 | ## - It is not critical data 7 | ## - You can live with a default value in case of an error 8 | ## - You have no other way to catch the error 9 | ## - You are to lazy to write the try-except procs yourself 10 | ## 11 | 12 | import 13 | std/typetraits 14 | 15 | when defined(waterparkPostgres): 16 | import waterpark/postgres 17 | elif defined(waterparkSqlite): 18 | import waterpark/sqlite 19 | 20 | when NimMajor >= 2: 21 | when defined(test): 22 | import db_connector/db_sqlite 23 | else: 24 | import db_connector/db_postgres 25 | else: 26 | when defined(test): 27 | import std/db_sqlite 28 | else: 29 | import std/db_postgres 30 | 31 | 32 | 33 | 34 | when defined(waterparkPostgres) or defined(waterparkSqlite): 35 | discard 36 | 37 | 38 | 39 | else: 40 | 41 | proc getValueTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): string = 42 | try: 43 | result = getValue(db, query, args) 44 | except: 45 | echo(distinctBase(query).subStr(0, 200) & "\n" & getCurrentExceptionMsg()) 46 | 47 | 48 | 49 | 50 | proc getAllRowsTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): seq[Row] = 51 | try: 52 | result = getAllRows(db, query, args) 53 | except: 54 | echo(distinctBase(query).subStr(0, 200) & "\n" & getCurrentExceptionMsg()) 55 | 56 | 57 | 58 | 59 | proc getRowTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`], fillIfNull = 0): Row = 60 | try: 61 | result = getRow(db, query, args) 62 | except: 63 | echo(distinctBase(query).subStr(0, 200) & "\n" & getCurrentExceptionMsg()) 64 | 65 | # If the fillIfNull is set, we will fill the result with empty strings in case 66 | # the result count does not match the fillIfNull value 67 | if fillIfNull != 0 and result.len() != fillIfNull: 68 | for i in 0..fillIfNull-1: 69 | result.add("") 70 | 71 | 72 | 73 | 74 | iterator fastRowsTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): Row = 75 | try: 76 | for i in fastRows(db, query, args): 77 | yield i 78 | except: 79 | echo(distinctBase(query).subStr(0, 200) & "\n" & getCurrentExceptionMsg()) 80 | 81 | 82 | 83 | 84 | proc tryExecTry*(db: DbConn, query: SqlQuery; args: varargs[string, `$`]): bool = 85 | try: 86 | result = tryExec(db, query, args) 87 | except: 88 | echo(distinctBase(query).subStr(0, 200) & "\n" & getCurrentExceptionMsg()) 89 | 90 | 91 | 92 | 93 | proc execTry*(db: DbConn, query: SqlQuery; args: varargs[string, `$`]) = 94 | try: 95 | exec(db, query, args) 96 | except: 97 | echo(distinctBase(query).subStr(0, 200) & "\n" & getCurrentExceptionMsg()) 98 | 99 | 100 | 101 | 102 | proc execAffectedRowsTry*(db: DbConn, query: SqlQuery; args: varargs[string, `$`]): int64 = 103 | try: 104 | result = execAffectedRows(db, query, args) 105 | except: 106 | echo(distinctBase(query).subStr(0, 200) & "\n" & getCurrentExceptionMsg()) 107 | return -1 108 | 109 | 110 | 111 | proc tryInsertIDTry*(db: DbConn, query: SqlQuery; args: varargs[string, `$`]): int64 = 112 | try: 113 | result = tryInsertID(db, query, args) 114 | except: 115 | echo(distinctBase(query).subStr(0, 200) & "\n" & getCurrentExceptionMsg()) 116 | return -1 -------------------------------------------------------------------------------- /src/sqlbuilderpkg/select.nim: -------------------------------------------------------------------------------- 1 | # Copyright 2020 - Thomas T. Jarløv 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/macros, 10 | std/strutils 11 | 12 | import 13 | ./utils_private 14 | 15 | from ./utils import SQLJoinType, ArgsContainer 16 | 17 | 18 | # when defined(sqlDeletemarker): 19 | # const sqlDeletemarkerSeq {.strdefine}: string = "sqlDeletemarkerSeq" 20 | # const tablesWithDeleteMarkerInit = sqlDeletemarkerSeq.split(",") 21 | # else: 22 | # const tablesWithDeleteMarkerInit = [""] 23 | 24 | 25 | ## 26 | ## Constant generator utilities 27 | ## 28 | proc sqlSelectConstSelect(select: varargs[string]): string = 29 | result = "SELECT " 30 | for i, d in select: 31 | if i > 0: result.add(", ") 32 | result.add($d) 33 | 34 | 35 | proc sqlSelectConstJoin( 36 | joinargs: varargs[tuple[table: string, tableAs: string, on: seq[string]]], 37 | jointype: NimNode, 38 | deleteMarkersFields: seq[string], 39 | deleteMarker: NimNode 40 | ): string = 41 | var lef = "" 42 | 43 | if joinargs.len == 0: 44 | return 45 | 46 | for d in joinargs: 47 | if d.repr.len == 0 and joinargs.len() == 2: 48 | continue 49 | 50 | lef.add(" " & $jointype & " JOIN ") 51 | lef.add($d.table & " ") 52 | 53 | if d.tableAs != "" and d.tableAs != d.table: 54 | lef.add("AS " & $d.tableAs & " ") 55 | 56 | lef.add("ON (") 57 | 58 | for i, join in d.on: 59 | if i > 0: 60 | lef.add(" AND ") 61 | lef.add($join) 62 | 63 | if d.table in deleteMarkersFields: 64 | if d.tableAs != "" and (d.tableAs & $deleteMarker) notin lef: 65 | lef.add(" AND " & d.tableAs & $deleteMarker) 66 | elif (d.table & $deleteMarker) notin lef: 67 | lef.add(" AND " & d.table & $deleteMarker) 68 | 69 | lef.add(")") 70 | 71 | return lef 72 | 73 | 74 | proc sqlSelectConstWhere(where: varargs[string], usePrepared: NimNode): string = 75 | 76 | var 77 | wes = "" 78 | prepareCount = 0 79 | 80 | for i, d in where: 81 | let v = $d 82 | let dataUpper = v.toUpperAscii() 83 | let needParenthesis = dataUpper.contains(" OR ") or dataUpper.contains(" AND ") 84 | 85 | 86 | if v.len() > 0 and i == 0: 87 | wes.add(" WHERE ") 88 | 89 | 90 | if i > 0: 91 | wes.add(" AND ") 92 | 93 | 94 | if v.len() > 0: 95 | # 96 | # If this is self-contained where with parenthesis in front and back 97 | # add it and continue 98 | # 99 | # => (xx = yy AND zz = qq) 100 | if v[0] == '(' and v[v.high] == ')': 101 | wes.add(v) 102 | continue 103 | 104 | 105 | if needParenthesis: 106 | wes.add("(") 107 | 108 | 109 | if v.contains("?"): 110 | if boolVal(usePrepared): 111 | var t: string 112 | for c in v: 113 | if c == '?': 114 | prepareCount += 1 115 | t.add("$" & $prepareCount) 116 | else: 117 | t.add($c) 118 | wes.add(t) 119 | else: 120 | wes.add(d) 121 | 122 | # => ... = NULL 123 | elif v.len() >= 5 and dataUpper[(v.high - 4)..v.high] == " NULL": 124 | wes.add(v) 125 | 126 | # => ... = TRUE 127 | elif v.len() >= 5 and dataUpper[(v.high - 4)..v.high] == " TRUE": 128 | wes.add(v) 129 | 130 | # => ... = FALSE 131 | elif v.len() >= 6 and dataUpper[(v.high - 5)..v.high] == " FALSE": 132 | wes.add(v) 133 | 134 | # => ? = ANY(...) 135 | elif v.len() > 5 and dataUpper[0..4] == "= ANY": 136 | if boolVal(usePrepared): 137 | prepareCount += 1 138 | wes.add("$" & $prepareCount & " " & v) 139 | else: 140 | wes.add("? " & v) 141 | 142 | # => ... IN (?) 143 | elif v.len() >= 3 and dataUpper[(v.high - 2)..v.high] == " IN": 144 | if boolVal(usePrepared): 145 | prepareCount += 1 146 | wes.add(v & " ($" & $prepareCount & ")") 147 | else: 148 | wes.add(v & " (?)") 149 | 150 | # => ? IN (...) 151 | elif v.len() > 2 and dataUpper[0..2] == "IN ": 152 | if boolVal(usePrepared): 153 | prepareCount += 1 154 | wes.add("$" & $prepareCount & " " & v) 155 | else: 156 | wes.add("? " & v) 157 | 158 | # => x = y 159 | elif v.len() >= 5 and ( 160 | v.contains(" = ") or 161 | v.contains(" != ") or 162 | v.contains(" >= ") or 163 | v.contains(" <= ") or 164 | v.contains(" <> ") or 165 | v.contains(" > ") or 166 | v.contains(" < ") 167 | ): 168 | const whereTypes = [" = ", " != ", " >= ", " <= ", " > ", " < "] 169 | for wt in whereTypes: 170 | if wt notin v: 171 | continue 172 | 173 | let eSplit = v.split(wt) #" = ") 174 | # Value included already 175 | if eSplit.len() == 2 and eSplit[0].strip().len() > 0 and eSplit[1].strip().len() > 0: 176 | if boolVal(usePrepared): 177 | wes.add(v) 178 | else: 179 | wes.add(v) 180 | # If there's multiple elements 181 | elif eSplit.len() > 2 and eSplit[eSplit.high].len() > 1: 182 | if boolVal(usePrepared): 183 | wes.add(v) 184 | else: 185 | wes.add(v) 186 | # Insert ? 187 | else: 188 | if boolVal(usePrepared): 189 | prepareCount += 1 190 | wes.add(v & " $" & $prepareCount) 191 | else: 192 | wes.add(v & " ?") 193 | 194 | break 195 | 196 | # => ... = ? 197 | else: 198 | if boolVal(usePrepared): 199 | prepareCount += 1 200 | wes.add(v & " $" & $prepareCount) 201 | else: 202 | wes.add(v & " ?") 203 | 204 | 205 | if needParenthesis: 206 | wes.add(")") 207 | 208 | 209 | return wes 210 | 211 | 212 | proc sqlSelectConstWhereIn( 213 | wes, acc: string, 214 | whereInField: NimNode, whereInValue: NimNode 215 | ): string = 216 | var acc = "" 217 | 218 | if ($whereInField).len() > 0 and (whereInValue).len() > 0: 219 | if wes.len == 0: 220 | acc.add(" WHERE " & $whereInField & " in (") 221 | else: 222 | acc.add(" AND " & $whereInField & " in (") 223 | 224 | 225 | var inVal: string 226 | 227 | for a in whereInValue: 228 | if inVal != "": 229 | inVal.add(",") 230 | inVal.add($a) 231 | 232 | acc.add(if inVal == "": "0" else: inVal) 233 | acc.add(")") 234 | 235 | return acc 236 | 237 | 238 | proc sqlSelectConstSoft( 239 | wes, acc: string, 240 | # tablesInQuery: seq[tuple[table: string, tableAs: string]], 241 | table, tableAs: string, 242 | tablesWithDeleteMarker: varargs[string], 243 | useDeleteMarker: NimNode, 244 | deleteMarker: NimNode 245 | ): (string, string) = 246 | if $useDeleteMarker == "true" and tablesWithDeleteMarker.len() > 0: 247 | var wesTo, accTo: string 248 | 249 | # for t in tablesInQuery: 250 | if table notin tablesWithDeleteMarker: 251 | return (wesTo, accTo) 252 | 253 | let toUse = if tableAs != "": tableAs else: table 254 | 255 | if wes == "" and acc == "": 256 | wesTo.add(" WHERE " & toUse & $deleteMarker) 257 | 258 | elif acc != "": 259 | accTo.add(" AND " & toUse & $deleteMarker) 260 | 261 | else: 262 | wesTo.add(" AND " & toUse & $deleteMarker) 263 | 264 | return (wesTo, accTo) 265 | 266 | 267 | 268 | ## 269 | ## Constant generator 270 | ## 271 | macro sqlSelectConst*( 272 | # BASE 273 | table: string, 274 | select: static varargs[string], 275 | where: static varargs[string], 276 | 277 | # Join 278 | joinargs: static varargs[tuple[table: string, tableAs: string, on: seq[string]]] = [], 279 | jointype: SQLJoinType = SQLJoinType.LEFT, 280 | 281 | # WHERE-IN 282 | whereInField: string = "", 283 | whereInValue: varargs[string] = [], 284 | 285 | # Custom SQL, e.g. ORDER BY 286 | customSQL: string = "", 287 | 288 | # Table alias 289 | tableAs: string = "", 290 | 291 | # Prepare statement 292 | usePrepared: bool = false, 293 | 294 | # Soft delete 295 | useDeleteMarker: bool = true, 296 | # If we are using `static` then we can assign const-variables to it. It not, 297 | # we must use direct varargs. 298 | tablesWithDeleteMarker: varargs[string] = [], 299 | deleteMarker = ".is_deleted IS NULL", 300 | ): SqlQuery = 301 | ## SQL builder for SELECT queries 302 | 303 | 304 | 305 | var deleteMarkersFields: seq[string] 306 | if tablesWithDeleteMarker.len() > 0: 307 | for t in tablesWithDeleteMarker: 308 | if t.repr.len == 0: 309 | continue 310 | if $t notin deleteMarkersFields: 311 | deleteMarkersFields.add($t) 312 | 313 | when declared(tablesWithDeleteMarkerInit): 314 | for t in tablesWithDeleteMarkerInit: 315 | if t.repr.len == 0: 316 | continue 317 | if t notin deleteMarkersFields: 318 | deleteMarkersFields.add(t) 319 | 320 | 321 | # 322 | # Create seq of tables 323 | # 324 | var tablesInQuery: seq[tuple[table: string, tableAs: string]] 325 | 326 | # Base table 327 | if $tableAs != "" and $table != $tableAs: 328 | tablesInQuery.add(($table, $tableAs)) 329 | else: 330 | tablesInQuery.add(($table, "")) 331 | 332 | # Join table 333 | var joinTablesUsed: seq[string] 334 | if joinargs.len != 0: 335 | for i, d in joinargs: 336 | if d.repr.len == 0: 337 | continue 338 | 339 | if $d.table in joinTablesUsed: 340 | continue 341 | joinTablesUsed.add($d.table) 342 | 343 | if $d.tableAs != "" and $d.tableAs != $d.table: 344 | tablesInQuery.add(($d.table, $d.tableAs)) 345 | else: 346 | tablesInQuery.add(($d.table, "")) 347 | 348 | 349 | # 350 | # Base - from table 351 | # 352 | let tableName = 353 | if ($tableAs).len() > 0 and $table != $tableAs: 354 | $table & " AS " & $tableAs 355 | else: 356 | $table 357 | 358 | 359 | # 360 | # Select 361 | if select.len() == 0: 362 | raise newException( 363 | Exception, 364 | "Bad SQL format. Please check your SQL statement. " & 365 | "This is most likely caused by a missing SELECT clause. " & 366 | "Bug: `select.len() == 0` in \n" & $select 367 | ) 368 | var res = sqlSelectConstSelect(select) 369 | 370 | 371 | # 372 | # Joins 373 | # 374 | var lef = "" 375 | if joinargs.len != 0: 376 | lef = sqlSelectConstJoin(joinargs, jointype, deleteMarkersFields, deleteMarker) 377 | 378 | 379 | # 380 | # Where - normal 381 | # 382 | var wes = sqlSelectConstWhere(where, usePrepared) 383 | 384 | 385 | 386 | # 387 | # Where - n IN (x,c,v) 388 | # 389 | var acc = "" 390 | acc.add sqlSelectConstWhereIn(wes, acc, whereInField, whereInValue) 391 | 392 | 393 | # 394 | # Soft delete 395 | # 396 | var (toWes, toAcc) = sqlSelectConstSoft( 397 | wes, acc, 398 | # tablesInQuery, 399 | $table, $tableAs, 400 | deleteMarkersFields, 401 | useDeleteMarker, deleteMarker 402 | ) 403 | wes.add(toWes) 404 | acc.add(toAcc) 405 | 406 | # 407 | # Combine the pretty SQL 408 | # 409 | let finalSQL = res & " FROM " & tableName & lef & wes & acc & " " & $customSQL 410 | 411 | 412 | # 413 | # Error checking 414 | # 415 | 416 | var illegal = hasIllegalFormats($finalSQL) 417 | if illegal.len() > 0: 418 | raise newException( 419 | Exception, 420 | "Bad SQL format. Please check your SQL statement. " & 421 | "This is most likely caused by a missing WHERE clause. " & 422 | "Bug: `" & illegal & "` in \n" & finalSQL 423 | ) 424 | 425 | if $table != $tableAs and lef.len() > 0: 426 | var hit: bool 427 | for s in select: 428 | if "." notin s: 429 | echo "WARNING (SQL MACRO): Missing table alias in select statement: " & $s 430 | hit = true 431 | if hit: 432 | echo "WARNING (SQL MACRO): " & finalSQL 433 | 434 | 435 | 436 | result = parseStmt("sql(\"" & finalSQL & "\")") 437 | 438 | 439 | 440 | 441 | ## 442 | ## Default select generator 443 | ## 444 | proc sqlSelect*( 445 | # BASE 446 | table: string, 447 | select: varargs[string], 448 | where: varargs[string], 449 | 450 | # Join 451 | joinargs: varargs[tuple[table: string, tableAs: string, on: seq[string]]] = [], 452 | jointype: SQLJoinType = LEFT, 453 | joinoverride: string = "", # Override the join statement by inserting without check 454 | 455 | # WHERE-IN 456 | whereInField: string = "", 457 | whereInValue: varargs[string] = [], # Could be unsafe. Is not checked. 458 | whereInValueString: seq[string] = @[], 459 | whereInValueInt: seq[int] = @[], 460 | 461 | # Custom SQL, e.g. ORDER BY 462 | customSQL: string = "", 463 | 464 | # Null checks 465 | checkedArgs: ArgsContainer.query = @[], 466 | 467 | # Table alias 468 | tableAs: string = table, 469 | 470 | # Prepare statement 471 | usePrepared: bool = false, 472 | 473 | # Soft delete 474 | useDeleteMarker: bool = true, 475 | tablesWithDeleteMarker: varargs[string] = [], #(when declared(tablesWithDeleteMarkerInit): tablesWithDeleteMarkerInit else: []), #@[], 476 | deleteMarker = ".is_deleted IS NULL", 477 | ): SqlQuery = 478 | ## SQL builder for SELECT queries 479 | 480 | 481 | var deleteMarkersFields: seq[string] 482 | for t in tablesWithDeleteMarker: 483 | if t == "": 484 | continue 485 | if t notin deleteMarkersFields: 486 | deleteMarkersFields.add(t) 487 | 488 | when declared(tablesWithDeleteMarkerInit): 489 | for t in tablesWithDeleteMarkerInit: 490 | if t == "": 491 | continue 492 | if t notin deleteMarkersFields: 493 | deleteMarkersFields.add(t) 494 | 495 | # 496 | # Create seq of tables 497 | # 498 | var tablesInQuery: seq[tuple[table: string, tableAs: string]] 499 | 500 | 501 | # Base table 502 | if $tableAs != "" and $table != $tableAs: 503 | tablesInQuery.add(($table, $tableAs)) 504 | else: 505 | tablesInQuery.add(($table, "")) 506 | 507 | 508 | # Join table 509 | if joinargs.len() > 0: 510 | for d in joinargs: 511 | if d.table == "": 512 | continue 513 | if d.tableAs != "" and d.tableAs != d.table: 514 | tablesInQuery.add((d.table, d.tableAs)) 515 | else: 516 | tablesInQuery.add((d.table, "")) 517 | 518 | 519 | # 520 | # Base - from table 521 | # 522 | let tableName = 523 | if tableAs != "" and table != tableAs: 524 | table & " AS " & tableAs 525 | else: 526 | table 527 | 528 | 529 | # 530 | # Select 531 | # 532 | var res = "SELECT " 533 | for i, d in select: 534 | if i > 0: res.add(", ") 535 | res.add(d) 536 | 537 | 538 | # 539 | # Joins 540 | # 541 | var lef = "" 542 | if joinargs.len() > 0: 543 | for i, d in joinargs: 544 | lef.add(" " & $jointype & " JOIN ") 545 | lef.add(d.table & " ") 546 | 547 | if d.tableAs != "" and d.tableAs != d.table: 548 | lef.add("AS " & d.tableAs & " ") 549 | 550 | lef.add("ON (") 551 | 552 | for i, join in d.on: 553 | if i > 0: 554 | lef.add(" AND ") 555 | lef.add(join) 556 | 557 | if d.table in deleteMarkersFields: 558 | if d.tableAs != "":# and (d.tableAs & $deleteMarker) notin lef: 559 | lef.add(" AND " & d.tableAs & $deleteMarker) 560 | else:#if (d.table & $deleteMarker) notin lef: 561 | lef.add(" AND " & d.table & $deleteMarker) 562 | 563 | lef.add(")") 564 | 565 | if joinoverride.len() > 0: 566 | lef.add(" " & joinoverride) 567 | 568 | 569 | # 570 | # Where - normal 571 | # 572 | var 573 | wes = "" 574 | prepareCount = 0 575 | for i, d in where: 576 | if d != "" and i == 0: 577 | wes.add(" WHERE ") 578 | 579 | if i > 0: 580 | wes.add(" AND ") 581 | 582 | if d != "": 583 | # 584 | # If this is self-contained where with parenthesis in front and back 585 | # add it and continue 586 | # 587 | # => (xx = yy AND zz = qq) 588 | if d[0] == '(' and d[d.high] == ')': 589 | wes.add(d) 590 | continue 591 | 592 | let dataUpper = d.toUpperAscii() 593 | let needParenthesis = dataUpper.contains(" OR ") or dataUpper.contains(" AND ") 594 | 595 | if needParenthesis: 596 | wes.add("(") 597 | 598 | # Parameter substitutions included 599 | if d.contains("?"): 600 | if usePrepared: 601 | var t: string 602 | for c in d: 603 | if c == '?': 604 | prepareCount += 1 605 | t.add("$" & $prepareCount) 606 | else: 607 | t.add($c) 608 | wes.add(t) 609 | else: 610 | wes.add(d) 611 | 612 | # => ... = NULL 613 | elif checkedArgs.len() > 0 and checkedArgs[i].isNull: 614 | wes.add(d & " NULL") 615 | 616 | # => ... = NULL 617 | elif d.len() >= 5 and dataUpper[(d.high - 4)..d.high] == " NULL": 618 | wes.add(d) 619 | 620 | # => ... = TRUE 621 | elif d.len() > 5 and dataUpper[(d.high - 4)..d.high] == " TRUE": 622 | wes.add(d) 623 | 624 | # => ... = FALSE 625 | elif d.len() > 6 and dataUpper[(d.high - 5)..d.high] == " FALSE": 626 | wes.add(d) 627 | 628 | # => ? = ANY(...) 629 | elif d.len() > 5 and dataUpper[0..4] == "= ANY": 630 | if usePrepared: 631 | prepareCount += 1 632 | wes.add("$" & $prepareCount & " " & d) 633 | else: 634 | wes.add("? " & d) 635 | 636 | # => ... IN (?) 637 | elif d.len() >= 3 and dataUpper[(d.high - 2)..d.high] == " IN": 638 | if usePrepared: 639 | prepareCount += 1 640 | wes.add(d & " ($" & $prepareCount & ")") 641 | else: 642 | wes.add(d & " (?)") 643 | 644 | # => ? IN (...) 645 | elif d.len() > 2 and dataUpper[0..2] == "IN ": 646 | if usePrepared: 647 | prepareCount += 1 648 | wes.add("$" & $prepareCount & " " & d) 649 | else: 650 | wes.add("? " & d) 651 | 652 | # => x != y 653 | elif d.len() >= 5 and d.contains(" != ") and d.split(" != ").len() == 2: 654 | wes.add(d) 655 | 656 | # !! Waring = pfl.action IN (2,3,4) <== not supported 657 | 658 | # => x = y 659 | elif d.len() >= 5 and ( 660 | d.contains(" = ") or 661 | d.contains(" != ") or 662 | d.contains(" >= ") or 663 | d.contains(" <= ") or 664 | d.contains(" <> ") or 665 | d.contains(" > ") or 666 | d.contains(" < ") 667 | ): #d.contains(" = "): 668 | const whereTypes = [" = ", " != ", " >= ", " <= ", " > ", " < "] 669 | for wt in whereTypes: 670 | if wt notin d: 671 | continue 672 | 673 | let eSplit = d.split(wt) 674 | # Value included already 675 | if eSplit.len() == 2 and eSplit[0].strip().len() > 0 and eSplit[1].strip().len() > 0: 676 | if usePrepared: 677 | wes.add(d) 678 | else: 679 | wes.add(d) 680 | # If there's multiple elements 681 | elif eSplit.len() > 2 and eSplit[eSplit.high].len() > 1: 682 | if usePrepared: 683 | wes.add(d) 684 | else: 685 | wes.add(d) 686 | # Insert ? 687 | else: 688 | if usePrepared: 689 | prepareCount += 1 690 | wes.add(d & " $" & $prepareCount) 691 | else: 692 | wes.add(d & " ?") 693 | 694 | break 695 | 696 | # => ... = ? 697 | else: 698 | if usePrepared: 699 | prepareCount += 1 700 | wes.add(d & " $" & $prepareCount) 701 | else: 702 | wes.add(d & " ?") 703 | 704 | 705 | if needParenthesis: 706 | wes.add(")") 707 | 708 | 709 | # 710 | # Where IN 711 | # 712 | var acc = "" 713 | if whereInField != "" and (whereInValue.len() > 0 or whereInValueString.len() > 0 or whereInValueInt.len() > 0): 714 | if wes.len == 0: 715 | acc.add(" WHERE " & whereInField & " in (") 716 | else: 717 | acc.add(" AND " & whereInField & " in (") 718 | 719 | var inVal: string 720 | 721 | if whereInValue.len() > 0: 722 | inVal.add(whereInValue.join(",")) 723 | 724 | elif whereInValueString.len() > 0: 725 | for a in whereInValueString: 726 | if a == "": 727 | continue 728 | 729 | if inVal != "": 730 | inVal.add(",") 731 | 732 | inVal.add("'" & dbQuotePrivate(a) & "'") 733 | 734 | else: 735 | for a in whereInValueInt: 736 | if inVal != "": 737 | inVal.add(",") 738 | inVal.add($a) 739 | 740 | if inVal.len() == 0: 741 | if whereInValue.len() > 0: 742 | acc.add("0") 743 | elif whereInValueString.len() > 0: 744 | acc.add("''") 745 | elif whereInValueInt.len() > 0: 746 | acc.add("0") 747 | else: 748 | acc.add(inVal) 749 | 750 | acc.add(")") 751 | 752 | 753 | 754 | # 755 | # Soft delete 756 | # 757 | if useDeleteMarker and deleteMarkersFields.len() > 0: 758 | # for t in tablesInQuery: 759 | if table in deleteMarkersFields: 760 | # continue 761 | 762 | let toUse = if tableAs != "": tableAs else: table 763 | 764 | if wes == "" and acc == "": 765 | wes.add(" WHERE " & toUse & $deleteMarker) 766 | 767 | elif acc != "": 768 | acc.add(" AND " & toUse & $deleteMarker) 769 | 770 | else: 771 | wes.add(" AND " & toUse & $deleteMarker) 772 | 773 | 774 | 775 | 776 | when defined(dev): 777 | 778 | let sqlString = res & " FROM " & tableName & lef & wes & acc & " " & customSQL 779 | 780 | let illegal = hasIllegalFormats(sqlString) 781 | if illegal.len() > 0: 782 | raise newException( 783 | Exception, 784 | "Bad SQL format. Please check your SQL statement. " & 785 | "This is most likely caused by a missing WHERE clause. " & 786 | "Bug: `" & illegal & "` in \n" & sqlString 787 | ) 788 | 789 | 790 | # Check for missing table alias 791 | if ( 792 | (tableAs != "" and table != tableAs) or 793 | (joinargs.len() > 0) 794 | ): 795 | var hit: bool 796 | for s in select: 797 | if "." notin s: 798 | echo "WARNING (SQL SELECT): Missing table alias in select statement: " & $s 799 | hit = true 800 | if hit: 801 | echo "WARNING (SQL SELECT): " & sqlString 802 | 803 | result = sql(res & " FROM " & tableName & lef & wes & acc & " " & customSQL) 804 | 805 | 806 | 807 | 808 | 809 | # 810 | # Legacy 811 | # 812 | proc sqlSelect*( 813 | table: string, 814 | data: varargs[string], 815 | left: varargs[string], 816 | whereC: varargs[string], 817 | access: string, accessC: string, 818 | user: string, 819 | args: ArgsContainer.query = @[], 820 | useDeleteMarker: bool = true, 821 | tablesWithDeleteMarker: varargs[string] = [], 822 | deleteMarker = ".is_deleted IS NULL", 823 | ): SqlQuery {.deprecated.} = 824 | ## 825 | ## Legacy converter 826 | ## 827 | 828 | 829 | var leftcon: seq[tuple[table: string, tableAs: string, on: seq[string]]] 830 | var joinoverride: string 831 | for raw in left: 832 | if raw.len() == 0: 833 | continue 834 | 835 | if raw.len() >= 7 and raw[0..6].toLowerAscii() == "lateral": 836 | joinoverride = "LEFT JOIN " & raw 837 | continue 838 | 839 | let d = raw#.toLowerAscii() 840 | 841 | let 842 | lsplit1 = if d.contains(" ON "): d.split(" ON ") else: d.split(" on ") 843 | lsplit2 = if lsplit1[0].contains(" AS "): lsplit1[0].split(" AS ") else: lsplit1[0].split(" as ") 844 | hasAlias = (lsplit2.len() > 1) 845 | 846 | leftcon.add( 847 | ( 848 | table: lsplit2[0].strip(), 849 | tableAs: (if hasAlias: lsplit2[1].strip() else: ""), 850 | on: @[lsplit1[1].strip()] 851 | ) 852 | ) 853 | 854 | let 855 | tableSplit = if table.contains(" AS "): table.split(" AS ") else: table.split(" as ") #table.toLowerAscii().split(" as ") 856 | tableName = 857 | if tableSplit.len() > 1: 858 | tableSplit[0] 859 | else: 860 | table 861 | tableAsName = 862 | if tableSplit.len() > 1: 863 | tableSplit[1] 864 | else: 865 | "" 866 | 867 | return sqlSelect( 868 | table = tableName, 869 | tableAs = tableAsName, 870 | select = data, 871 | joinargs = leftcon, 872 | jointype = LEFT, 873 | joinoverride = joinoverride, 874 | where = whereC, 875 | whereInField = accessC, 876 | whereInValue = @[access], 877 | customSQL = user, 878 | checkedArgs = args, 879 | useDeleteMarker = useDeleteMarker, 880 | tablesWithDeleteMarker = tablesWithDeleteMarker, 881 | deleteMarker = deleteMarker 882 | ) 883 | 884 | 885 | 886 | macro sqlSelectMacro*(table: string, data: varargs[string], left: varargs[string], whereC: varargs[string], access: string, accessC: string, user: string): SqlQuery {.deprecated.} = 887 | ## SQL builder for SELECT queries 888 | ## Does NOT check for NULL values 889 | ## 890 | ## Legacy converter 891 | ## 892 | 893 | var res: string 894 | for i, d in data: 895 | if i > 0: 896 | res.add(", ") 897 | res.add($d) 898 | 899 | var lef: string 900 | for i, d in left: 901 | if $d != "": 902 | lef.add(" LEFT JOIN " & $d) 903 | 904 | var wes: string 905 | for i, d in whereC: 906 | if $d != "" and i == 0: 907 | wes.add(" WHERE ") 908 | if i > 0: 909 | wes.add(" AND ") 910 | if $d != "": 911 | wes.add($d & " ?") 912 | 913 | var acc: string 914 | if access.len() != 0: 915 | if wes.len == 0: 916 | acc.add(" WHERE " & $accessC & " in (") 917 | else: 918 | acc.add(" AND " & $accessC & " in (") 919 | for a in split($access, ","): 920 | acc.add(a & ",") 921 | acc = acc[0 .. ^2] 922 | if acc.len() == 0: 923 | acc.add("0") 924 | acc.add(")") 925 | 926 | when defined(testSqlquery): 927 | echo "SELECT " & res & " FROM " & $table & lef & wes & acc & " " & $user 928 | 929 | result = parseStmt("sql(\"SELECT " & res & " FROM " & $table & lef & wes & acc & " " & $user & "\")") 930 | 931 | -------------------------------------------------------------------------------- /src/sqlbuilderpkg/select_legacy.nim: -------------------------------------------------------------------------------- 1 | # Copyright 2020 - Thomas T. Jarløv 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/macros, 10 | std/strutils 11 | 12 | import 13 | ./utils 14 | 15 | import 16 | ./select 17 | 18 | 19 | 20 | 21 | proc sqlSelect*( 22 | table: string, 23 | data: varargs[string], 24 | left: varargs[string], 25 | whereC: varargs[string], 26 | access: string, 27 | accessC: string, 28 | user: string, 29 | args: ArgsContainer.query = @[], 30 | useDeleteMarker: bool = true, 31 | tablesWithDeleteMarker: varargs[string] = (when declared(tablesWithDeleteMarkerInit): tablesWithDeleteMarkerInit else: []), #@[], 32 | deleteMarker = ".is_deleted IS NULL", 33 | ): SqlQuery {.deprecated.} = 34 | ## 35 | ## This is a legacy converter for the old sqlSelect() use. 36 | ## 37 | 38 | 39 | var leftcon: seq[tuple[table: string, tableAs: string, on: seq[string]]] 40 | var joinoverride: string 41 | for raw in left: 42 | if raw.len() == 0: 43 | continue 44 | 45 | if raw.len() >= 7 and raw[0..6].toLowerAscii() == "lateral": 46 | joinoverride = "LEFT JOIN " & raw 47 | continue 48 | 49 | let d = raw.toLowerAscii() 50 | 51 | let 52 | lsplit1 = d.split(" on ") 53 | lsplit2 = lsplit1[0].split(" as ") 54 | hasAlias = (lsplit2.len() > 1) 55 | 56 | leftcon.add( 57 | ( 58 | table: lsplit2[0].strip(), 59 | tableAs: (if hasAlias: lsplit2[1].strip() else: ""), 60 | on: @[lsplit1[1].strip()] 61 | ) 62 | ) 63 | 64 | let 65 | tableSplit = table.split(" as ") 66 | tableName = 67 | if tableSplit.len() > 1: 68 | tableSplit[0] 69 | else: 70 | table 71 | tableAsName = 72 | if tableSplit.len() > 1: 73 | tableSplit[1] 74 | else: 75 | "" 76 | 77 | 78 | 79 | return sqlSelect( 80 | table = tableName, 81 | tableAs = tableAsName, 82 | select = data, 83 | joinargs = leftcon, 84 | jointype = LEFT, 85 | joinoverride = joinoverride, 86 | where = whereC, 87 | whereInField = accessC, 88 | whereInValue = @[access], 89 | customSQL = user, 90 | checkedArgs = args, 91 | useDeleteMarker = useDeleteMarker, 92 | tablesWithDeleteMarker = tablesWithDeleteMarker, 93 | deleteMarker = deleteMarker 94 | ) 95 | 96 | 97 | #[ 98 | ## 99 | ## Raw legacy procedure 100 | ## 101 | proc sqlSelect*(table: string, data: varargs[string], left: varargs[string], whereC: varargs[string], access: string, accessC: string, user: string): SqlQuery = 102 | ## SQL builder for SELECT queries 103 | ## Does NOT check for NULL values 104 | 105 | var res = "SELECT " 106 | for i, d in data: 107 | if i > 0: res.add(", ") 108 | res.add(d) 109 | 110 | var lef = "" 111 | for i, d in left: 112 | if d != "": 113 | lef.add(" LEFT JOIN ") 114 | lef.add(d) 115 | 116 | var wes = "" 117 | for i, d in whereC: 118 | if d != "" and i == 0: 119 | wes.add(" WHERE ") 120 | if i > 0: 121 | wes.add(" AND ") 122 | if d != "": 123 | wes.add(d & " ?") 124 | 125 | var acc = "" 126 | if access != "": 127 | if wes.len == 0: 128 | acc.add(" WHERE " & accessC & " in ") 129 | acc.add("(") 130 | else: 131 | acc.add(" AND " & accessC & " in (") 132 | var inVal: string 133 | for a in split(access, ","): 134 | if a == "": continue 135 | if inVal != "": 136 | inVal.add(",") 137 | inVal.add(a) 138 | acc.add(if inVal == "": "0" else: inVal) 139 | acc.add(")") 140 | 141 | when defined(testSqlquery): 142 | echo res & " FROM " & table & lef & wes & acc & " " & user 143 | 144 | when defined(test): 145 | testout = res & " FROM " & table & lef & wes & acc & " " & user 146 | 147 | result = sql(res & " FROM " & table & lef & wes & acc & " " & user) 148 | 149 | 150 | proc sqlSelect*(table: string, data: varargs[string], left: varargs[string], whereC: varargs[string], access: string, accessC: string, user: string, args: ArgsContainer.query): SqlQuery = 151 | ## SQL builder for SELECT queries 152 | ## Checks for NULL values 153 | 154 | var res = "SELECT " 155 | for i, d in data: 156 | if i > 0: res.add(", ") 157 | res.add(d) 158 | 159 | var lef = "" 160 | for i, d in left: 161 | if d != "": 162 | lef.add(" LEFT JOIN ") 163 | lef.add(d) 164 | 165 | var wes = "" 166 | for i, d in whereC: 167 | if d != "" and i == 0: 168 | wes.add(" WHERE ") 169 | if i > 0: 170 | wes.add(" AND ") 171 | if d != "": 172 | if args[i].isNull: 173 | wes.add(d & " = NULL") 174 | else: 175 | wes.add(d & " ?") 176 | 177 | var acc = "" 178 | if access != "": 179 | if wes.len == 0: 180 | acc.add(" WHERE " & accessC & " in ") 181 | acc.add("(") 182 | else: 183 | acc.add(" AND " & accessC & " in (") 184 | 185 | var inVal: string 186 | for a in split(access, ","): 187 | if a == "": continue 188 | if inVal != "": 189 | inVal.add(",") 190 | inVal.add(a) 191 | acc.add(if inVal == "": "0" else: inVal) 192 | acc.add(")") 193 | 194 | when defined(testSqlquery): 195 | echo res & " FROM " & table & lef & wes & acc & " " & user 196 | 197 | when defined(test): 198 | testout = res & " FROM " & table & lef & wes & acc & " " & user 199 | 200 | result = sql(res & " FROM " & table & lef & wes & acc & " " & user) 201 | ]# 202 | 203 | macro sqlSelectMacro*( 204 | table: string, 205 | data: varargs[string], 206 | left: varargs[string], 207 | whereC: varargs[string], 208 | access: string, 209 | accessC: string, 210 | user: string 211 | ): SqlQuery {.deprecated.} = 212 | ## SQL builder for SELECT queries 213 | ## Does NOT check for NULL values 214 | 215 | var res: string 216 | for i, d in data: 217 | if i > 0: 218 | res.add(", ") 219 | res.add($d) 220 | 221 | var lef: string 222 | for i, d in left: 223 | if $d != "": 224 | lef.add(" LEFT JOIN " & $d) 225 | 226 | var wes: string 227 | for i, d in whereC: 228 | if $d != "" and i == 0: 229 | wes.add(" WHERE ") 230 | if i > 0: 231 | wes.add(" AND ") 232 | if $d != "": 233 | wes.add($d & " ?") 234 | 235 | var acc: string 236 | if access.len() != 0: 237 | if wes.len == 0: 238 | acc.add(" WHERE " & $accessC & " in (") 239 | else: 240 | acc.add(" AND " & $accessC & " in (") 241 | for a in split($access, ","): 242 | acc.add(a & ",") 243 | acc = acc[0 .. ^2] 244 | if acc.len() == 0: 245 | acc.add("0") 246 | acc.add(")") 247 | 248 | when defined(testSqlquery): 249 | echo "SELECT " & res & " FROM " & $table & lef & wes & acc & " " & $user 250 | 251 | result = parseStmt("sql(\"SELECT " & res & " FROM " & $table & lef & wes & acc & " " & $user & "\")") 252 | 253 | -------------------------------------------------------------------------------- /src/sqlbuilderpkg/totypes.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | import 4 | std/strutils 5 | 6 | 7 | # proc toSnakeCase(s: string): string = 8 | # for c in s: 9 | # if c.isUpperAscii(): 10 | # if len(result) > 0: 11 | # result.add('_') 12 | # result.add(c.toLowerAscii()) 13 | # else: 14 | # result.add(c) 15 | 16 | proc parseVal[T: bool](data: string, v: var T) = 17 | v = (data == "t" or data == "true") 18 | 19 | proc parseFloat[T: float](data: string, v: var T) = 20 | if data == "": 21 | v = 0.0 22 | else: 23 | v = data.parseFloat() 24 | 25 | proc parseVal[T: int](data: string, v: var T) = 26 | if data == "": 27 | v = 0 28 | else: 29 | v = data.parseInt() 30 | 31 | proc parseVal[T: string](data: string, v: var T) = 32 | v = data 33 | 34 | 35 | proc parseVal[T: bool](v: var T) = (v = false) 36 | 37 | proc parseVal[T: float](v: var T) = (v = 0.0) 38 | 39 | proc parseVal[T: int](v: var T) = (v = 0) 40 | 41 | proc parseVal[T: string](v: var T) = (v = "") 42 | 43 | 44 | proc sqlToType*[T](t: typedesc[T], columns, val: seq[string]): T = 45 | let tmp = t() 46 | 47 | for fieldName, field in tmp[].fieldPairs: 48 | var 49 | found = false 50 | for ci in 0..columns.high: 51 | if columns[ci] == fieldName:#.toSnakeCase: 52 | parseVal(val[ci], field) 53 | found = true 54 | break 55 | if not found: 56 | parseVal(field) 57 | else: 58 | found = false 59 | 60 | return tmp 61 | 62 | 63 | proc sqlToType*[T](t: typedesc[T], columns: seq[string], vals: seq[seq[string]]): seq[T] = 64 | for v in vals: 65 | result.add(sqlToType(t, columns, v)) 66 | return result 67 | 68 | 69 | proc sqlToTypeAs*[T](t: typedesc[T], columns, val: seq[string]): T = 70 | let tmp = t() 71 | 72 | for fieldName, field in tmp[].fieldPairs: 73 | var 74 | found = false 75 | for ci in 0..columns.high: 76 | let cSplit = if columns[ci].contains(" as "): columns[ci].split(" as ") else: columns[ci].split(" AS ") 77 | if cSplit.len() == 2: 78 | if cSplit[1].strip() == fieldName: 79 | parseVal(val[ci], field) 80 | found = true 81 | break 82 | if not found: 83 | parseVal(field) 84 | else: 85 | found = false 86 | 87 | return tmp 88 | -------------------------------------------------------------------------------- /src/sqlbuilderpkg/update.nim: -------------------------------------------------------------------------------- 1 | # Copyright 2020 - Thomas T. Jarløv 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/macros, 10 | std/strutils 11 | 12 | import 13 | ./utils_private 14 | 15 | from ./utils import ArgsContainer 16 | 17 | 18 | proc updateSetFormat(v: string): string = 19 | ## The table columns that we want to update. Validate whether 20 | ## the user has provided equal sign or not. 21 | 22 | let 23 | field = v.strip() 24 | fieldSplit = field.split("=") 25 | 26 | # 27 | # Does the data have a `=` sign? 28 | # 29 | if fieldSplit.len() == 2: 30 | # 31 | # If the data is only having equal but no value, insert a `?` sign. 32 | # Eg. `field =` 33 | # 34 | if fieldSplit[1] == "": 35 | return (field & " ?") 36 | 37 | # 38 | # Otherwise just add the data as is, eg. `field = value` 39 | # Eg. `field = value` 40 | # 41 | else: 42 | return (field) 43 | 44 | # 45 | # Otherwise revert to default 46 | # Eg. `field = ?` 47 | # 48 | else: 49 | return (field & " = ?") 50 | 51 | 52 | 53 | proc updateSet(data: varargs[string]): string = 54 | ## Update the SET part of the query. 55 | ## 56 | ## => ["name", "age = "] 57 | ## => `SET name = ?, age = ?` 58 | ## 59 | for i, d in data: 60 | if i > 0: 61 | result.add(", ") 62 | result.add(updateSetFormat(d)) 63 | return result 64 | 65 | 66 | proc updateArray(arrayType: string, arrayAppend: varargs[string]): string = 67 | ## Format the arrayAppend part of the query. 68 | for i, d in arrayAppend: 69 | if i > 0: 70 | result.add(", ") 71 | result.add(d & " = " & arrayType & "(" & d & ", ?)") 72 | return result 73 | 74 | 75 | proc sqlUpdate*(table: string, data: varargs[string], where: varargs[string], args: ArgsContainer.query): SqlQuery = 76 | ## SQL builder for UPDATE queries 77 | ## Checks for NULL values 78 | 79 | var fields = "UPDATE " & table & " SET " 80 | for i, d in data: 81 | if i > 0: 82 | fields.add(", ") 83 | if args[i].isNull: 84 | fields.add(d & " = NULL") 85 | elif d.len() > 5 and d[(d.high-3)..d.high] == "NULL": 86 | fields.add(d) 87 | elif d[d.high-1..d.high] == "=": 88 | fields.add(d & " ?") 89 | else: 90 | fields.add(d & " = ?") 91 | 92 | var wes = " WHERE " 93 | for i, d in where: 94 | if i > 0: 95 | wes.add(" AND ") 96 | if d[d.high..d.high] == "=": 97 | wes.add(d & " ?") 98 | else: 99 | wes.add(d & " = ?") 100 | 101 | result = sql(fields & wes) 102 | 103 | 104 | proc sqlUpdate*( 105 | table: string, 106 | data: varargs[string], 107 | where: varargs[string], 108 | ): SqlQuery = 109 | ## SQL builder for UPDATE queries 110 | ## 111 | ## Can utilize custom equal signs and can also check for NULL values 112 | ## 113 | ## data => ["name", "age = ", "email = NULL"] 114 | ## where => ["id = ", "name IS NULL"] 115 | var fields: string 116 | fields.add(updateSet(data)) 117 | fields.add(sqlWhere(where)) 118 | result = sql("UPDATE " & table & " SET " & fields) 119 | 120 | 121 | proc sqlUpdateArrayRemove*( 122 | table: string, 123 | arrayRemove: varargs[string], 124 | where: varargs[string], 125 | ): SqlQuery = 126 | ## ARRAY_REMOVE 127 | var fields: string 128 | fields.add(updateArray("ARRAY_REMOVE", arrayRemove)) 129 | fields.add(sqlWhere(where)) 130 | result = sql("UPDATE " & table & " SET " & fields) 131 | 132 | 133 | proc sqlUpdateArrayAppend*( 134 | table: string, 135 | arrayAppend: varargs[string], 136 | where: varargs[string], 137 | ): SqlQuery = 138 | ## ARRAY_APPEND 139 | var fields: string 140 | fields.add(updateArray("ARRAY_APPEND", arrayAppend)) 141 | fields.add(sqlWhere(where)) 142 | result = sql("UPDATE " & table & " SET " & fields) 143 | 144 | 145 | 146 | # 147 | # 148 | # Macro based 149 | # 150 | # 151 | proc updateSet(data: NimNode): string = 152 | ## Update the SET part of the query. 153 | ## 154 | ## => ["name", "age = "] 155 | ## => `SET name = ?, age = ?` 156 | ## 157 | for i, v in data: 158 | # Convert NimNode to string 159 | let d = $v 160 | if i > 0: 161 | result.add(", ") 162 | result.add(updateSetFormat(d)) 163 | return result 164 | 165 | 166 | macro sqlUpdateMacro*( 167 | table: string, 168 | data: varargs[string], 169 | where: varargs[string] 170 | ): SqlQuery = 171 | ## SQL builder for UPDATE queries 172 | ## 173 | ## Can utilize custom equal signs and can also check for NULL values 174 | ## 175 | ## data => ["name", "age = ", "email = NULL"] 176 | ## where => ["id = ", "name IS NULL"] 177 | var fields: string 178 | fields.add(updateSet(data)) 179 | fields.add(sqlWhere(where)) 180 | result = parseStmt("sql(\"" & "UPDATE " & $table & " SET " & fields & "\")") 181 | 182 | 183 | # 184 | # Macro arrays 185 | # 186 | proc updateArray(arrayType: string, arrayAppend: NimNode): string = 187 | # Format the arrayAppend part of the query. 188 | 189 | for i, v in arrayAppend: 190 | let d = $v 191 | if d == "": 192 | continue 193 | 194 | if i > 0: 195 | result.add(", ") 196 | 197 | result.add(d & " = " & $arrayType & "(" & d & ", ?)") 198 | 199 | return result 200 | 201 | 202 | macro sqlUpdateMacroArrayRemove*( 203 | table: string, 204 | arrayRemove: varargs[string], 205 | where: varargs[string], 206 | ): SqlQuery = 207 | ## ARRAY_REMOVE macro 208 | var fields: string 209 | fields.add(updateArray("ARRAY_REMOVE", arrayRemove)) 210 | fields.add(sqlWhere(where)) 211 | result = parseStmt("sql(\"" & "UPDATE " & $table & " SET " & fields & "\")") 212 | 213 | 214 | macro sqlUpdateMacroArrayAppend*( 215 | table: string, 216 | arrayAppend: varargs[string], 217 | where: varargs[string], 218 | ): SqlQuery = 219 | ## ARRAY_APPEND macro 220 | var fields: string 221 | fields.add(updateArray("ARRAY_APPEND", arrayAppend)) 222 | fields.add(sqlWhere(where)) 223 | result = parseStmt("sql(\"" & "UPDATE " & $table & " SET " & fields & "\")") 224 | -------------------------------------------------------------------------------- /src/sqlbuilderpkg/utils.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | 4 | type 5 | ArgObj* = object ## Argument object 6 | val*: string 7 | isNull*: bool 8 | 9 | ArgsContainer* = object ## Argument container used for queries and args 10 | query*: seq[ArgObj] 11 | args*: seq[string] 12 | 13 | ArgsFormat = object 14 | use: bool 15 | column: string 16 | value: ArgObj 17 | 18 | SQLJoinType* = enum 19 | INNER 20 | LEFT 21 | RIGHT 22 | CROSS 23 | FULL 24 | 25 | SQLQueryType* = enum 26 | INSERT 27 | UPDATE 28 | 29 | SQLJoinObject* = ref object 30 | joinType*: SQLJoinType 31 | table*: string 32 | tableAs*: string 33 | on*: seq[string] 34 | 35 | 36 | when defined(test): 37 | var testout*: string 38 | 39 | 40 | const dbNullVal* = ArgObj(isNull: true) ## Global NULL value 41 | 42 | 43 | proc argType*(v: ArgObj): ArgObj = 44 | ## Checks if a ``ArgObj`` is NULL and return 45 | ## ``dbNullVal``. If it's not NULL, the passed 46 | ## ``ArgObj`` is returned. 47 | if v.isNull: 48 | return dbNullVal 49 | else: 50 | return v 51 | 52 | 53 | proc argType*(v: string | int): ArgObj = 54 | ## Transforms a string or int to a ``ArgObj`` 55 | var arg: ArgObj 56 | arg.isNull = false 57 | arg.val = $v 58 | return arg 59 | 60 | 61 | proc argTypeSetNull*(v: ArgObj): ArgObj = 62 | if v.isNull: 63 | return dbNullVal 64 | elif v.val.len() == 0: 65 | return dbNullVal 66 | else: 67 | return v 68 | 69 | 70 | proc argTypeSetNull*(v: string | int): ArgObj = 71 | var arg: ArgObj 72 | if len($v) == 0: 73 | return dbNullVal 74 | else: 75 | arg.isNull = false 76 | arg.val = $v 77 | return arg 78 | 79 | 80 | proc dbValOrNullString*(v: string | int): string = 81 | ## Return NULL obj if len() == 0, else return value obj 82 | if len($v) == 0: 83 | return "" 84 | return v 85 | 86 | 87 | proc dbValOrNull*(v: string | int): ArgObj = 88 | ## Return NULL obj if len() == 0, else return value obj 89 | if len($v) == 0: 90 | return dbNullVal 91 | var arg: ArgObj 92 | arg.val = $v 93 | arg.isNull = false 94 | return arg 95 | 96 | 97 | proc argFormat*(v: tuple): ArgsFormat = 98 | ## Formats the tuple, so int, float, bool, etc. can be used directly. 99 | result.use = v[0] 100 | result.column = v[1] 101 | result.value = argType(v[2]) 102 | 103 | 104 | template genArgs*[T](arguments: varargs[T, argType]): ArgsContainer = 105 | ## Create argument container for query and passed args. This allows for 106 | ## using NULL in queries. 107 | var argsContainer: ArgsContainer 108 | argsContainer.query = @[] 109 | argsContainer.args = @[] 110 | for arg in arguments: 111 | let argObject = argType(arg) 112 | if argObject.isNull: 113 | argsContainer.query.add(argObject) 114 | else: 115 | argsContainer.query.add(argObject) 116 | argsContainer.args.add(argObject.val) 117 | argsContainer 118 | 119 | 120 | template genArgsSetNull*[T](arguments: varargs[T, argType]): ArgsContainer = 121 | ## Create argument container for query and passed args 122 | var argsContainer: ArgsContainer 123 | argsContainer.query = @[] 124 | argsContainer.args = @[] 125 | for arg in arguments: 126 | let argObject = argTypeSetNull(arg) 127 | if argObject.isNull: 128 | argsContainer.query.add(argObject) 129 | else: 130 | argsContainer.query.add(argObject) 131 | argsContainer.args.add(argObject.val) 132 | argsContainer 133 | 134 | 135 | template genArgsColumns*[T](queryType: SQLQueryType, arguments: varargs[T, argFormat]): tuple[select: seq[string], args: ArgsContainer] = 136 | ## Create argument container for query and passed args and selecting which 137 | ## columns to update. It's and expansion of `genArgs()`, since you can 138 | ## decide which columns, there should be included. 139 | ## 140 | ## 141 | ## Lets say, that you are importing data from a spreadsheet with static 142 | ## columns, and you need to update your DB with the new values. 143 | ## 144 | ## If the spreadsheet contains an empty field, you can update your DB with the 145 | ## NULL value. But sometimes the spreadsheet only contains 5 out of the 10 146 | ## static columns. Now you don't know the values of the last 5 columns, 147 | ## which will result in updating the DB with NULL values. 148 | ## 149 | ## This template allows you to use the same query, but dynamic selecting only the 150 | ## columns which shall be updated. When importing your spreadsheet, check 151 | ## if the column exists (bool), and pass that as the `use: bool` param. If 152 | ## the column does not exists, it will be skipped in the query. 153 | ## 154 | ## The objects `ArgsFormat` is: 155 | ## (use: bool, column: string, value: ArgObj) 156 | var 157 | select: seq[string] 158 | argsContainer: ArgsContainer 159 | argsContainer.query = @[] 160 | argsContainer.args = @[] 161 | for arg in arguments: 162 | let argObject = argType(arg.value) 163 | if not arg.use: 164 | continue 165 | if ( 166 | queryType == SQLQueryType.INSERT and 167 | (argObject.isNull or argObject.val.len() == 0) 168 | ): 169 | continue 170 | if arg.column != "": 171 | select.add(arg.column) 172 | if argObject.isNull or argObject.val.len() == 0: 173 | argsContainer.query.add(dbNullVal) 174 | else: 175 | argsContainer.query.add(argObject) 176 | argsContainer.args.add(argObject.val) 177 | (select, argsContainer) 178 | -------------------------------------------------------------------------------- /src/sqlbuilderpkg/utils_private.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | 9 | import 10 | std/macros, 11 | std/strutils 12 | 13 | 14 | proc querycompare*(a, b: SqlQuery): bool = 15 | var 16 | a1: seq[string] 17 | b1: seq[string] 18 | for c in splitWhitespace(string(a).toLowerAscii()): 19 | a1.add($c) 20 | for c in splitWhitespace(string(b).toLowerAscii()): 21 | b1.add($c) 22 | 23 | if a1 != b1: 24 | echo "" 25 | echo "a1: ", string(a) 26 | echo "b1: ", string(b).replace("\n", " ").splitWhitespace().join(" ") 27 | echo "" 28 | 29 | return a1 == b1 30 | 31 | 32 | proc dbQuotePrivate*(s: string): string = 33 | ## DB quotes the string. 34 | result = "'" 35 | for c in items(s): 36 | case c 37 | of '\'': add(result, "''") 38 | of '\0': add(result, "\\0") 39 | else: add(result, c) 40 | add(result, '\'') 41 | 42 | 43 | 44 | proc formatWhereParams*(v: string): string = 45 | ## Format the WHERE part of the query. 46 | let 47 | field = v.strip() 48 | 49 | var fieldSplit: seq[string] 50 | if field.contains(" "): 51 | if field.contains("="): 52 | fieldSplit = field.split("=") 53 | elif field.contains("IS NOT"): 54 | fieldSplit = field.split("IS NOT") 55 | elif field.contains("IS"): 56 | fieldSplit = field.split("IS") 57 | elif field.contains("NOT IN"): 58 | fieldSplit = field.split("NOT IN") 59 | elif field.contains("IN"): 60 | fieldSplit = field.split("IN") 61 | elif field.contains("!="): 62 | fieldSplit = field.split("!=") 63 | elif field.contains("<="): 64 | fieldSplit = field.split("<=") 65 | elif field.contains(">="): 66 | fieldSplit = field.split(">=") 67 | elif field.contains("<"): 68 | fieldSplit = field.split("<") 69 | elif field.contains(">"): 70 | fieldSplit = field.split(">") 71 | else: 72 | fieldSplit = field.split("=") 73 | 74 | # 75 | # Does the data have a `=` sign? 76 | # 77 | if fieldSplit.len() == 2: 78 | # 79 | # If the data is only having equal but no value, insert a `?` sign 80 | # 81 | if fieldSplit[1] == "": 82 | return (field & " ?") 83 | # 84 | # Otherwise just add the data as is, eg. `field = value` 85 | # 86 | else: 87 | return (field) 88 | 89 | # 90 | # Otherwise revert to default 91 | # 92 | else: 93 | return (field & " = ?") 94 | 95 | 96 | 97 | proc hasIllegalFormats*(query: string): string = 98 | const illegalFormats = [ 99 | "WHERE AND", 100 | "WHERE OR", 101 | "AND AND", 102 | "OR OR", 103 | "AND OR", 104 | "OR AND", 105 | "WHERE IN", 106 | "WHERE =", 107 | "WHERE >", 108 | "WHERE <", 109 | "WHERE !", 110 | "WHERE LIKE", 111 | "WHERE NOT", 112 | "WHERE IS", 113 | "WHERE NULL", 114 | "WHERE ANY" 115 | ] 116 | 117 | for illegalFormat in illegalFormats: 118 | if illegalFormat in query: 119 | return illegalFormat 120 | 121 | 122 | # 123 | # Parentheses check 124 | # 125 | let 126 | parentheseOpen = count(query, "(") 127 | parentheseClose = count(query, ")") 128 | 129 | if parentheseOpen > parentheseClose: 130 | return "parentheses does not match. Missing closing parentheses. (" & $parentheseOpen & " open, " & $parentheseClose & " close)" 131 | elif parentheseOpen < parentheseClose: 132 | return "parentheses does not match. Missing opening parentheses. (" & $parentheseOpen & " open, " & $parentheseClose & " close)" 133 | 134 | 135 | # 136 | # Check for double insert 137 | # 138 | let noSpaces = query.strip().replace(" ", "") 139 | 140 | const nospaceBad = [ 141 | "??", 142 | "=?,?,", 143 | "=?,AND", 144 | "=?,OR", 145 | "AND?,", 146 | "OR?,", 147 | ] 148 | 149 | for b in nospaceBad: 150 | if b in noSpaces: 151 | return "wrong position of ?. (" & b & ")" 152 | 153 | 154 | 155 | # 156 | # Bad ? substittution 157 | # 158 | const badSubstitutions = [ 159 | "= ? AND ?", 160 | "= ? OR ?" 161 | ] 162 | const badSubstitutionsAccept = [ 163 | " = ? AND ? ANY ", 164 | " = ? AND ? IN ", 165 | " = ? AND ? = " 166 | ] 167 | for o in badSubstitutions: 168 | if o in query: 169 | var pass: bool 170 | for b in badSubstitutionsAccept: 171 | if b in query: 172 | pass = true 173 | break 174 | if not pass: 175 | return "bad ? substitution. (= ? AND ?)" 176 | 177 | 178 | 179 | proc sqlWhere*(where: varargs[string]): string = 180 | ## the WHERE part of the query. 181 | ## 182 | ## => ["name", "age = "] 183 | ## => `WHERE name = ?, age = ?` 184 | ## 185 | ## => ["name = ", "age >"] 186 | ## => `WHERE name = ?, age > ?` 187 | var wes = " WHERE " 188 | for i, v in where: 189 | if i > 0: 190 | wes.add(" AND ") 191 | wes.add(formatWhereParams(v)) 192 | return wes 193 | 194 | proc sqlWhere*(where: NimNode): string = 195 | ## the WHERE part of the query. 196 | ## 197 | ## => ["name", "age = "] 198 | ## => `WHERE name = ?, age = ?` 199 | ## 200 | ## => ["name = ", "age >"] 201 | ## => `WHERE name = ?, age > ?` 202 | var wes = " WHERE " 203 | for i, v in where: 204 | # Convert NimNode to string 205 | let d = $v 206 | if i > 0: 207 | wes.add(" AND ") 208 | wes.add(formatWhereParams(d)) 209 | return wes -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "..") 2 | switch("d", "test") 3 | switch("d", "dev") 4 | 5 | # switch("d", "sqlDeletemarker") 6 | # switch("d", "sqlDeletemarkerSeq=tasks") -------------------------------------------------------------------------------- /tests/create_db.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | when NimMajor >= 2: 4 | import 5 | db_connector/db_sqlite 6 | else: 7 | import 8 | std/db_sqlite 9 | 10 | 11 | const dbName = "tests/db_general.db" 12 | 13 | proc openDB*(): DbConn = 14 | return open(dbName, "", "", "") 15 | 16 | proc createDB*() = 17 | let db = openDB() 18 | 19 | db.exec(sql"DROP TABLE IF EXISTS my_table") 20 | db.exec(sql"""CREATE TABLE my_table ( 21 | id INTEGER PRIMARY KEY, 22 | name VARCHAR(50) NOT NULL, 23 | age INTEGER, 24 | ident TEXT, 25 | is_nimmer BOOLEAN 26 | )""") 27 | 28 | 29 | db.exec(sql"INSERT INTO my_table (id, name) VALUES (0, ?)", "Jack") 30 | 31 | for i in 1..5: 32 | db.exec(sql("INSERT INTO my_table (id, name, age, ident, is_nimmer) VALUES (?, ?, ?, ?, ?)"), $i, "Joe-" & $i, $i, "Nim", (if i <= 2: "true" else: "false")) 33 | 34 | for i in 6..10: 35 | db.exec(sql("INSERT INTO my_table (id, name, age, ident) VALUES (?, ?, ?, ?)"), $i, "Cathrine-" & $i, $i, "Lag") 36 | 37 | db.close() 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/custom_args/test_args.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) 2 | 3 | import 4 | std/unittest 5 | 6 | import 7 | src/sqlbuilder 8 | 9 | 10 | 11 | 12 | 13 | suite "test formats": 14 | 15 | test "genArgsColumns": 16 | let (s, a) = genArgsColumns(SQLQueryType.INSERT, (true, "name", ""), (true, "age", 30), (false, "nim", ""), (true, "", "154")) 17 | 18 | check s == ["age"] 19 | 20 | for k, v in a.query: 21 | if k == 0: 22 | check $v == """(val: "30", isNull: false)""" 23 | if k == 2: 24 | check $v == """(val: "154", isNull: false)""" 25 | 26 | for k, v in a.args: 27 | if k == 0: 28 | check $v == "30" 29 | if k == 2: 30 | check $v == "154" 31 | 32 | let a1 = sqlInsert("my-table", s, a.query) 33 | let a2 = sqlDelete("my-table", s, a.query) 34 | let a3 = sqlUpdate("my-table", s, ["id"], a.query) 35 | let a4 = sqlSelect("my-table", s, [""], ["id ="], "", "", "", a.query) 36 | 37 | 38 | test "genArgsSetNull": 39 | let b = genArgsSetNull("hje", "", "12") 40 | assert b.args == @["hje", "12"] 41 | assert b.query.len() == 3 42 | for k, v in b.query: 43 | if k == 0: 44 | assert $v == """(val: "hje", isNull: false)""" 45 | if k == 1: 46 | assert $v == """(val: "", isNull: true)""" 47 | if k == 2: 48 | assert $v == """(val: "12", isNull: false)""" 49 | 50 | test "genArgs": 51 | let b = genArgs("hje", "", "12") 52 | assert b.args == @["hje", "", "12"] 53 | assert b.query.len() == 3 54 | for k, v in b.query: 55 | if k == 0: 56 | assert $v == """(val: "hje", isNull: false)""" 57 | if k == 1: 58 | assert $v == """(val: "", isNull: false)""" 59 | if k == 2: 60 | assert $v == """(val: "12", isNull: false)""" 61 | 62 | test "genArgs with null": 63 | let b = genArgs("hje", dbNullVal, "12") 64 | assert b.args == @["hje", "12"] 65 | assert b.query.len() == 3 66 | for k, v in b.query: 67 | if k == 0: 68 | assert $v == """(val: "hje", isNull: false)""" 69 | if k == 1: 70 | assert $v == """(val: "", isNull: true)""" 71 | if k == 2: 72 | assert $v == """(val: "12", isNull: false)""" 73 | -------------------------------------------------------------------------------- /tests/delete/test_delete.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | when NimMajor >= 2: 4 | import 5 | db_connector/db_common 6 | else: 7 | import 8 | std/db_common 9 | 10 | import 11 | std/strutils, 12 | std/unittest 13 | 14 | import 15 | src/sqlbuilder, 16 | src/sqlbuilderpkg/utils_private 17 | 18 | 19 | suite "delete - normal": 20 | 21 | test "sqlDelete": 22 | var test: SqlQuery 23 | 24 | test = sqlDelete("my-table", ["name", "age"]) 25 | check querycompare(test, sql("DELETE FROM my-table WHERE name = ? AND age = ?")) 26 | 27 | test = sqlDelete("my-table", []) 28 | check querycompare(test, sql("DELETE FROM my-table")) 29 | 30 | 31 | test "sqlDeleteWhere": 32 | var test: SqlQuery 33 | 34 | test = sqlDelete("my-table", ["name !=", "age > "]) 35 | check querycompare(test, sql("DELETE FROM my-table WHERE name != ? AND age > ?")) 36 | 37 | 38 | test "sqlDelete with null manually": 39 | var test: SqlQuery 40 | 41 | test = sqlDelete("my-table", ["name IS NULL", "age"]) 42 | check querycompare(test, sql("DELETE FROM my-table WHERE name IS NULL AND age = ?")) 43 | 44 | 45 | 46 | 47 | suite "delete - macro": 48 | 49 | test "sqlDelete": 50 | var test: SqlQuery 51 | 52 | test = sqlDeleteMacro("my-table", ["name", "age"]) 53 | check querycompare(test, sql("DELETE FROM my-table WHERE name = ? AND age = ?")) 54 | 55 | 56 | test "sqlDeleteWhere": 57 | var test: SqlQuery 58 | 59 | test = sqlDeleteMacro("my-table", ["name !=", "age > "]) 60 | check querycompare(test, sql("DELETE FROM my-table WHERE name != ? AND age > ?")) 61 | 62 | 63 | test "sqlDelete with null manually": 64 | var test: SqlQuery 65 | 66 | test = sqlDeleteMacro("my-table", ["name IS NULL", "age"]) 67 | check querycompare(test, sql("DELETE FROM my-table WHERE name IS NULL AND age = ?")) 68 | 69 | 70 | suite "delete - genArgs": 71 | 72 | test "sqlDelete with genArgs": 73 | 74 | var a = genArgs("123", dbNullVal) 75 | 76 | var test = sqlDelete("tasksQQ", ["id =", "status IS"], a.query) 77 | 78 | check querycompare(test, sql("""DELETE FROM tasksQQ WHERE id = ? AND status IS ?""")) 79 | 80 | 81 | 82 | a = genArgs("123", dbNullVal, dbNullVal) 83 | 84 | test = sqlDelete("tasksQQ", ["id =", "status IS NOT", "phase IS"], a.query) 85 | 86 | check querycompare(test, sql("""DELETE FROM tasksQQ WHERE id = ? AND status IS NOT ? AND phase IS ?""")) 87 | 88 | 89 | 90 | test "sqlDelete with genArgsSetNull": 91 | 92 | var a = genArgsSetNull("123", "", "") 93 | 94 | var test = sqlDelete("tasksQQ", ["id =", "status IS NOT", "phase IS"], a.query) 95 | 96 | check querycompare(test, sql("""DELETE FROM tasksQQ WHERE id = ? AND status IS NOT ? AND phase IS ?""")) 97 | 98 | 99 | -------------------------------------------------------------------------------- /tests/importpackage/test_import1.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/strutils, 10 | std/unittest 11 | 12 | import 13 | src/sqlbuilderpkg/utils_private 14 | 15 | import 16 | ./test_sql_import_with_deletemarkers 17 | 18 | 19 | suite "delete marker - package import": 20 | 21 | test "useDeleteMarker = default": 22 | var test: SqlQuery 23 | 24 | test = sqlSelect( 25 | table = "tasks", 26 | select = @["id", "name", "description", "created", "updated", "completed"], 27 | where = @["id ="], 28 | ) 29 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE id = ? AND tasks.is_deleted IS NULL ")) 30 | 31 | test "useDeleteMarker = default": 32 | var test: SqlQuery 33 | 34 | test = sqlSelectConst( 35 | table = "tasks", 36 | select = @["id", "name", "description", "created", "updated", "completed"], 37 | where = @["id ="], 38 | joinargs = [] 39 | ) 40 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE id = ? AND tasks.is_deleted IS NULL ")) 41 | -------------------------------------------------------------------------------- /tests/importpackage/test_import2.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/strutils, 10 | std/unittest 11 | 12 | import 13 | src/sqlbuilderpkg/utils_private 14 | 15 | import 16 | ./test_sql_import_with_deletemarkers as sqlUno, 17 | ./test_sql_import_with_deletemarkers2 as sqlDos 18 | 19 | 20 | suite "delete marker - package import - first import sqlUno": 21 | 22 | test "useDeleteMarker = tasks": 23 | var test: SqlQuery 24 | 25 | test = sqlUno.sqlSelect( 26 | table = "tasks", 27 | select = @["id", "name", "description", "created", "updated", "completed"], 28 | where = @["id ="], 29 | ) 30 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE id = ? AND tasks.is_deleted IS NULL ")) 31 | 32 | test "useDeleteMarker = tasks (const)": 33 | var test: SqlQuery 34 | 35 | test = sqlUno.sqlSelectConst( 36 | table = "tasks", 37 | select = @["id", "name", "description", "created", "updated", "completed"], 38 | where = @["id ="], 39 | joinargs = [] 40 | ) 41 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE id = ? AND tasks.is_deleted IS NULL ")) 42 | 43 | 44 | suite "delete marker - package import - second import sqlDos": 45 | 46 | test "useDeleteMarker = project": 47 | var test: SqlQuery 48 | 49 | test = sqlDos.sqlSelect( 50 | table = "project", 51 | select = @["id", "name", "description", "created", "updated", "completed"], 52 | where = @["id ="], 53 | ) 54 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM project WHERE id = ? AND project.is_deleted IS NULL ")) 55 | 56 | test "useDeleteMarker = project (const)": 57 | var test: SqlQuery 58 | 59 | test = sqlDos.sqlSelectConst( 60 | table = "project", 61 | select = @["id", "name", "description", "created", "updated", "completed"], 62 | where = @["id ="], 63 | joinargs = [] 64 | ) 65 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM project WHERE id = ? AND project.is_deleted IS NULL ")) 66 | 67 | -------------------------------------------------------------------------------- /tests/importpackage/test_sql_import_with_deletemarkers.nim: -------------------------------------------------------------------------------- 1 | 2 | const tablesWithDeleteMarkerInit = ["tasks"] 3 | 4 | include src/sqlbuilder_include -------------------------------------------------------------------------------- /tests/importpackage/test_sql_import_with_deletemarkers2.nim: -------------------------------------------------------------------------------- 1 | 2 | const tablesWithDeleteMarkerInit = ["project"] 3 | 4 | include src/sqlbuilder_include -------------------------------------------------------------------------------- /tests/insert/test_insert.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | when NimMajor >= 2: 4 | import 5 | db_connector/db_common 6 | else: 7 | import 8 | std/db_common 9 | 10 | import 11 | std/strutils, 12 | std/unittest 13 | 14 | import 15 | src/sqlbuilder, 16 | src/sqlbuilderpkg/utils_private 17 | 18 | 19 | 20 | suite "insert - custom args": 21 | 22 | test "sqlInsert - dynamic columns": 23 | var test: SqlQuery 24 | 25 | let (s, a1) = genArgsColumns(SQLQueryType.INSERT, (true, "name", ""), (true, "age", 30), (false, "nim", ""), (true, "", "154")) 26 | test = sqlInsert("my-table", s, a1.query) 27 | check querycompare(test, sql("INSERT INTO my-table (age) VALUES (?)")) 28 | 29 | 30 | test "sqlInsert - setting null": 31 | var test: SqlQuery 32 | 33 | let a2 = genArgsSetNull("hje", "") 34 | test = sqlInsert("my-table", ["name", "age"], a2.query) 35 | check querycompare(test, sql("INSERT INTO my-table (name) VALUES (?)")) 36 | 37 | 38 | 39 | suite "insert - default": 40 | 41 | test "sqlInsert - default": 42 | var test: SqlQuery 43 | 44 | test = sqlInsert("my-table", ["name", "age"]) 45 | check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (?, ?)")) 46 | 47 | 48 | test "sqlInsert - with manual null": 49 | var test: SqlQuery 50 | 51 | test = sqlInsert("my-table", ["name", "age = NULL"]) 52 | check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (?, NULL)")) 53 | 54 | 55 | test "sqlInsert - with args check for null #1": 56 | var test: SqlQuery 57 | 58 | let vals = @["thomas", "30"] 59 | test = sqlInsert("my-table", ["name", "age"], vals) 60 | check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (?, ?)")) 61 | 62 | 63 | test "sqlInsert - with args check for null #2": 64 | var test: SqlQuery 65 | 66 | let vals = @["thomas", ""] 67 | test = sqlInsert("my-table", ["name", "age"], vals) 68 | check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (?, NULL)")) 69 | 70 | 71 | 72 | test "empty value transform to NULL #1": 73 | var test: SqlQuery 74 | 75 | test = sqlInsert("my-table", ["name", "age"], @["", "30"]) 76 | check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (NULL, ?)")) 77 | 78 | test "empty value transform to NULL #2": 79 | var test: SqlQuery 80 | 81 | test = sqlInsert("my-table", ["name", "age"], @["", ""]) 82 | check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (NULL, NULL)")) 83 | 84 | test "empty value transform to NULL #3": 85 | var test: SqlQuery 86 | 87 | test = sqlInsert("my-table", ["name", "age", "company", "ident"], @["john", "23", "", "ok"]) 88 | check querycompare(test, sql("INSERT INTO my-table (name, age, company, ident) VALUES (?, ?, NULL, ?)")) 89 | 90 | test "empty value transform to NULL #5": 91 | var test: SqlQuery 92 | 93 | test = sqlInsert("my-table", ["name", "age", "company", "ident"], @["", "", "", ""]) 94 | check querycompare(test, sql("INSERT INTO my-table (name, age, company, ident) VALUES (NULL, NULL, NULL, NULL)")) 95 | 96 | test "empty value transform to NULL #4": 97 | var test: SqlQuery 98 | 99 | test = sqlInsert("my-table", ["name", "age", "company", "ident"], @["john", "", "", "ok"]) 100 | check querycompare(test, sql("INSERT INTO my-table (name, age, company, ident) VALUES (?, NULL, NULL, ?)")) 101 | 102 | 103 | 104 | test "manual NULL": 105 | var test: SqlQuery 106 | 107 | test = sqlInsert("my-table", ["name", "age"], @["NULL", "30"]) 108 | check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (NULL, ?)")) 109 | 110 | 111 | suite "insert - macro": 112 | 113 | test "sqlInsert - default": 114 | var test: SqlQuery 115 | 116 | test = sqlInsertMacro("my-table", ["name", "age"]) 117 | check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (?, ?)")) 118 | 119 | 120 | test "sqlInsertMacro - with manual null": 121 | var test: SqlQuery 122 | 123 | test = sqlInsertMacro("my-table", ["name", "age = NULL"]) 124 | check querycompare(test, sql("INSERT INTO my-table (name, age) VALUES (?, NULL)")) 125 | -------------------------------------------------------------------------------- /tests/insert/test_insert_db.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | when NimMajor >= 2: 4 | import 5 | db_connector/db_sqlite 6 | else: 7 | import 8 | std/db_sqlite 9 | 10 | import 11 | std/strutils, 12 | std/unittest 13 | 14 | import 15 | src/sqlbuilder, 16 | src/sqlbuilderpkg/utils_private 17 | 18 | 19 | import tests/create_db 20 | 21 | 22 | 23 | 24 | suite "insert into db": 25 | test "empty value transform to NULL #5": 26 | 27 | createDB() 28 | 29 | let db = openDB() 30 | 31 | check tryInsertID(db, 32 | sqlInsert("my_table", 33 | ["name", "age", "ident"], 34 | @["john", "", ""]), @["john", "", ""] 35 | ) > 0 36 | 37 | echo tryInsertID(db, 38 | sqlInsert("my_table", 39 | ["name", "age", "ident"], 40 | @["john", "12", ""]), @["john", "", ""] 41 | ) 42 | 43 | 44 | -------------------------------------------------------------------------------- /tests/legacy_convert/test_legacy.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/strutils, 10 | std/unittest 11 | 12 | import 13 | src/sqlbuilder, 14 | src/sqlbuilderpkg/utils_private 15 | 16 | 17 | 18 | 19 | 20 | suite "legacy - sqlSelect(Convert)": 21 | 22 | 23 | test "legacy - sqlSelect - simple": 24 | let a2 = genArgsSetNull("hje", "", "123") 25 | let q1 = sqlSelect("my-table", ["name", "age"], [""], ["id ="], "", "", "", a2.query) 26 | check querycompare(q1, sql"SELECT name, age FROM my-table WHERE id = ? ") 27 | 28 | let a3 = genArgs("hje", "") 29 | let q2 = sqlSelect("my-table AS m", ["m.name", "m.age"], ["p ON p.id = m.id"], ["m.id ="], "", "", "", a3.query) 30 | check querycompare(q2, sql"SELECT m.name, m.age FROM my-table AS m LEFT JOIN p ON (p.id = m.id) WHERE m.id = ? ") 31 | 32 | let a4 = genArgs("hje", dbNullVal) 33 | let q3 = sqlSelect("my-table", ["name", "age"], [""], ["id ="], "", "", "", a4.query) 34 | check querycompare(q3, sql"SELECT name, age FROM my-table WHERE id = ? ") 35 | 36 | 37 | test "sqlSelect - #1": 38 | var test: SqlQuery 39 | 40 | test = sqlSelect("my-table", ["name", "age"], [""], ["id ="], "", "", "") 41 | 42 | check querycompare(test, sql("SELECT name, age FROM my-table WHERE id = ?")) 43 | 44 | 45 | test "sqlSelect - #2 - join": 46 | var test: SqlQuery 47 | 48 | test = sqlSelect("tasksQQ", ["tasksQQ.id", "tasksQQ.name"], ["project ON project.id = tasksQQ.project_id"], ["id ="], "", "", "") 49 | 50 | check querycompare(test, sql(""" 51 | SELECT 52 | tasksQQ.id, 53 | tasksQQ.name 54 | FROM 55 | tasksQQ 56 | LEFT JOIN project ON 57 | (project.id = tasksQQ.project_id) 58 | WHERE 59 | id = ? 60 | """)) 61 | 62 | 63 | test "sqlSelect - #3 - join with alias": 64 | var test: SqlQuery 65 | 66 | test = sqlSelect("tasksQQ", ["tasksQQ.id", "tasksQQ.name", "p.id"], ["project AS p ON p.id = tasksQQ.project_id"], ["tasksQQ.id ="], "", "", "") 67 | 68 | check querycompare(test, sql(""" 69 | SELECT 70 | tasksQQ.id, 71 | tasksQQ.name, 72 | p.id 73 | FROM 74 | tasksQQ 75 | LEFT JOIN project AS p ON 76 | (p.id = tasksQQ.project_id) 77 | WHERE 78 | tasksQQ.id = ? 79 | """)) 80 | 81 | 82 | test "sqlSelect - #4 - alias all the way": 83 | var test: SqlQuery 84 | 85 | test = sqlSelect("tasksQQ AS t", ["t.id", "t.name", "p.id"], ["project AS p ON p.id = t.project_id"], ["t.id ="], "", "", "") 86 | 87 | check querycompare(test, sql(""" 88 | SELECT 89 | t.id, 90 | t.name, 91 | p.id 92 | FROM 93 | tasksQQ AS t 94 | LEFT JOIN project AS p ON 95 | (p.id = t.project_id) 96 | WHERE 97 | t.id = ? 98 | """)) 99 | 100 | 101 | test "sqlSelect - #5 - alias all the way with IN": 102 | var test: SqlQuery 103 | 104 | test = sqlSelect("tasksQQ AS t", ["t.id", "t.name", "p.id"], ["project AS p ON p.id = t.project_id"], ["t.id ="], "2,4,6,7", "p.id", "ORDER BY t.name") 105 | 106 | check querycompare(test, sql(""" 107 | SELECT 108 | t.id, 109 | t.name, 110 | p.id 111 | FROM 112 | tasksQQ AS t 113 | LEFT JOIN project AS p ON 114 | (p.id = t.project_id) 115 | WHERE 116 | t.id = ? 117 | AND p.id in (2,4,6,7) 118 | ORDER BY 119 | t.name 120 | """)) 121 | 122 | 123 | test "sqlSelect - #6 - alias all the way with IN and delete marker": 124 | var test: SqlQuery 125 | 126 | test = sqlSelect("tasks AS t", ["t.id", "t.name", "p.id"], ["project AS p ON p.id = t.project_id"], ["t.id ="], "2,4,6,7", "p.id", "ORDER BY t.name", tablesWithDeleteMarker = ["tasks", "persons"]) 127 | 128 | check querycompare(test, sql(""" 129 | SELECT 130 | t.id, 131 | t.name, 132 | p.id 133 | FROM 134 | tasks AS t 135 | LEFT JOIN project AS p ON 136 | (p.id = t.project_id) 137 | WHERE 138 | t.id = ? 139 | AND p.id in (2,4,6,7) 140 | AND t.is_deleted IS NULL 141 | ORDER BY 142 | t.name 143 | """)) 144 | 145 | 146 | test "complex query": 147 | 148 | var test: SqlQuery 149 | 150 | test = sqlSelect("tasksitems AS tasks", 151 | [ 152 | "tasks.id", 153 | "tasks.name", 154 | "tasks.status", 155 | "tasks.created", 156 | "his.id", 157 | "his.name", 158 | "his.status", 159 | "his.created", 160 | "projects.id", 161 | "projects.name", 162 | "person.id", 163 | "person.name", 164 | "person.email" 165 | ], 166 | [ 167 | "history AS his ON his.id = tasks.hid AND his.status = 1", 168 | "projects ON projects.id = tasks.project_id AND projects.status = 1", 169 | "person ON person.id = tasks.person_id" 170 | ], 171 | [ 172 | "projects.id =", 173 | "tasks.status >" 174 | ], 175 | "1,2,3", 176 | "tasks.id", 177 | "ORDER BY tasks.created DESC" 178 | ) 179 | 180 | check querycompare(test, (sql(""" 181 | SELECT 182 | tasks.id, 183 | tasks.name, 184 | tasks.status, 185 | tasks.created, 186 | his.id, 187 | his.name, 188 | his.status, 189 | his.created, 190 | projects.id, 191 | projects.name, 192 | person.id, 193 | person.name, 194 | person.email 195 | FROM 196 | tasksitems AS tasks 197 | LEFT JOIN history AS his ON 198 | (his.id = tasks.hid AND his.status = 1) 199 | LEFT JOIN projects ON 200 | (projects.id = tasks.project_id AND projects.status = 1) 201 | LEFT JOIN person ON 202 | (person.id = tasks.person_id) 203 | WHERE 204 | projects.id = ? 205 | AND tasks.status > ? 206 | AND tasks.id in (1,2,3) 207 | ORDER BY 208 | tasks.created DESC 209 | """))) 210 | 211 | 212 | 213 | suite "legacy - sqlSelect(Convert) - genArgs": 214 | 215 | test "sqlSelect with genArgs - refactor 2022-01": 216 | 217 | var a = genArgs("123", dbNullVal) 218 | 219 | var test = sqlSelect("tasksQQ", ["tasksQQ.id", "tasksQQ.name"], [""], ["id =", "status IS"], "", "", "", a.query) 220 | 221 | check querycompare(test, sql("""SELECT tasksQQ.id, tasksQQ.name FROM tasksQQ WHERE id = ? AND status IS NULL""")) 222 | 223 | 224 | 225 | a = genArgs("123", dbNullVal, dbNullVal) 226 | 227 | test = sqlSelect("tasksQQ", ["tasksQQ.id", "tasksQQ.name"], [""], ["id =", "status IS NOT", "phase IS"], "", "", "", a.query) 228 | 229 | check querycompare(test, sql("""SELECT tasksQQ.id, tasksQQ.name FROM tasksQQ WHERE id = ? AND status IS NOT NULL AND phase IS NULL""")) 230 | 231 | 232 | 233 | test "sqlSelect with genArgsSetNull - refactor 2022-01": 234 | 235 | var a = genArgsSetNull("123", "", "") 236 | 237 | var test = sqlSelect("tasksQQ", ["tasksQQ.id", "tasksQQ.name"], [""], ["id =", "status IS NOT", "phase IS"], "", "", "", a.query) 238 | 239 | check querycompare(test, sql("""SELECT tasksQQ.id, tasksQQ.name FROM tasksQQ WHERE id = ? AND status IS NOT NULL AND phase IS NULL""")) 240 | 241 | 242 | 243 | 244 | suite "test sqlSelectMacro": 245 | 246 | test "sqlSelectMacro legacy - refactor 2022-01": 247 | 248 | let q1 = sqlSelectMacro( 249 | table = "my-table", 250 | data = ["name", "age"], 251 | left = [""], 252 | whereC = ["id ="], "", "", "") 253 | 254 | check querycompare(q1, sql("SELECT name, age FROM my-table WHERE id = ?")) 255 | 256 | 257 | 258 | 259 | suite "test various": 260 | 261 | test "xxx": 262 | 263 | let q1 = sqlSelect("locker", ["name"], [""], ["project_id =", "name =", "info ="], "", "", "") 264 | 265 | check querycompare(q1, sql("SELECT name FROM locker WHERE project_id = ? AND name = ? AND info = ?")) 266 | 267 | 268 | -------------------------------------------------------------------------------- /tests/legacy_convert/test_legacy_with_softdelete.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/strutils, 10 | std/unittest 11 | 12 | import 13 | src/sqlbuilderpkg/utils_private 14 | 15 | 16 | const tablesWithDeleteMarkerInit* = ["tasks", "history", "tasksitems"] 17 | include 18 | src/sqlbuilder_include 19 | 20 | 21 | # 22 | # Differs by using less fields in `tablesWithDeleteMarkerInit` 23 | # 24 | suite "legacy - sqlSelect(converter) - with new functionality to avoid regression": 25 | 26 | 27 | test "set tablesWithDeleteMarker": 28 | 29 | let test = sqlSelect("tasks AS t", ["t.id", "t.name", "p.id"], ["project AS p ON p.id = t.project_id"], ["t.id ="], "2,4,6,7", "p.id", "ORDER BY t.name") 30 | 31 | check querycompare(test, sql(""" 32 | SELECT 33 | t.id, 34 | t.name, 35 | p.id 36 | FROM 37 | tasks AS t 38 | LEFT JOIN project AS p ON 39 | (p.id = t.project_id) 40 | WHERE 41 | t.id = ? 42 | AND p.id in (2,4,6,7) 43 | AND t.is_deleted IS NULL 44 | ORDER BY 45 | t.name 46 | """)) 47 | 48 | 49 | test "existing is_deleted in where": 50 | 51 | let test = sqlSelect("tasks AS t", ["t.id", "t.name", "p.id"], ["project AS p ON p.id = t.project_id"], ["t.id ="], "2,4,6,7", "p.id", "AND tasks.is_deleted IS NULL ORDER BY t.name") 52 | 53 | check querycompare(test, sql(""" 54 | SELECT 55 | t.id, 56 | t.name, 57 | p.id 58 | FROM 59 | tasks AS t 60 | LEFT JOIN project AS p ON 61 | (p.id = t.project_id) 62 | WHERE 63 | t.id = ? 64 | AND p.id in (2,4,6,7) 65 | AND t.is_deleted IS NULL AND tasks.is_deleted IS NULL 66 | ORDER BY 67 | t.name 68 | """)) 69 | 70 | 71 | test "existing delete in where with alias": 72 | 73 | let test = sqlSelect("tasks AS t", ["t.id", "t.name", "p.id"], ["project AS p ON p.id = t.project_id"], ["t.id ="], "2,4,6,7", "p.id", "AND t.is_deleted IS NULL ORDER BY t.name") 74 | 75 | check querycompare(test, sql(""" 76 | SELECT 77 | t.id, 78 | t.name, 79 | p.id 80 | FROM 81 | tasks AS t 82 | LEFT JOIN project AS p ON 83 | (p.id = t.project_id) 84 | WHERE 85 | t.id = ? 86 | AND p.id in (2,4,6,7) 87 | AND t.is_deleted IS NULL AND t.is_deleted IS NULL 88 | ORDER BY 89 | t.name 90 | """)) 91 | 92 | 93 | test "existing delete in left join (double)": 94 | 95 | let test = sqlSelect("tasks AS t", ["t.id", "t.name", "p.id"], ["project AS p ON p.id = t.project_id", "persons ON persons.id = tasks.person_id AND persons.is_deleted IS NULL"], ["t.id ="], "2,4,6,7", "p.id", "ORDER BY t.name") 96 | 97 | check querycompare(test, sql(""" 98 | SELECT 99 | t.id, 100 | t.name, 101 | p.id 102 | FROM 103 | tasks AS t 104 | LEFT JOIN project AS p ON 105 | (p.id = t.project_id) 106 | LEFT JOIN persons ON 107 | (persons.id = tasks.person_id AND persons.is_deleted IS NULL) 108 | WHERE 109 | t.id = ? 110 | AND p.id in (2,4,6,7) 111 | AND t.is_deleted IS NULL 112 | ORDER BY 113 | t.name 114 | """)) 115 | 116 | 117 | test "add delete marker in left join": 118 | 119 | let test = sqlSelect("tasks AS t", ["t.id", "t.name", "p.id"], ["project AS p ON p.id = t.project_id", "persons ON persons.id = tasks.person_id"], ["t.id ="], "2,4,6,7", "p.id", "ORDER BY t.name") 120 | 121 | check querycompare(test, sql(""" 122 | SELECT 123 | t.id, 124 | t.name, 125 | p.id 126 | FROM 127 | tasks AS t 128 | LEFT JOIN project AS p ON 129 | (p.id = t.project_id) 130 | LEFT JOIN persons ON 131 | (persons.id = tasks.person_id) 132 | WHERE 133 | t.id = ? 134 | AND p.id in (2,4,6,7) 135 | AND t.is_deleted IS NULL 136 | ORDER BY 137 | t.name 138 | """)) 139 | 140 | 141 | test "complex query": 142 | 143 | var test: SqlQuery 144 | 145 | test = sqlSelect("tasksitems AS tasks", 146 | [ 147 | "tasks.id", 148 | "tasks.name", 149 | "tasks.status", 150 | "tasks.created", 151 | "his.id", 152 | "his.name", 153 | "his.status", 154 | "his.created", 155 | "projects.id", 156 | "projects.name", 157 | "person.id", 158 | "person.name", 159 | "person.email" 160 | ], 161 | [ 162 | "history AS his ON his.id = tasks.hid AND his.status = 1", 163 | "projects ON projects.id = tasks.project_id AND projects.status = 1", 164 | "person ON person.id = tasks.person_id" 165 | ], 166 | [ 167 | "projects.id =", 168 | "tasks.status >" 169 | ], 170 | "1,2,3", 171 | "tasks.id", 172 | "ORDER BY tasks.created DESC" 173 | ) 174 | 175 | check querycompare(test, (sql(""" 176 | SELECT 177 | tasks.id, 178 | tasks.name, 179 | tasks.status, 180 | tasks.created, 181 | his.id, 182 | his.name, 183 | his.status, 184 | his.created, 185 | projects.id, 186 | projects.name, 187 | person.id, 188 | person.name, 189 | person.email 190 | FROM 191 | tasksitems AS tasks 192 | LEFT JOIN history AS his ON 193 | (his.id = tasks.hid AND his.status = 1 AND his.is_deleted IS NULL) 194 | LEFT JOIN projects ON 195 | (projects.id = tasks.project_id AND projects.status = 1) 196 | LEFT JOIN person ON 197 | (person.id = tasks.person_id) 198 | WHERE 199 | projects.id = ? 200 | AND tasks.status > ? 201 | AND tasks.id in (1,2,3) 202 | AND tasks.is_deleted IS NULL 203 | ORDER BY 204 | tasks.created DESC 205 | """))) 206 | -------------------------------------------------------------------------------- /tests/legacy_convert/test_legacy_with_softdelete2.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | when NimMajor >= 2: 4 | import 5 | db_connector/db_common 6 | else: 7 | import 8 | std/db_common 9 | 10 | import 11 | std/strutils, 12 | std/unittest 13 | 14 | import 15 | src/sqlbuilderpkg/utils_private 16 | 17 | 18 | const tablesWithDeleteMarkerInit* = ["tasks", "history", "tasksitems", "persons", "actions", "project"] 19 | include 20 | src/sqlbuilder_include 21 | 22 | 23 | 24 | 25 | # 26 | # Differs by using more fields in `tablesWithDeleteMarkerInit` 27 | # 28 | suite "legacy - sqlSelect(converter) - with new functionality to avoid regression - #2": 29 | 30 | 31 | test "existing delete in left join (double) - delete marker from left join": 32 | 33 | let test = sqlSelect("tasks AS t", ["t.id", "t.name", "p.id"], ["invoice AS p ON p.id = t.invoice_id", "persons ON persons.id = tasks.person_id"], ["t.id ="], "2,4,6,7", "p.id", "ORDER BY t.name") 34 | 35 | check querycompare(test, sql(""" 36 | SELECT 37 | t.id, 38 | t.name, 39 | p.id 40 | FROM 41 | tasks AS t 42 | LEFT JOIN invoice AS p ON 43 | (p.id = t.invoice_id) 44 | LEFT JOIN persons ON 45 | (persons.id = tasks.person_id AND persons.is_deleted IS NULL) 46 | WHERE 47 | t.id = ? 48 | AND p.id in (2,4,6,7) 49 | AND t.is_deleted IS NULL 50 | ORDER BY 51 | t.name 52 | """)) 53 | 54 | 55 | test "add delete marker in left join - delete marker from left join": 56 | 57 | let test = sqlSelect("tasks AS t", ["t.id", "t.name", "p.id"], ["invoice AS p ON p.id = t.invoice_id", "persons ON persons.id = tasks.person_id"], ["t.id ="], "2,4,6,7", "p.id", "ORDER BY t.name") 58 | 59 | check querycompare(test, sql(""" 60 | SELECT 61 | t.id, 62 | t.name, 63 | p.id 64 | FROM 65 | tasks AS t 66 | LEFT JOIN invoice AS p ON 67 | (p.id = t.invoice_id) 68 | LEFT JOIN persons ON 69 | (persons.id = tasks.person_id AND persons.is_deleted IS NULL) 70 | WHERE 71 | t.id = ? 72 | AND p.id in (2,4,6,7) 73 | AND t.is_deleted IS NULL 74 | ORDER BY 75 | t.name 76 | """)) 77 | 78 | 79 | test "set left join without AS": 80 | 81 | let test = sqlSelect("tasks", ["t.id", "t.name", "invoice.id"], ["persons ON persons.id = t.persons_id"], ["t.id ="], "2,4,6,7", "invoice.id", "ORDER BY t.name") 82 | 83 | check querycompare(test, sql(""" 84 | SELECT 85 | t.id, 86 | t.name, 87 | invoice.id 88 | FROM 89 | tasks 90 | LEFT JOIN persons ON 91 | (persons.id = t.persons_id AND persons.is_deleted IS NULL) 92 | WHERE 93 | t.id = ? 94 | AND invoice.id in (2,4,6,7) 95 | AND tasks.is_deleted IS NULL 96 | ORDER BY 97 | t.name 98 | """)) 99 | 100 | 101 | -------------------------------------------------------------------------------- /tests/query_calls/test_query_calls.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | 4 | when NimMajor >= 2: 5 | import db_connector/db_sqlite 6 | else: 7 | import std/db_sqlite 8 | 9 | import 10 | std/logging, 11 | std/strutils, 12 | std/unittest 13 | 14 | import 15 | src/sqlbuilderpkg/query_calls 16 | 17 | import 18 | tests/create_db 19 | 20 | 21 | var consoleLog = newConsoleLogger() 22 | addHandler(consoleLog) 23 | 24 | 25 | 26 | # 27 | # Set up a test database 28 | # 29 | createDB() 30 | let db = openDB() 31 | 32 | for i in 1..5: 33 | db.exec(sql("INSERT INTO my_table (name, age, ident, is_nimmer) VALUES (?, ?, ?, ?)"), "Call-" & $i, $i, "Call", (if i <= 2: "true" else: "false")) 34 | 35 | 36 | 37 | suite "Query calls": 38 | 39 | test "getRowTry() - with match": 40 | let row = getRowTry(db, sql("SELECT name, age, ident, is_nimmer FROM my_table WHERE name = ?"), ["Call-1"]) 41 | check row == @["Call-1", "1", "Call", "true"] 42 | 43 | test "getRowTry() - without match": 44 | let row = getRowTry(db, sql("SELECT name, age, ident, is_nimmer FROM my_table WHERE name = ?"), ["Call-"]) 45 | check row == @["", "", "", ""] 46 | 47 | test "getRowTry() - missing args": 48 | let row = getRowTry(db, sql("SELECT name, age, ident, is_nimmer FROM my_table WHERE name = ?"), []) 49 | check row.len() == 0 50 | 51 | test "getRowTry() - missing args auto fill": 52 | let row = getRowTry(db, sql("SELECT name, age, ident, is_nimmer FROM my_table WHERE name = ?"), [], fillIfNull = 4) 53 | check row == @["", "", "", ""] -------------------------------------------------------------------------------- /tests/select/test_select.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/strutils, 10 | std/unittest 11 | 12 | import 13 | src/sqlbuilder, 14 | src/sqlbuilderpkg/utils_private 15 | 16 | 17 | 18 | 19 | 20 | suite "test sqlSelect": 21 | 22 | test "useDeleteMarker = false": 23 | var test: SqlQuery 24 | 25 | test = sqlSelect( 26 | table = "tasks", 27 | select = @["id", "name", "description", "created", "updated", "completed"], 28 | where = @["id ="], 29 | useDeleteMarker = false 30 | ) 31 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE id = ? ")) 32 | 33 | 34 | test "from using AS ": 35 | var test: SqlQuery 36 | 37 | test = sqlSelect( 38 | table = "tasks", 39 | tableAs = "t", 40 | select = @["id", "name", "description", "created", "updated", "completed"], 41 | where = @["id ="], 42 | useDeleteMarker = false 43 | ) 44 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks AS t WHERE id = ? ")) 45 | 46 | 47 | test = sqlSelect( 48 | table = "tasks", 49 | tableAs = "t", 50 | select = @["t.id", "t.name", "t.description", "t.created", "t.updated", "t.completed"], 51 | where = @["t.id ="], 52 | useDeleteMarker = false 53 | ) 54 | check querycompare(test, sql("SELECT t.id, t.name, t.description, t.created, t.updated, t.completed FROM tasks AS t WHERE t.id = ? ")) 55 | 56 | 57 | test "from using AS inline ": 58 | var test: SqlQuery 59 | 60 | test = sqlSelect( 61 | table = "tasks AS t", 62 | select = @["id", "name", "description", "created", "updated", "completed"], 63 | where = @["id ="], 64 | useDeleteMarker = false 65 | ) 66 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks AS t WHERE id = ? ")) 67 | 68 | 69 | 70 | test "WHERE statements: general": 71 | var test: SqlQuery 72 | 73 | test = sqlSelect( 74 | table = "tasks", 75 | tableAs = "t", 76 | select = @["id", "name", "description", "created", "updated", "completed"], 77 | where = @["id =", "name !=", "updated >", "completed IS", "description LIKE"], 78 | useDeleteMarker = false 79 | ) 80 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks AS t WHERE id = ? AND name != ? AND updated > ? AND completed IS ? AND description LIKE ? ")) 81 | check string(test).count("?") == 5 82 | 83 | 84 | test = sqlSelect( 85 | table = "tasks", 86 | tableAs = "t", 87 | select = @["id", "name", "description", "created", "updated", "completed"], 88 | where = @["id =", "name !=", "updated >", "completed IS", "description LIKE"], 89 | customSQL = "AND name != 'test' AND created > ? ", 90 | useDeleteMarker = false 91 | ) 92 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks AS t WHERE id = ? AND name != ? AND updated > ? AND completed IS ? AND description LIKE ? AND name != 'test' AND created > ? ")) 93 | check string(test).count("?") == 6 94 | 95 | 96 | test "WHERE statements: <=": 97 | var test: SqlQuery 98 | test = sqlSelect( 99 | table = "tasks", 100 | select = @["id", "name", "description", "created", "updated", "completed"], 101 | where = @["id =", "name !=", "updated <= NOW()", "completed IS", "description LIKE"], 102 | useDeleteMarker = false 103 | ) 104 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE id = ? AND name != ? AND updated <= NOW() AND completed IS ? AND description LIKE ? ")) 105 | 106 | 107 | 108 | 109 | test "WHERE statements: = ANY(...)": 110 | var test: SqlQuery 111 | 112 | test = sqlSelect( 113 | table = "tasks", 114 | tableAs = "t", 115 | select = @["id", "ids_array"], 116 | where = @["id =", "= ANY(ids_array)"], 117 | useDeleteMarker = false 118 | ) 119 | check querycompare(test, sql("SELECT id, ids_array FROM tasks AS t WHERE id = ? AND ? = ANY(ids_array) ")) 120 | check string(test).count("?") == 2 121 | 122 | 123 | 124 | test "WHERE statements: x IN y": 125 | var test: SqlQuery 126 | 127 | test = sqlSelect( 128 | table = "tasks", 129 | tableAs = "t", 130 | select = @["id", "ids_array"], 131 | where = @["id =", "IN (ids_array)"], 132 | useDeleteMarker = false 133 | ) 134 | check querycompare(test, sql("SELECT id, ids_array FROM tasks AS t WHERE id = ? AND ? IN (ids_array) ")) 135 | check string(test).count("?") == 2 136 | 137 | 138 | test = sqlSelect( 139 | table = "tasks", 140 | tableAs = "t", 141 | select = @["id", "ids_array"], 142 | where = @["id =", "id IN"], 143 | useDeleteMarker = false 144 | ) 145 | check querycompare(test, sql("SELECT id, ids_array FROM tasks AS t WHERE id = ? AND id IN (?) ")) 146 | check string(test).count("?") == 2 147 | 148 | 149 | 150 | test "WHERE statements: `is NULL` ": 151 | var test: SqlQuery 152 | 153 | test = sqlSelect( 154 | table = "tasks", 155 | tableAs = "t", 156 | select = @["id", "name", "description", "created", "updated", "completed"], 157 | where = @["id =", "name != NULL", "description = NULL"], 158 | useDeleteMarker = false 159 | ) 160 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks AS t WHERE id = ? AND name != NULL AND description = NULL ")) 161 | check string(test).count("?") == 1 162 | 163 | 164 | test = sqlSelect( 165 | table = "tasks", 166 | tableAs = "t", 167 | select = @["id", "name", "description", "created", "updated", "completed"], 168 | where = @["id =", "name !=", "description = NULL"], 169 | useDeleteMarker = false 170 | ) 171 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks AS t WHERE id = ? AND name != ? AND description = NULL ")) 172 | check string(test).count("?") == 2 173 | 174 | 175 | test "SELECT with SUM and COUNT": 176 | var test: SqlQuery 177 | 178 | test = sqlSelect( 179 | table = "checklist_table", 180 | tableAs = "cht", 181 | select = [ 182 | "cht.c_id", 183 | "SUM(CASE WHEN cht.status IS NULL THEN 1 ELSE 0 END) as null", 184 | "SUM(CASE WHEN cht.status = 0 THEN 1 ELSE 0 END) as zero", 185 | "SUM(CASE WHEN cht.status = 1 THEN 1 ELSE 0 END) as one", 186 | "SUM(CASE WHEN cht.status = 2 THEN 1 ELSE 0 END) as two", 187 | "COUNT(cht.id) as total", 188 | "SUM(CASE WHEN cht.extra_check IS TRUE AND cht.extra_approve IS TRUE THEN 1 ELSE 0 END) as extraApproved", 189 | "SUM(CASE WHEN cht.extra_check IS TRUE AND cht.extra_approve IS False THEN 1 ELSE 0 END) as extraFailed", 190 | "SUM(CASE WHEN cht.extra_check IS TRUE AND cht.extra_approve IS NULL AND cht.status = 1 THEN 1 ELSE 0 END) as extraAwaiting", 191 | ], 192 | where = [ 193 | "cht.project_id =", 194 | "cht.type =" 195 | ], 196 | joinargs = [ 197 | (table: "checklist", tableAs: "c", on: @["c.id = cht.c_id"]) 198 | ], 199 | customSQL = "GROUP BY cht.c_id" 200 | ) 201 | check querycompare(test, sql(""" 202 | select 203 | cht.c_id, 204 | SUM(case when cht.status is null then 1 else 0 end) as null, 205 | SUM(case when cht.status = 0 then 1 else 0 end) as zero, 206 | SUM(case when cht.status = 1 then 1 else 0 end) as one, 207 | SUM(case when cht.status = 2 then 1 else 0 end) as two, 208 | COUNT(cht.id) as total, 209 | SUM(case when cht.extra_check is true and cht.extra_approve is true then 1 else 0 end) as extraApproved, 210 | SUM(case when cht.extra_check is true and cht.extra_approve is false then 1 else 0 end) as extraFailed, 211 | SUM(case when cht.extra_check is true and cht.extra_approve is null and cht.status = 1 then 1 else 0 end) as extraAwaiting 212 | from 213 | checklist_table as cht 214 | left join checklist as c on 215 | (c.id = cht.c_id) 216 | where 217 | cht.project_id = ? 218 | and cht.type = ? 219 | group by 220 | cht.c_id 221 | """)) 222 | 223 | 224 | suite "test sqlSelect - joins": 225 | 226 | test "LEFT JOIN [no values] using empty []": 227 | var test: SqlQuery 228 | 229 | test = sqlSelect( 230 | table = "tasks", 231 | tableAs = "t", 232 | select = @["id", "name"], 233 | where = @["id ="], 234 | joinargs = @[], 235 | useDeleteMarker = false 236 | ) 237 | check querycompare(test, sql("SELECT id, name FROM tasks AS t WHERE id = ? ")) 238 | 239 | test "LEFT JOIN [no values] using varargs instead of seq": 240 | var test: SqlQuery 241 | 242 | test = sqlSelect( 243 | table = "tasks", 244 | tableAs = "t", 245 | select = ["id", "name"], 246 | where = ["id ="], 247 | joinargs = [], 248 | useDeleteMarker = false 249 | ) 250 | check querycompare(test, sql("SELECT id, name FROM tasks AS t WHERE id = ? ")) 251 | 252 | test "LEFT JOIN using AS values with varargs": 253 | var test: SqlQuery 254 | 255 | test = sqlSelect( 256 | table = "tasks", 257 | tableAs = "t", 258 | select = ["id", "name"], 259 | where = ["id ="], 260 | joinargs = [(table: "projects", tableAs: "p", on: @["p.id = t.project_id", "p.status = 1"])], 261 | useDeleteMarker = false 262 | ) 263 | check querycompare(test, sql("SELECT id, name FROM tasks AS t LEFT JOIN projects AS p ON (p.id = t.project_id AND p.status = 1) WHERE id = ? ")) 264 | 265 | test "LEFT JOIN using AS values": 266 | var test: SqlQuery 267 | 268 | test = sqlSelect( 269 | table = "tasks", 270 | tableAs = "t", 271 | select = @["id", "name"], 272 | where = @["id ="], 273 | joinargs = @[(table: "projects", tableAs: "p", on: @["p.id = t.project_id", "p.status = 1"])], 274 | useDeleteMarker = false 275 | ) 276 | check querycompare(test, sql("SELECT id, name FROM tasks AS t LEFT JOIN projects AS p ON (p.id = t.project_id AND p.status = 1) WHERE id = ? ")) 277 | 278 | test "LEFT JOIN (default)": 279 | var test: SqlQuery 280 | 281 | test = sqlSelect( 282 | table = "tasks", 283 | tableAs = "t", 284 | select = @["id", "name"], 285 | where = @["id ="], 286 | joinargs = @[(table: "projects", tableAs: "", on: @["projects.id = t.project_id", "projects.status = 1"])], 287 | useDeleteMarker = false 288 | ) 289 | check querycompare(test, sql("SELECT id, name FROM tasks AS t LEFT JOIN projects ON (projects.id = t.project_id AND projects.status = 1) WHERE id = ? ")) 290 | 291 | test "LEFT JOIN (default) from table as inline": 292 | var test: SqlQuery 293 | 294 | test = sqlSelect( 295 | table = "tasks AS t", 296 | select = @["id", "name"], 297 | where = @["id ="], 298 | joinargs = @[(table: "projects", tableAs: "", on: @["projects.id = t.project_id", "projects.status = 1"])], 299 | useDeleteMarker = false 300 | ) 301 | check querycompare(test, sql("SELECT id, name FROM tasks AS t LEFT JOIN projects ON (projects.id = t.project_id AND projects.status = 1) WHERE id = ? ")) 302 | 303 | 304 | test "INNER JOIN": 305 | var test: SqlQuery 306 | 307 | test = sqlSelect( 308 | table = "tasks", 309 | tableAs = "t", 310 | select = @["id", "name"], 311 | where = @["id ="], 312 | joinargs = @[(table: "projects", tableAs: "", on: @["projects.id = t.project_id", "projects.status = 1"])], 313 | jointype = INNER, 314 | useDeleteMarker = false 315 | ) 316 | 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 = ? ")) 317 | 318 | test "CROSS JOIN": 319 | var test: SqlQuery 320 | 321 | test = sqlSelect( 322 | table = "a", 323 | select = @["id"], 324 | joinargs = @[(table: "b", tableAs: "", on: @["a.id = b.id"])], 325 | jointype = CROSS, 326 | useDeleteMarker = false 327 | ) 328 | check querycompare(test, sql("SELECT id FROM a CROSS JOIN b ON (a.id = b.id)")) 329 | 330 | 331 | 332 | suite "test sqlSelect - deletemarkers / softdelete": 333 | 334 | 335 | test "deletemarkers from seq": 336 | var test: SqlQuery 337 | let tableWithDeleteMarkerLet = @["tasks", "history", "tasksitems"] 338 | 339 | test = sqlSelect( 340 | table = "tasks", 341 | select = @["id", "name"], 342 | where = @["id ="], 343 | joinargs = @[(table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"])], 344 | tablesWithDeleteMarker = tableWithDeleteMarkerLet 345 | ) 346 | check querycompare(test, sql("SELECT id, name FROM tasks LEFT JOIN projects ON (projects.id = tasks.project_id AND projects.status = 1) WHERE id = ? AND tasks.is_deleted IS NULL ")) 347 | 348 | 349 | 350 | test "deletemarkers from seq - using full name for deletemarker table in LEFT JOIN": 351 | var test: SqlQuery 352 | let tableWithDeleteMarkerLet = @["tasks", "history", "tasksitems"] 353 | 354 | test = sqlSelect( 355 | table = "tasks", 356 | select = @["id", "name"], 357 | where = @["id ="], 358 | joinargs = @[(table: "history", tableAs: "", on: @["history.id = tasks.hid"])], 359 | tablesWithDeleteMarker = tableWithDeleteMarkerLet 360 | ) 361 | check querycompare(test, sql("SELECT id, name FROM tasks LEFT JOIN history ON (history.id = tasks.hid AND history.is_deleted IS NULL) WHERE id = ? AND tasks.is_deleted IS NULL ")) 362 | 363 | 364 | test "deletemarkers from seq - using ALIAS for deletemarker table in LEFT JOIN": 365 | var test: SqlQuery 366 | let tableWithDeleteMarkerLet = @["tasks", "history", "tasksitems"] 367 | 368 | test = sqlSelect( 369 | table = "tasks", 370 | select = @["tasks.id", "tasks.name"], 371 | where = @["tasks.id ="], 372 | joinargs = @[(table: "history", tableAs: "his", on: @["his.id = tasks.hid"])], 373 | tablesWithDeleteMarker = tableWithDeleteMarkerLet 374 | ) 375 | check querycompare(test, sql("SELECT tasks.id, tasks.name FROM tasks LEFT JOIN history AS his ON (his.id = tasks.hid AND his.is_deleted IS NULL) WHERE tasks.id = ? AND tasks.is_deleted IS NULL ")) 376 | 377 | 378 | 379 | test "deletemarkers on the fly": 380 | var test: SqlQuery 381 | 382 | test = sqlSelect( 383 | table = "tasks", 384 | select = @["id", "name"], 385 | where = @["id ="], 386 | joinargs = @[(table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"])], 387 | tablesWithDeleteMarker = @["tasks", "history", "tasksitems"] 388 | ) 389 | check querycompare(test, sql("SELECT id, name FROM tasks LEFT JOIN projects ON (projects.id = tasks.project_id AND projects.status = 1) WHERE id = ? AND tasks.is_deleted IS NULL ")) 390 | 391 | 392 | 393 | test "custom deletemarker override": 394 | var test: SqlQuery 395 | let tableWithDeleteMarkerLet = @["tasks", "history", "tasksitems"] 396 | 397 | test = sqlSelect( 398 | table = "tasks", 399 | select = @["id", "name"], 400 | where = @["id ="], 401 | joinargs = @[(table: "history", tableAs: "", on: @["history.id = tasks.hid"])], 402 | tablesWithDeleteMarker = tableWithDeleteMarkerLet, 403 | deleteMarker = ".deleted_at = 543234563" 404 | ) 405 | check querycompare(test, sql("SELECT id, name FROM tasks LEFT JOIN history ON (history.id = tasks.hid AND history.deleted_at = 543234563) WHERE id = ? AND tasks.deleted_at = 543234563 ")) 406 | 407 | 408 | 409 | test "complex query": 410 | var test: SqlQuery 411 | 412 | let tableWithDeleteMarkerLet = @["history", "tasksitems"] 413 | 414 | 415 | test = sqlSelect( 416 | table = "tasksitems", 417 | tableAs = "tasks", 418 | select = [ 419 | "tasks.id", 420 | "tasks.name", 421 | "tasks.status", 422 | "tasks.created", 423 | "his.id", 424 | "his.name", 425 | "his.status", 426 | "his.created", 427 | "projects.id", 428 | "projects.name", 429 | "person.id", 430 | "person.name", 431 | "person.email" 432 | ], 433 | where = [ 434 | "projects.id =", 435 | "tasks.status >" 436 | ], 437 | joinargs = [ 438 | (table: "history", tableAs: "his", on: @["his.id = tasks.hid", "his.status = 1"]), 439 | (table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"]), 440 | (table: "person", tableAs: "", on: @["person.id = tasks.person_id"]) 441 | ], 442 | whereInField = "tasks.id", 443 | whereInValue = ["1", "2", "3"], 444 | customSQL = "ORDER BY tasks.created DESC", 445 | tablesWithDeleteMarker = tableWithDeleteMarkerLet 446 | ) 447 | check querycompare(test, (sql(""" 448 | SELECT 449 | tasks.id, 450 | tasks.name, 451 | tasks.status, 452 | tasks.created, 453 | his.id, 454 | his.name, 455 | his.status, 456 | his.created, 457 | projects.id, 458 | projects.name, 459 | person.id, 460 | person.name, 461 | person.email 462 | FROM 463 | tasksitems AS tasks 464 | LEFT JOIN history AS his ON 465 | (his.id = tasks.hid AND his.status = 1 AND his.is_deleted IS NULL) 466 | LEFT JOIN projects ON 467 | (projects.id = tasks.project_id AND projects.status = 1) 468 | LEFT JOIN person ON 469 | (person.id = tasks.person_id) 470 | WHERE 471 | projects.id = ? 472 | AND tasks.status > ? 473 | AND tasks.id in (1,2,3) 474 | AND tasks.is_deleted IS NULL 475 | ORDER BY 476 | tasks.created DESC 477 | """))) 478 | 479 | 480 | 481 | suite "test where cases custom formatting": 482 | 483 | test "where OR": 484 | var test: SqlQuery 485 | 486 | const 487 | table = "tasks" 488 | select = ["id", "name", "description", "created", "updated", "completed"] 489 | where = ["id = ? OR id = ?"] 490 | 491 | let 492 | resNormal = sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE (id = ? OR id = ?) ") 493 | resPrepared = sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE (id = $1 OR id = $2) ") 494 | 495 | # Normal 496 | test = sqlSelect( 497 | table = table, 498 | select = select, 499 | where = where, 500 | useDeleteMarker = false, 501 | usePrepared = false 502 | ) 503 | check querycompare(test, resNormal) 504 | 505 | 506 | # Prepared 507 | test = sqlSelect( 508 | table = table, 509 | select = select, 510 | where = where, 511 | useDeleteMarker = false, 512 | usePrepared = true 513 | ) 514 | check querycompare(test, resPrepared) 515 | 516 | 517 | # Macro normal 518 | test = sqlSelectConst( 519 | table = "tasks", 520 | select = select, 521 | where = where, 522 | joinargs = [], 523 | useDeleteMarker = false, 524 | usePrepared = false 525 | ) 526 | check querycompare(test, resNormal) 527 | 528 | 529 | # Macro prepared 530 | test = sqlSelectConst( 531 | table = "tasks", 532 | select = select, 533 | where = where, 534 | joinargs = [], 535 | useDeleteMarker = false, 536 | usePrepared = true 537 | ) 538 | check querycompare(test, resPrepared) 539 | 540 | 541 | test "where OR OR": 542 | var test: SqlQuery 543 | 544 | test = sqlSelect( 545 | table = "tasks", 546 | select = @["id", "name", "description", "created", "updated", "completed"], 547 | where = @["id = ? OR name = ? OR description = ?"], 548 | useDeleteMarker = false 549 | ) 550 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE (id = ? OR name = ? OR description = ?) ")) 551 | 552 | 553 | test "where OR OR parentheses": 554 | var test: SqlQuery 555 | 556 | test = sqlSelect( 557 | table = "tasks", 558 | select = @["id", "name", "description", "created", "updated", "completed"], 559 | where = @["id = ? OR (name = ? OR description = ?)"], 560 | useDeleteMarker = false 561 | ) 562 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE (id = ? OR (name = ? OR description = ?)) ")) 563 | 564 | 565 | test "where AND OR parentheses": 566 | var test: SqlQuery 567 | 568 | test = sqlSelect( 569 | table = "tasks", 570 | select = @["id", "name", "description", "created", "updated", "completed"], 571 | where = @["id = ? AND (name = ? OR description = ?)"], 572 | useDeleteMarker = false 573 | ) 574 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE (id = ? AND (name = ? OR description = ?)) ")) 575 | 576 | 577 | test "where OR OR parentheses AND ? = ANY(...)": 578 | var test: SqlQuery 579 | const 580 | table = "tasks" 581 | select = @["id", "name", "description", "created", "updated", "completed"] 582 | where = @["id = ? OR (name = ? OR description = ?) AND ? = ANY(ids_array)"] 583 | 584 | let 585 | resNormal = sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE (id = ? OR (name = ? OR description = ?) AND ? = ANY(ids_array)) ") 586 | resPrepared = sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE (id = $1 OR (name = $2 OR description = $3) AND $4 = ANY(ids_array)) ") 587 | 588 | test = sqlSelect( 589 | table = table, 590 | select = select, 591 | where = where, 592 | useDeleteMarker = false, 593 | usePrepared = false 594 | ) 595 | check querycompare(test, resNormal) 596 | 597 | 598 | test = sqlSelect( 599 | table = table, 600 | select = select, 601 | where = where, 602 | useDeleteMarker = false, 603 | usePrepared = true 604 | ) 605 | check querycompare(test, resPrepared) 606 | 607 | 608 | test "where x = 'y'": 609 | 610 | let test = sqlSelect( 611 | table = "history", 612 | tableAs = "h", 613 | select = [ 614 | "h.uuid", 615 | "h.text", 616 | "h.creation", 617 | "person.name" 618 | ], 619 | where = [ 620 | "h.project_id =", 621 | "h.item_id =", 622 | "h.element = 'tasks'", 623 | ], 624 | joinargs = [ 625 | (table: "person", tableAs: "person", on: @["person.id = h.user_id"]), 626 | ], 627 | customSQL = "ORDER BY h.creation DESC", 628 | ) 629 | 630 | check querycompare(test, sql("SELECT h.uuid, h.text, h.creation, person.name FROM history AS h LEFT JOIN person ON (person.id = h.user_id) WHERE h.project_id = ? AND h.item_id = ? AND h.element = 'tasks' ORDER BY h.creation DESC")) 631 | 632 | 633 | test "where x = 'y' and x = 'y' and x = ::int": 634 | 635 | let test = sqlSelect( 636 | table = "history", 637 | tableAs = "h", 638 | select = [ 639 | "h.uuid", 640 | "h.text", 641 | "h.creation", 642 | "person.name" 643 | ], 644 | where = [ 645 | "h.project_id =", 646 | "h.item_id = 33", 647 | "h.element = 'tasks'", 648 | "h.data = 'special'", 649 | "h.ident = 99", 650 | ], 651 | joinargs = [ 652 | (table: "person", tableAs: "person", on: @["person.id = h.user_id"]), 653 | ], 654 | customSQL = "ORDER BY h.creation DESC", 655 | ) 656 | 657 | check querycompare(test, sql("SELECT h.uuid, h.text, h.creation, person.name FROM history AS h LEFT JOIN person ON (person.id = h.user_id) WHERE h.project_id = ? AND h.item_id = 33 AND h.element = 'tasks' AND h.data = 'special' AND h.ident = 99 ORDER BY h.creation DESC")) 658 | 659 | 660 | 661 | test "where x = 'y' and x = 'y' and x = ::int with fake spaces": 662 | 663 | let test = sqlSelect( 664 | table = "history", 665 | tableAs = "h", 666 | select = [ 667 | "h.uuid", 668 | "h.text", 669 | "h.creation", 670 | "person.name" 671 | ], 672 | where = [ 673 | "h.project_id = ", 674 | "h.item_id = ", 675 | "h.data = ", 676 | "h.ident = 33 ", 677 | ], 678 | joinargs = [ 679 | (table: "person", tableAs: "person", on: @["person.id = h.user_id"]), 680 | ], 681 | customSQL = "ORDER BY h.creation DESC", 682 | ) 683 | 684 | check querycompare(test, sql("SELECT h.uuid, h.text, h.creation, person.name FROM history AS h LEFT JOIN person ON (person.id = h.user_id) WHERE h.project_id = ? AND h.item_id = ? AND h.data = ? AND h.ident = 33 ORDER BY h.creation DESC")) 685 | 686 | 687 | 688 | test "where - complex where item - with parenthesis around": 689 | 690 | let test = sqlSelect( 691 | table = "history", 692 | tableAs = "history", 693 | select = [ 694 | "person.name as user_id", 695 | "history.creation" 696 | ], 697 | where = [ 698 | "history.project_id =", 699 | "history.item_id =", 700 | "history.is_deleted IS NULL", 701 | "(history.choice = 'Comment' OR history.choice = 'Picture' OR history.choice = 'File' OR history.choice = 'Design' OR history.choice = 'Update' OR history.choice = 'Create')" 702 | ], 703 | joinargs = [ 704 | (table: "person", tableAs: "", on: @["history.user_id = person.id"]) 705 | ], 706 | customSQL = "ORDER BY history.creation DESC, history.id DESC" 707 | ) 708 | 709 | check querycompare(test, sql("SELECT person.name as user_id, history.creation FROM history LEFT JOIN person ON (history.user_id = person.id) WHERE history.project_id = ? AND history.item_id = ? AND history.is_deleted IS NULL AND (history.choice = 'Comment' OR history.choice = 'Picture' OR history.choice = 'File' OR history.choice = 'Design' OR history.choice = 'Update' OR history.choice = 'Create') ORDER BY history.creation DESC, history.id DESC")) 710 | 711 | 712 | 713 | test "where - complex where item - without parenthesis around": 714 | 715 | let test = sqlSelect( 716 | table = "history", 717 | tableAs = "history", 718 | select = [ 719 | "person.name as user_id", 720 | "history.creation" 721 | ], 722 | where = [ 723 | "history.project_id =", 724 | "history.item_id =", 725 | "history.is_deleted IS NULL", 726 | "history.choice = 'Comment' OR history.choice = 'Picture' OR history.choice = 'File' OR history.choice = 'Design' OR history.choice = 'Update' OR history.choice = 'Create'" 727 | ], 728 | joinargs = [ 729 | (table: "person", tableAs: "", on: @["history.user_id = person.id"]) 730 | ], 731 | customSQL = "ORDER BY history.creation DESC, history.id DESC" 732 | ) 733 | 734 | check querycompare(test, sql("SELECT person.name as user_id, history.creation FROM history LEFT JOIN person ON (history.user_id = person.id) WHERE history.project_id = ? AND history.item_id = ? AND history.is_deleted IS NULL AND (history.choice = 'Comment' OR history.choice = 'Picture' OR history.choice = 'File' OR history.choice = 'Design' OR history.choice = 'Update' OR history.choice = 'Create') ORDER BY history.creation DESC, history.id DESC")) 735 | 736 | 737 | 738 | test "where - using !=": 739 | 740 | let test = sqlSelect( 741 | table = "history", 742 | tableAs = "history", 743 | select = [ 744 | "person.name as user_id", 745 | "history.creation" 746 | ], 747 | where = [ 748 | "history.project_id =", 749 | "history.creation != history.updated", 750 | ], 751 | joinargs = [ 752 | (table: "person", tableAs: "", on: @["history.user_id = person.id"]) 753 | ], 754 | customSQL = "ORDER BY history.creation DESC, history.id DESC" 755 | ) 756 | 757 | check querycompare(test, sql("SELECT person.name as user_id, history.creation FROM history LEFT JOIN person ON (history.user_id = person.id) WHERE history.project_id = ? AND history.creation != history.updated ORDER BY history.creation DESC, history.id DESC")) 758 | 759 | 760 | 761 | suite "test using DB names for columns": 762 | 763 | test "info => in, nonull => null, anything => any": 764 | 765 | let test = sqlSelect( 766 | table = "tasks", 767 | select = @["id", "name"], 768 | where = @["id =", "user =", "info =", "IN info", "anything =", "IN nonull"], 769 | ) 770 | check querycompare(test, sql(" SELECT id, name FROM tasks WHERE id = ? AND user = ? AND info = ? AND ? IN info AND anything = ? AND ? IN nonull")) 771 | 772 | 773 | 774 | 775 | suite "catch bad formats": 776 | 777 | test "malicious ?": 778 | 779 | const mal = [ 780 | "id = ?, AND ?", 781 | "id = ?, OR ?", 782 | "id = ? ?", 783 | "id = ? AND ?", 784 | "id = ? OR ?", 785 | ] 786 | 787 | for m in mal: 788 | check hasIllegalFormats(m) != "" 789 | 790 | 791 | 792 | 793 | 794 | test "kkk": 795 | let a = sqlSelect( 796 | table = "project", 797 | tableAs = "p", 798 | select = @[ 799 | "p.id", # 0 800 | "pfl.folder_id", # 1 801 | "array_to_string(pfa.see, ',') AS pfaSee", 802 | "array_to_string(pfa.write, ',') AS pfaWrite", 803 | "p.name", # 4 804 | "p.author_id", # 5 805 | "pfa.folder_name", # 6 806 | "array_to_string(pfa.see_userIDs, ',')", # 7 807 | "array_to_string(pfa.write_userIDs, ',')", # 8 808 | "array_to_string(pugSee.userIDs, ',')", # 9 809 | "array_to_string(pugWrite.userIDs, ',')", # 10 810 | "array_to_string(p.priv_admin, ',')", # 11 811 | "array_to_string(p.priv_manager, ',')", # 12 812 | "array_to_string(p.priv_work, ',')", # 13 813 | "array_to_string(p.priv_designer, ',')", # 14 814 | "array_to_string(p.priv_contractor, ',')", # 15 815 | "array_to_string(p.priv_see, ',')" # 16 816 | ], 817 | joinargs = @[ 818 | (table: "project_files_log", tableAs: "pfl", on: @["pfl.project_id = p.id"]), 819 | (table: "project_files_access", tableAs: "pfa", on: @["pfa.project_id = p.id", "pfa.key = pfl.folder_id"]), 820 | (table: "project_users_groups", tableAs: "pugSee", on: @["pugSee.project_id = p.id", "pugSee.id = ANY(pfa.see_groupIDs)"]), 821 | (table: "project_users_groups", tableAs: "pugWrite", on: @["pugWrite.project_id = p.id", "pugWrite.id = ANY(pfa.write_groupIDs)"]) 822 | ], 823 | where = @[ 824 | "pfl.creation >", 825 | "pfl.action IN (2, 3, 4, 5)", 826 | "p.usetender IS NULL" # !! => Decides if tender 827 | ], 828 | customSQL = """ 829 | GROUP BY 830 | p.id, 831 | pfl.folder_id, 832 | pfa.see, 833 | pfa.WRITE, 834 | p.name, 835 | p.author_id, 836 | p.priv_admin, 837 | p.priv_manager, 838 | pfa.folder_name, 839 | pfa.see_userIDs, 840 | pfa.write_userIDs, 841 | pugSee.userIDs, 842 | pugWrite.userIDs 843 | ORDER BY 844 | p.name ASC 845 | """ 846 | ) 847 | echo string(a) 848 | 849 | -------------------------------------------------------------------------------- /tests/select/test_select_arrays.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/strutils, 10 | std/unittest 11 | 12 | import 13 | src/sqlbuilder, 14 | src/sqlbuilderpkg/utils_private 15 | 16 | 17 | 18 | suite "sqlSelect - array formatting": 19 | 20 | test "setting type int": 21 | 22 | let test = sqlSelect( 23 | table = "tasks", 24 | select = @["id", "name"], 25 | where = @["some_ids = ANY(?::INT[])"], 26 | ) 27 | check querycompare(test, sql("SELECT id, name FROM tasks WHERE some_ids = ANY(?::INT[])")) 28 | 29 | 30 | test "setting type int multiple times": 31 | 32 | let test = sqlSelect( 33 | table = "tasks", 34 | select = @["id", "name"], 35 | where = @[ 36 | "some_ids = ANY(?::INT[])", 37 | "hash_ids = ANY(?::INT[])", 38 | "version =", 39 | "revisions = ANY(?::INT[])" 40 | ], 41 | ) 42 | check querycompare(test, sql("SELECT id, name FROM tasks WHERE some_ids = ANY(?::INT[]) AND hash_ids = ANY(?::INT[]) AND version = ? AND revisions = ANY(?::INT[])")) 43 | 44 | 45 | test "setting type int + text multiple times": 46 | 47 | let test = sqlSelect( 48 | table = "tasks", 49 | select = @["id", "name"], 50 | where = @[ 51 | "some_ids = ANY(?::TEXT[])", 52 | "hash_ids = ANY(?::INT[])", 53 | "version =", 54 | "revisions = ANY(?::TEXT[])" 55 | ], 56 | ) 57 | check querycompare(test, sql("SELECT id, name FROM tasks WHERE some_ids = ANY(?::TEXT[]) AND hash_ids = ANY(?::INT[]) AND version = ? AND revisions = ANY(?::TEXT[])")) 58 | 59 | 60 | -------------------------------------------------------------------------------- /tests/select/test_select_const.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/strutils, 10 | std/unittest 11 | 12 | import 13 | src/sqlbuilder, 14 | src/sqlbuilderpkg/utils_private 15 | 16 | 17 | 18 | 19 | 20 | suite "test sqlSelectConst": 21 | 22 | test "useDeleteMarker = false": 23 | var test: SqlQuery 24 | 25 | test = sqlSelectConst( 26 | table = "tasks", 27 | select = ["id", "name", "description", "created", "updated", "completed"], 28 | where = ["id ="], 29 | joinargs = [], 30 | useDeleteMarker = false 31 | ) 32 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE id = ? ")) 33 | 34 | 35 | 36 | test "from using AS ": 37 | var test: SqlQuery 38 | 39 | test = sqlSelectConst( 40 | table = "tasks", 41 | tableAs = "t", 42 | select = ["id", "name", "description", "created", "updated", "completed"], 43 | where = ["id ="], 44 | joinargs = [], 45 | useDeleteMarker = false 46 | ) 47 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks AS t WHERE id = ? ")) 48 | 49 | 50 | test = sqlSelectConst( 51 | table = "tasks", 52 | tableAs = "t", 53 | select = ["t.id", "t.name", "t.description", "t.created", "t.updated", "t.completed"], 54 | where = ["t.id ="], 55 | joinargs = [], 56 | useDeleteMarker = false, 57 | customSQL = "ORDER BY t.created DESC" 58 | ) 59 | check querycompare(test, sql("SELECT t.id, t.name, t.description, t.created, t.updated, t.completed FROM tasks AS t WHERE t.id = ? ORDER BY t.created DESC ")) 60 | 61 | 62 | 63 | test "WHERE statements: general": 64 | var test: SqlQuery 65 | 66 | test = sqlSelectConst( 67 | table = "tasks", 68 | tableAs = "t", 69 | select = ["id", "name", "description", "created", "updated", "completed"], 70 | where = ["id =", "name !=", "updated >", "completed IS", "description LIKE"], 71 | joinargs = [], 72 | useDeleteMarker = false 73 | ) 74 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks AS t WHERE id = ? AND name != ? AND updated > ? AND completed IS ? AND description LIKE ? ")) 75 | check string(test).count("?") == 5 76 | 77 | 78 | test = sqlSelectConst( 79 | table = "tasks", 80 | tableAs = "t", 81 | select = ["id", "name", "description", "created", "updated", "completed"], 82 | where = ["id =", "name !=", "updated >", "completed IS", "description LIKE"], 83 | joinargs = [], 84 | customSQL = "AND name != 'test' AND created > ? ", 85 | useDeleteMarker = false 86 | ) 87 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks AS t WHERE id = ? AND name != ? AND updated > ? AND completed IS ? AND description LIKE ? AND name != 'test' AND created > ? ")) 88 | check string(test).count("?") == 6 89 | 90 | 91 | 92 | test "WHERE statements: = ANY(...)": 93 | var test: SqlQuery 94 | 95 | test = sqlSelectConst( 96 | table = "tasks", 97 | tableAs = "t", 98 | select = ["id", "ids_array"], 99 | where = ["id =", "= ANY(ids_array)"], 100 | joinargs = [], 101 | useDeleteMarker = false 102 | ) 103 | check querycompare(test, sql("SELECT id, ids_array FROM tasks AS t WHERE id = ? AND ? = ANY(ids_array) ")) 104 | check string(test).count("?") == 2 105 | 106 | 107 | 108 | test "WHERE statements: = ANY(...) multiple instances": 109 | var test: SqlQuery 110 | 111 | test = sqlSelectConst( 112 | table = "tasks", 113 | tableAs = "t", 114 | select = ["id", "ids_array"], 115 | where = ["id =", "= ANY(ids_array)", "= ANY(user_array)", "= ANY(tasks_array)"], 116 | joinargs = [], 117 | useDeleteMarker = false 118 | ) 119 | check querycompare(test, sql("SELECT id, ids_array FROM tasks AS t WHERE id = ? AND ? = ANY(ids_array) AND ? = ANY(user_array) AND ? = ANY(tasks_array) ")) 120 | check string(test).count("?") == 4 121 | 122 | 123 | 124 | test "WHERE statements: x IN y": 125 | var test: SqlQuery 126 | 127 | test = sqlSelectConst( 128 | table = "tasks", 129 | tableAs = "t", 130 | select = ["id", "ids_array"], 131 | where = ["id =", "IN (ids_array)"], 132 | joinargs = [], 133 | useDeleteMarker = false 134 | ) 135 | check querycompare(test, sql("SELECT id, ids_array FROM tasks AS t WHERE id = ? AND ? IN (ids_array) ")) 136 | check string(test).count("?") == 2 137 | 138 | 139 | test = sqlSelectConst( 140 | table = "tasks", 141 | tableAs = "t", 142 | select = ["id", "ids_array"], 143 | where = ["id =", "id IN"], 144 | joinargs = [], 145 | useDeleteMarker = false 146 | ) 147 | check querycompare(test, sql("SELECT id, ids_array FROM tasks AS t WHERE id = ? AND id IN (?) ")) 148 | check string(test).count("?") == 2 149 | 150 | 151 | 152 | test "WHERE statements: `is NULL` ": 153 | var test: SqlQuery 154 | 155 | test = sqlSelectConst( 156 | table = "tasks", 157 | tableAs = "t", 158 | select = ["id", "name", "description", "created", "updated", "completed"], 159 | where = ["id =", "name != NULL", "description = NULL"], 160 | joinargs = [], 161 | useDeleteMarker = false 162 | ) 163 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks AS t WHERE id = ? AND name != NULL AND description = NULL ")) 164 | check string(test).count("?") == 1 165 | 166 | 167 | test = sqlSelectConst( 168 | table = "tasks", 169 | tableAs = "t", 170 | select = ["id", "name", "description", "created", "updated", "completed"], 171 | where = ["id =", "name !=", "description = NULL"], 172 | joinargs = [], 173 | useDeleteMarker = false 174 | ) 175 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks AS t WHERE id = ? AND name != ? AND description = NULL ")) 176 | check string(test).count("?") == 2 177 | 178 | 179 | test "SELECT with SUM and COUNT": 180 | var test: SqlQuery 181 | 182 | test = sqlSelectConst( 183 | table = "checklist_table", 184 | tableAs = "cht", 185 | select = [ 186 | "cht.c_id", 187 | "SUM(CASE WHEN cht.status IS NULL THEN 1 ELSE 0 END) as null", 188 | "SUM(CASE WHEN cht.status = 0 THEN 1 ELSE 0 END) as zero", 189 | "SUM(CASE WHEN cht.status = 1 THEN 1 ELSE 0 END) as one", 190 | "SUM(CASE WHEN cht.status = 2 THEN 1 ELSE 0 END) as two", 191 | "COUNT(cht.id) as total", 192 | "SUM(CASE WHEN cht.extra_check IS TRUE AND cht.extra_approve IS TRUE THEN 1 ELSE 0 END) as extraApproved", 193 | "SUM(CASE WHEN cht.extra_check IS TRUE AND cht.extra_approve IS False THEN 1 ELSE 0 END) as extraFailed", 194 | "SUM(CASE WHEN cht.extra_check IS TRUE AND cht.extra_approve IS NULL AND cht.status = 1 THEN 1 ELSE 0 END) as extraAwaiting", 195 | ], 196 | where = [ 197 | "cht.project_id =", 198 | "cht.type =" 199 | ], 200 | joinargs = [ 201 | (table: "checklist", tableAs: "c", on: @["c.id = cht.c_id"]) 202 | ], 203 | customSQL = "GROUP BY cht.c_id" 204 | ) 205 | check querycompare(test, sql(""" 206 | select 207 | cht.c_id, 208 | SUM(case when cht.status is null then 1 else 0 end) as null, 209 | SUM(case when cht.status = 0 then 1 else 0 end) as zero, 210 | SUM(case when cht.status = 1 then 1 else 0 end) as one, 211 | SUM(case when cht.status = 2 then 1 else 0 end) as two, 212 | COUNT(cht.id) as total, 213 | SUM(case when cht.extra_check is true and cht.extra_approve is true then 1 else 0 end) as extraApproved, 214 | SUM(case when cht.extra_check is true and cht.extra_approve is false then 1 else 0 end) as extraFailed, 215 | SUM(case when cht.extra_check is true and cht.extra_approve is null and cht.status = 1 then 1 else 0 end) as extraAwaiting 216 | from 217 | checklist_table as cht 218 | left join checklist as c on 219 | (c.id = cht.c_id) 220 | where 221 | cht.project_id = ? 222 | and cht.type = ? 223 | group by 224 | cht.c_id 225 | """)) 226 | 227 | 228 | 229 | suite "test sqlSelectConst - joins": 230 | 231 | test "LEFT JOIN using AS values": 232 | var test: SqlQuery 233 | 234 | test = sqlSelectConst( 235 | table = "tasks", 236 | tableAs = "t", 237 | select = ["id", "name"], 238 | where = ["id ="], 239 | joinargs = [(table: "projects", tableAs: "p", on: @["p.id = t.project_id", "p.status = 1"])], 240 | useDeleteMarker = false 241 | ) 242 | check querycompare(test, sql("SELECT id, name FROM tasks AS t LEFT JOIN projects AS p ON (p.id = t.project_id AND p.status = 1) WHERE id = ? ")) 243 | 244 | test "LEFT JOIN (default) - 1 value": 245 | var test: SqlQuery 246 | 247 | test = sqlSelectConst( 248 | table = "tasks", 249 | tableAs = "t", 250 | select = ["id", "name"], 251 | where = ["id ="], 252 | joinargs = [(table: "projects", tableAs: "", on: @["projects.id = t.project_id", "projects.status = 1"])], 253 | useDeleteMarker = false 254 | ) 255 | check querycompare(test, sql("SELECT id, name FROM tasks AS t LEFT JOIN projects ON (projects.id = t.project_id AND projects.status = 1) WHERE id = ? ")) 256 | 257 | test "LEFT JOIN (default) - 2 value": 258 | var test: SqlQuery 259 | 260 | test = sqlSelectConst( 261 | table = "tasks", 262 | tableAs = "t", 263 | select = ["id", "name"], 264 | where = ["id ="], 265 | joinargs = [(table: "projects", tableAs: "", on: @["projects.id = t.project_id", "projects.status = 1"]), (table: "invoice", tableAs: "", on: @["invoice.id = t.invoice_id", "invoice.status = 1"])], 266 | useDeleteMarker = false 267 | ) 268 | check querycompare(test, sql("SELECT id, name FROM tasks AS t LEFT JOIN projects ON (projects.id = t.project_id AND projects.status = 1) LEFT JOIN invoice ON (invoice.id = t.invoice_id AND invoice.status = 1) WHERE id = ? ")) 269 | 270 | test "LEFT JOIN (default) - 3 value": 271 | var test: SqlQuery 272 | 273 | test = sqlSelectConst( 274 | table = "tasks", 275 | tableAs = "t", 276 | select = ["id", "name"], 277 | where = ["id ="], 278 | joinargs = [(table: "projects", tableAs: "", on: @["projects.id = t.project_id", "projects.status = 1"]), (table: "invoice", tableAs: "", on: @["invoice.id = t.invoice_id", "invoice.status = 1"]), (table: "letter", tableAs: "", on: @["letter.id = t.letter_id", "letter.status = 1"])], 279 | useDeleteMarker = false 280 | ) 281 | check querycompare(test, sql("SELECT id, name FROM tasks AS t LEFT JOIN projects ON (projects.id = t.project_id AND projects.status = 1) LEFT JOIN invoice ON (invoice.id = t.invoice_id AND invoice.status = 1) LEFT JOIN letter ON (letter.id = t.letter_id AND letter.status = 1) WHERE id = ? ")) 282 | 283 | 284 | test "INNER JOIN": 285 | var test: SqlQuery 286 | 287 | test = sqlSelectConst( 288 | table = "tasks", 289 | tableAs = "t", 290 | select = ["id", "name"], 291 | where = ["id ="], 292 | joinargs = [(table: "projects", tableAs: "", on: @["projects.id = t.project_id", "projects.status = 1"])], 293 | jointype = INNER, 294 | useDeleteMarker = false 295 | ) 296 | 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 = ? ")) 297 | 298 | test "CROSS JOIN": 299 | var test: SqlQuery 300 | 301 | test = sqlSelectConst( 302 | table = "a", 303 | select = ["id"], 304 | where = [], 305 | joinargs = [(table: "b", tableAs: "", on: @["a.id = b.id"])], 306 | jointype = CROSS, 307 | ) 308 | check querycompare(test, sql("SELECT id FROM a CROSS JOIN b ON (a.id = b.id)")) 309 | 310 | test "JOIN #1": 311 | var test: SqlQuery 312 | 313 | test = sqlSelectConst( 314 | table = "tasks", 315 | tableAs = "t", 316 | select = ["id", "name", "description", "created", "updated", "completed"], 317 | where = ["id ="], 318 | joinargs = [(table: "projects", tableAs: "", on: @["projects.id = t.project_id", "projects.status = 1"])], 319 | useDeleteMarker = false, 320 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"] 321 | ) 322 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks AS t LEFT JOIN projects ON (projects.id = t.project_id AND projects.status = 1) WHERE id = ? ")) 323 | 324 | 325 | test "JOIN #2": 326 | var test: SqlQuery 327 | 328 | test = sqlSelectConst( 329 | table = "tasks", 330 | tableAs = "t", 331 | select = ["t.id", "t.name", "t.description", "t.created", "t.updated", "t.completed"], 332 | where = ["t.id ="], 333 | joinargs = [(table: "projects", tableAs: "", on: @["projects.id = t.project_id", "projects.status = 1"])], 334 | useDeleteMarker = false, 335 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"] 336 | ) 337 | check querycompare(test, sql("SELECT t.id, t.name, t.description, t.created, t.updated, t.completed FROM tasks AS t LEFT JOIN projects ON (projects.id = t.project_id AND projects.status = 1) WHERE t.id = ? ")) 338 | 339 | 340 | test "JOIN #3": 341 | var test: SqlQuery 342 | 343 | test = sqlSelectConst( 344 | table = "tasks", 345 | tableAs = "t", 346 | select = ["t.id", "t.name", "t.description", "t.created", "t.updated", "t.completed"], 347 | where = ["t.id ="], 348 | # joinargs = [], 349 | joinargs = [ 350 | (table: "projects", tableAs: "", on: @["projects.id = t.project_id", "projects.status = 1"]), 351 | (table: "projects", tableAs: "", on: @["projects.id = t.project_id", "projects.status = 1"]) 352 | ], 353 | useDeleteMarker = false, 354 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"] 355 | ) 356 | check querycompare(test, sql("SELECT t.id, t.name, t.description, t.created, t.updated, t.completed FROM tasks AS t LEFT JOIN projects ON (projects.id = t.project_id AND projects.status = 1) LEFT JOIN projects ON (projects.id = t.project_id AND projects.status = 1) WHERE t.id = ? ")) 357 | 358 | 359 | 360 | 361 | suite "test sqlSelectConst - deletemarkers / softdelete": 362 | 363 | 364 | 365 | test "deletemarkers from const - 1 value": 366 | let test = sqlSelectConst( 367 | table = "tasks", 368 | select = ["id", "name"], 369 | where = ["id ="], 370 | joinargs = [(table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"])], 371 | tablesWithDeleteMarker = ["tasks"], 372 | ) 373 | check querycompare(test, sql("SELECT id, name FROM tasks LEFT JOIN projects ON (projects.id = tasks.project_id AND projects.status = 1) WHERE id = ? AND tasks.is_deleted IS NULL ")) 374 | 375 | 376 | test "deletemarkers from const - 2 values": 377 | let test = sqlSelectConst( 378 | table = "tasks", 379 | select = ["id", "name"], 380 | where = ["id ="], 381 | joinargs = [(table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"])], 382 | tablesWithDeleteMarker = ["tasks", "history"], 383 | ) 384 | check querycompare(test, sql("SELECT id, name FROM tasks LEFT JOIN projects ON (projects.id = tasks.project_id AND projects.status = 1) WHERE id = ? AND tasks.is_deleted IS NULL ")) 385 | 386 | 387 | test "deletemarkers from const - 3 values": 388 | let test = sqlSelectConst( 389 | table = "tasks", 390 | select = ["id", "name"], 391 | where = ["id ="], 392 | joinargs = [(table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"])], 393 | tablesWithDeleteMarker = ["tasks", "history", "person"], 394 | ) 395 | check querycompare(test, sql("SELECT id, name FROM tasks LEFT JOIN projects ON (projects.id = tasks.project_id AND projects.status = 1) WHERE id = ? AND tasks.is_deleted IS NULL ")) 396 | 397 | 398 | 399 | test "deletemarkers from const": 400 | const tableWithDeleteMarkerLet = ["tasks", "history", "tasksitems"] 401 | 402 | let test = sqlSelectConst( 403 | table = "tasks", 404 | select = ["id", "name"], 405 | where = ["id ="], 406 | joinargs = [(table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"])], 407 | # tablesWithDeleteMarker = tableWithDeleteMarkerLet, 408 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"] 409 | ) 410 | check querycompare(test, sql("SELECT id, name FROM tasks LEFT JOIN projects ON (projects.id = tasks.project_id AND projects.status = 1) WHERE id = ? AND tasks.is_deleted IS NULL ")) 411 | 412 | 413 | 414 | 415 | test "deletemarkers from inline": 416 | var test: SqlQuery 417 | # const tableWithDeleteMarkerLet = ["tasks", "history", "tasksitems"] 418 | 419 | test = sqlSelectConst( 420 | table = "tasks", 421 | select = ["id", "name"], 422 | where = ["id ="], 423 | joinargs = [(table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"])], 424 | # tablesWithDeleteMarker = [] #tableWithDeleteMarkerLet, 425 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"] 426 | ) 427 | check querycompare(test, sql("SELECT id, name FROM tasks LEFT JOIN projects ON (projects.id = tasks.project_id AND projects.status = 1) WHERE id = ? AND tasks.is_deleted IS NULL ")) 428 | 429 | 430 | 431 | 432 | test "deletemarkers from inline (without join)": 433 | var test: SqlQuery 434 | # const tableWithDeleteMarkerLet = ["tasks", "history", "tasksitems"] 435 | 436 | test = sqlSelectConst( 437 | table = "tasks", 438 | select = ["id", "name"], 439 | where = ["id ="], 440 | joinargs = [], 441 | # joinargs = [(table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"])], 442 | # tablesWithDeleteMarker = [] #tableWithDeleteMarkerLet, 443 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"] 444 | ) 445 | check querycompare(test, sql("SELECT id, name FROM tasks WHERE id = ? AND tasks.is_deleted IS NULL ")) 446 | 447 | 448 | 449 | test "deletemarkers from inline with WHERE IN": 450 | var test: SqlQuery 451 | const tableWithDeleteMarkerLet = ["tasks", "history", "tasksitems"] 452 | 453 | test = sqlSelectConst( 454 | table = "tasks", 455 | select = ["id", "name"], 456 | where = ["id ="], 457 | joinargs = [(table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"])], 458 | # tablesWithDeleteMarker = [] #tableWithDeleteMarkerLet, 459 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"], 460 | 461 | whereInField = "tasks", 462 | whereInValue = ["1", "2", "3"] 463 | ) 464 | check querycompare(test, sql("SELECT id, name FROM tasks LEFT JOIN projects ON (projects.id = tasks.project_id AND projects.status = 1) WHERE id = ? AND tasks in (1,2,3) AND tasks.is_deleted IS NULL ")) 465 | 466 | 467 | test "deletemarkers misc": 468 | var test: SqlQuery 469 | # const tableWithDeleteMarkerLet = ["tasks", "history", "tasksitems"] 470 | 471 | test = sqlSelectConst( 472 | table = "tasks", 473 | select = ["id", "name"], 474 | where = ["id ="], 475 | joinargs = [(table: "history", tableAs: "", on: @["history.id = tasks.hid"])], 476 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"] 477 | ) 478 | check querycompare(test, sql("SELECT id, name FROM tasks LEFT JOIN history ON (history.id = tasks.hid AND history.is_deleted IS NULL) WHERE id = ? AND tasks.is_deleted IS NULL ")) 479 | 480 | 481 | 482 | test = sqlSelectConst( 483 | table = "tasks", 484 | select = ["tasks.id", "tasks.name"], 485 | where = ["tasks.id ="], 486 | joinargs = [(table: "history", tableAs: "his", on: @["his.id = tasks.hid"])], 487 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"] 488 | ) 489 | check querycompare(test, sql("SELECT tasks.id, tasks.name FROM tasks LEFT JOIN history AS his ON (his.id = tasks.hid AND his.is_deleted IS NULL) WHERE tasks.id = ? AND tasks.is_deleted IS NULL ")) 490 | 491 | 492 | 493 | test "deletemarkers on the fly": 494 | var test: SqlQuery 495 | 496 | test = sqlSelectConst( 497 | table = "tasks", 498 | select = ["id", "name"], 499 | where = ["id ="], 500 | joinargs = [(table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"])], 501 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"] 502 | ) 503 | check querycompare(test, sql("SELECT id, name FROM tasks LEFT JOIN projects ON (projects.id = tasks.project_id AND projects.status = 1) WHERE id = ? AND tasks.is_deleted IS NULL ")) 504 | 505 | 506 | 507 | test "custom deletemarker override": 508 | var test: SqlQuery 509 | 510 | test = sqlSelectConst( 511 | table = "tasks", 512 | select = ["id", "name"], 513 | where = ["id ="], 514 | joinargs = [(table: "history", tableAs: "", on: @["history.id = tasks.hid"])], 515 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"], 516 | deleteMarker = ".deleted_at = 543234563" 517 | ) 518 | check querycompare(test, sql("SELECT id, name FROM tasks LEFT JOIN history ON (history.id = tasks.hid AND history.deleted_at = 543234563) WHERE id = ? AND tasks.deleted_at = 543234563 ")) 519 | 520 | 521 | 522 | test "complex query": 523 | var test: SqlQuery 524 | 525 | test = sqlSelectConst( 526 | table = "tasksitems", 527 | tableAs = "tasks", 528 | select = [ 529 | "tasks.id", 530 | "tasks.name", 531 | "tasks.status", 532 | "tasks.created", 533 | "his.id", 534 | "his.name", 535 | "his.status", 536 | "his.created", 537 | "projects.id", 538 | "projects.name", 539 | "person.id", 540 | "person.name", 541 | "person.email" 542 | ], 543 | where = [ 544 | "projects.id =", 545 | "tasks.status >" 546 | ], 547 | joinargs = [ 548 | (table: "history", tableAs: "his", on: @["his.id = tasks.hid", "his.status = 1"]), 549 | (table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"]), 550 | (table: "person", tableAs: "", on: @["person.id = tasks.person_id"]) 551 | ], 552 | whereInField = "tasks.id", 553 | whereInValue = ["1", "2", "3"], 554 | customSQL = "ORDER BY tasks.created DESC", 555 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"] 556 | ) 557 | check querycompare(test, (sql(""" 558 | SELECT 559 | tasks.id, 560 | tasks.name, 561 | tasks.status, 562 | tasks.created, 563 | his.id, 564 | his.name, 565 | his.status, 566 | his.created, 567 | projects.id, 568 | projects.name, 569 | person.id, 570 | person.name, 571 | person.email 572 | FROM 573 | tasksitems AS tasks 574 | LEFT JOIN history AS his ON 575 | (his.id = tasks.hid AND his.status = 1 AND his.is_deleted IS NULL) 576 | LEFT JOIN projects ON 577 | (projects.id = tasks.project_id AND projects.status = 1) 578 | LEFT JOIN person ON 579 | (person.id = tasks.person_id) 580 | WHERE 581 | projects.id = ? 582 | AND tasks.status > ? 583 | AND tasks.id in (1,2,3) 584 | AND tasks.is_deleted IS NULL 585 | ORDER BY 586 | tasks.created DESC 587 | """))) 588 | 589 | 590 | 591 | 592 | 593 | 594 | suite "sqlSelectConst": 595 | 596 | test "with delete marker": 597 | let a = sqlSelectConst( 598 | table = "tasks", 599 | tableAs = "t", 600 | select = ["t.id", "t.name", "t.description", "t.created", "t.updated", "t.completed"], 601 | where = ["t.id ="], 602 | joinargs = [], 603 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"], #tableWithDeleteMarker 604 | ) 605 | 606 | check querycompare(a, (sql(""" 607 | SELECT 608 | t.id, t.name, t.description, t.created, t.updated, t.completed 609 | FROM 610 | tasks AS t 611 | WHERE 612 | t.id = ? 613 | AND t.is_deleted IS NULL 614 | """))) 615 | 616 | 617 | test "deletemarkers + joind": 618 | let b = sqlSelectConst( 619 | table = "tasks", 620 | # tableAs = "t", 621 | select = ["id", "name", "description", "created", "updated", "completed"], 622 | where = ["id =", "status >"], 623 | joinargs = [ 624 | (table: "history", tableAs: "his", on: @["his.id = tasks.hid", "his.status = 1"]), 625 | (table: "projects", tableAs: "", on: @["projects.id = tasks.project_id", "projects.status = 1"]), 626 | ], 627 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"] #tableWithDeleteMarker 628 | ) 629 | 630 | check querycompare(b, sql(""" 631 | SELECT 632 | id, name, description, created, updated, completed 633 | FROM 634 | tasks 635 | LEFT JOIN history AS his ON 636 | (his.id = tasks.hid AND his.status = 1 AND his.is_deleted IS NULL) 637 | LEFT JOIN projects ON 638 | (projects.id = tasks.project_id AND projects.status = 1) 639 | WHERE 640 | id = ? 641 | AND status > ? 642 | AND tasks.is_deleted IS NULL 643 | """)) 644 | 645 | 646 | let c = sqlSelectConst( 647 | table = "tasks", 648 | select = ["tasks.id"], 649 | where = ["status >"], 650 | joinargs = [ 651 | (table: "history", tableAs: "", on: @["history.id = tasks.hid"]), 652 | ], 653 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"] 654 | ) 655 | check querycompare(c, sql(""" 656 | SELECT 657 | tasks.id 658 | FROM 659 | tasks 660 | LEFT JOIN history ON 661 | (history.id = tasks.hid AND history.is_deleted IS NULL) 662 | WHERE 663 | status > ? 664 | AND tasks.is_deleted IS NULL 665 | """)) 666 | 667 | 668 | test "deletemarkers + join-mess": 669 | let d = sqlSelectConst( 670 | table = "tasks", 671 | select = ["tasks.id"], 672 | where = ["status >"], 673 | joinargs = [ 674 | (table: "history", tableAs: "his", on: @["his.id = tasks.hid"]), 675 | # (table: "history", tableAs: "his", on: @["his.id = tasks.hid"]), 676 | # (table: "history", tableAs: "his", on: @["his.id = tasks.hid"]), 677 | # (table: "history", tableAs: "his", on: @["his.id = tasks.hid"]), 678 | # (table: "history", tableAs: "his", on: @["his.id = tasks.hid"]), 679 | ], 680 | tablesWithDeleteMarker = ["tasks", "history", "tasksitems"] 681 | ) 682 | check querycompare(d, sql(""" 683 | SELECT 684 | tasks.id 685 | FROM 686 | tasks 687 | LEFT JOIN history AS his ON 688 | (his.id = tasks.hid AND his.is_deleted IS NULL) 689 | WHERE 690 | status > ? 691 | AND tasks.is_deleted IS NULL 692 | """)) 693 | # LEFT JOIN history AS his ON 694 | # (his.id = tasks.hid AND his.is_deleted IS NULL) 695 | # LEFT JOIN history AS his ON 696 | # (his.id = tasks.hid AND his.is_deleted IS NULL) 697 | # LEFT JOIN history AS his ON 698 | # (his.id = tasks.hid AND his.is_deleted IS NULL) 699 | # LEFT JOIN history AS his ON 700 | # (his.id = tasks.hid AND his.is_deleted IS NULL) 701 | 702 | test "where in values (preformatted)": 703 | let e = sqlSelectConst( 704 | table = "tasks", 705 | tableAs = "t", 706 | select = ["t.id", "t.name"], 707 | where = ["t.id ="], 708 | joinargs = [], 709 | whereInField = "t.name", 710 | whereInValue = ["'1aa'", "'2bb'", "'3cc'"], 711 | tablesWithDeleteMarker = ["tasksQ", "history", "tasksitems"], #tableWithDeleteMarker 712 | ) 713 | check querycompare(e, sql(""" 714 | SELECT 715 | t.id, t.name 716 | FROM 717 | tasks AS t 718 | WHERE 719 | t.id = ? 720 | AND t.name in ('1aa','2bb','3cc')""")) 721 | 722 | 723 | test "where in values (empty)": 724 | 725 | let f = sqlSelectConst( 726 | table = "tasks", 727 | tableAs = "t", 728 | select = ["t.id", "t.name"], 729 | where = ["t.id ="], 730 | joinargs = [], 731 | whereInField = "t.id", 732 | whereInValue = [""], 733 | tablesWithDeleteMarker = ["tasksQ", "history", "tasksitems"], #tableWithDeleteMarker 734 | ) 735 | check querycompare(f, sql("SELECT t.id, t.name FROM tasks AS t WHERE t.id = ? AND t.id in (0)")) 736 | 737 | -------------------------------------------------------------------------------- /tests/select/test_select_const_deletemarker.nim: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # This sets a global delete marker 4 | # 5 | 6 | 7 | when NimMajor >= 2: 8 | import db_connector/db_common 9 | else: 10 | import std/db_common 11 | 12 | import 13 | std/strutils, 14 | std/unittest 15 | 16 | import 17 | src/sqlbuilderpkg/utils_private 18 | 19 | const tablesWithDeleteMarkerInit = ["tasks"] 20 | 21 | include src/sqlbuilder_include 22 | 23 | 24 | suite "select with tablesWithDeleteMarkerInit init": 25 | 26 | test "constants": 27 | var test: SqlQuery 28 | 29 | test = sqlSelectConst( 30 | table = "tasks", 31 | select = ["id", "name", "description", "created", "updated", "completed"], 32 | where = ["id ="], 33 | joinargs = [], 34 | useDeleteMarker = true 35 | ) 36 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE id = ? AND tasks.is_deleted IS NULL ")) 37 | 38 | 39 | test "dynamic": 40 | var test: SqlQuery 41 | 42 | test = sqlSelect( 43 | table = "tasks", 44 | select = ["id", "name", "description", "created", "updated", "completed"], 45 | where = ["id ="], 46 | joinargs = [], 47 | useDeleteMarker = true 48 | ) 49 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE id = ? AND tasks.is_deleted IS NULL ")) 50 | -------------------------------------------------------------------------------- /tests/select/test_select_const_where.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/unittest 10 | 11 | import 12 | src/sqlbuilder, 13 | src/sqlbuilderpkg/utils_private 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | suite "test where cases custom formatting": 22 | 23 | test "where OR": 24 | var test: SqlQuery 25 | 26 | 27 | let 28 | resNormal = sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE (id = ? OR id = ?) ") 29 | resPrepared = sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE (id = $1 OR id = $2) ") 30 | 31 | 32 | # Macro normal 33 | test = sqlSelectConst( 34 | table = "tasks", 35 | select = ["id", "name", "description", "created", "updated", "completed"], 36 | where = ["id = ? OR id = ?"], 37 | joinargs = [], 38 | useDeleteMarker = false, 39 | usePrepared = false 40 | ) 41 | check querycompare(test, resNormal) 42 | 43 | 44 | # Macro prepared 45 | test = sqlSelectConst( 46 | table = "tasks", 47 | select = ["id", "name", "description", "created", "updated", "completed"], 48 | where = ["id = ? OR id = ?"], 49 | joinargs = [], 50 | useDeleteMarker = false, 51 | usePrepared = true 52 | ) 53 | check querycompare(test, resPrepared) 54 | 55 | 56 | test "where OR OR": 57 | var test: SqlQuery 58 | 59 | test = sqlSelectConst( 60 | table = "tasks", 61 | select = ["id", "name", "description", "created", "updated", "completed"], 62 | where = ["id = ? OR name = ? OR description = ?"], 63 | joinargs = [], 64 | useDeleteMarker = false 65 | ) 66 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE (id = ? OR name = ? OR description = ?) ")) 67 | 68 | 69 | test "where OR OR parentheses": 70 | var test: SqlQuery 71 | 72 | test = sqlSelectConst( 73 | table = "tasks", 74 | select = ["id", "name", "description", "created", "updated", "completed"], 75 | where = ["id = ? OR (name = ? OR description = ?)"], 76 | joinargs = [], 77 | useDeleteMarker = false 78 | ) 79 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE (id = ? OR (name = ? OR description = ?)) ")) 80 | 81 | 82 | test "where AND OR parentheses": 83 | var test: SqlQuery 84 | 85 | test = sqlSelectConst( 86 | table = "tasks", 87 | select = ["id", "name", "description", "created", "updated", "completed"], 88 | where = ["id = ? AND (name = ? OR description = ?)"], 89 | joinargs = [], 90 | useDeleteMarker = false 91 | ) 92 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE (id = ? AND (name = ? OR description = ?)) ")) 93 | 94 | 95 | test "where OR OR parentheses AND ? = ANY(...)": 96 | var test: SqlQuery 97 | 98 | let 99 | resNormal = sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE (id = ? OR (name = ? OR description = ?) AND ? = ANY(ids_array)) ") 100 | resPrepared = sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE (id = $1 OR (name = $2 OR description = $3) AND $4 = ANY(ids_array)) ") 101 | 102 | test = sqlSelectConst( 103 | table = "tasks", 104 | select = ["id", "name", "description", "created", "updated", "completed"], 105 | where = ["id = ? OR (name = ? OR description = ?) AND ? = ANY(ids_array)"], 106 | joinargs = [], 107 | useDeleteMarker = false, 108 | usePrepared = false 109 | ) 110 | check querycompare(test, resNormal) 111 | 112 | 113 | test = sqlSelectConst( 114 | table = "tasks", 115 | select = ["id", "name", "description", "created", "updated", "completed"], 116 | where = ["id = ? OR (name = ? OR description = ?) AND ? = ANY(ids_array)"], 117 | joinargs = [], 118 | useDeleteMarker = false, 119 | usePrepared = true 120 | ) 121 | check querycompare(test, resPrepared) 122 | 123 | 124 | test "where x = 'y'": 125 | 126 | let test = sqlSelectConst( 127 | table = "history", 128 | tableAs = "h", 129 | select = [ 130 | "h.uuid", 131 | "h.text", 132 | "h.creation", 133 | "person.name" 134 | ], 135 | where = [ 136 | "h.project_id =", 137 | "h.item_id =", 138 | "h.element = 'tasks'", 139 | ], 140 | joinargs = [ 141 | (table: "person", tableAs: "person", on: @["person.id = h.user_id"]), 142 | ], 143 | customSQL = "ORDER BY h.creation DESC", 144 | ) 145 | 146 | check querycompare(test, sql("SELECT h.uuid, h.text, h.creation, person.name FROM history AS h LEFT JOIN person ON (person.id = h.user_id) WHERE h.project_id = ? AND h.item_id = ? AND h.element = 'tasks' ORDER BY h.creation DESC")) 147 | 148 | 149 | test "where x = 'y' and x = 'y' and x = ::int": 150 | 151 | let test = sqlSelectConst( 152 | table = "history", 153 | tableAs = "h", 154 | select = [ 155 | "h.uuid", 156 | "h.text", 157 | "h.creation", 158 | "person.name" 159 | ], 160 | where = [ 161 | "h.project_id =", 162 | "h.item_id = 33", 163 | "h.element = 'tasks'", 164 | "h.data = 'special'", 165 | "h.ident = 99", 166 | ], 167 | joinargs = [ 168 | (table: "person", tableAs: "person", on: @["person.id = h.user_id"]), 169 | ], 170 | customSQL = "ORDER BY h.creation DESC", 171 | ) 172 | 173 | check querycompare(test, sql("SELECT h.uuid, h.text, h.creation, person.name FROM history AS h LEFT JOIN person ON (person.id = h.user_id) WHERE h.project_id = ? AND h.item_id = 33 AND h.element = 'tasks' AND h.data = 'special' AND h.ident = 99 ORDER BY h.creation DESC")) 174 | 175 | 176 | 177 | test "where x = 'y' and x = 'y' and x = ::int with fake spaces": 178 | 179 | let test = sqlSelectConst( 180 | table = "history", 181 | tableAs = "h", 182 | select = [ 183 | "h.uuid", 184 | "h.text", 185 | "h.creation", 186 | "person.name" 187 | ], 188 | where = [ 189 | "h.project_id = ", 190 | "h.item_id = ", 191 | "h.data = ", 192 | "h.ident = 33 ", 193 | ], 194 | joinargs = [ 195 | (table: "person", tableAs: "person", on: @["person.id = h.user_id"]), 196 | ], 197 | customSQL = "ORDER BY h.creation DESC", 198 | ) 199 | 200 | check querycompare(test, sql("SELECT h.uuid, h.text, h.creation, person.name FROM history AS h LEFT JOIN person ON (person.id = h.user_id) WHERE h.project_id = ? AND h.item_id = ? AND h.data = ? AND h.ident = 33 ORDER BY h.creation DESC")) 201 | 202 | 203 | 204 | 205 | test "where - complex where item - with parenthesis around": 206 | 207 | let test = sqlSelectConst( 208 | table = "history", 209 | tableAs = "history", 210 | select = [ 211 | "person.name as user_id", 212 | "history.creation" 213 | ], 214 | where = [ 215 | "history.project_id =", 216 | "history.item_id =", 217 | "history.is_deleted IS NULL", 218 | "(history.choice = 'Comment' OR history.choice = 'Picture' OR history.choice = 'File' OR history.choice = 'Design' OR history.choice = 'Update' OR history.choice = 'Create')" 219 | ], 220 | joinargs = [ 221 | (table: "person", tableAs: "", on: @["history.user_id = person.id"]) 222 | ], 223 | customSQL = "ORDER BY history.creation DESC, history.id DESC" 224 | ) 225 | 226 | check querycompare(test, sql("SELECT person.name as user_id, history.creation FROM history LEFT JOIN person ON (history.user_id = person.id) WHERE history.project_id = ? AND history.item_id = ? AND history.is_deleted IS NULL AND (history.choice = 'Comment' OR history.choice = 'Picture' OR history.choice = 'File' OR history.choice = 'Design' OR history.choice = 'Update' OR history.choice = 'Create') ORDER BY history.creation DESC, history.id DESC")) 227 | 228 | 229 | 230 | test "where - complex where item - without parenthesis around": 231 | 232 | let test = sqlSelectConst( 233 | table = "history", 234 | tableAs = "history", 235 | select = [ 236 | "person.name as user_id", 237 | "history.creation" 238 | ], 239 | where = [ 240 | "history.project_id =", 241 | "history.item_id =", 242 | "history.is_deleted IS NULL", 243 | "history.choice = 'Comment' OR history.choice = 'Picture' OR history.choice = 'File' OR history.choice = 'Design' OR history.choice = 'Update' OR history.choice = 'Create'" 244 | ], 245 | joinargs = [ 246 | (table: "person", tableAs: "", on: @["history.user_id = person.id"]) 247 | ], 248 | customSQL = "ORDER BY history.creation DESC, history.id DESC" 249 | ) 250 | 251 | check querycompare(test, sql("SELECT person.name as user_id, history.creation FROM history LEFT JOIN person ON (history.user_id = person.id) WHERE history.project_id = ? AND history.item_id = ? AND history.is_deleted IS NULL AND (history.choice = 'Comment' OR history.choice = 'Picture' OR history.choice = 'File' OR history.choice = 'Design' OR history.choice = 'Update' OR history.choice = 'Create') ORDER BY history.creation DESC, history.id DESC")) 252 | 253 | 254 | 255 | -------------------------------------------------------------------------------- /tests/select/test_select_deletemarker.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/strutils, 10 | std/unittest 11 | 12 | import 13 | src/sqlbuilderpkg/utils_private 14 | 15 | 16 | const tablesWithDeleteMarkerInit = ["tasks"] 17 | 18 | include src/sqlbuilder_include 19 | 20 | 21 | suite "sqlSelect - delete marker const": 22 | 23 | test "useDeleteMarker = default": 24 | var test: SqlQuery 25 | 26 | test = sqlSelect( 27 | table = "tasks", 28 | select = @["id", "name", "description", "created", "updated", "completed"], 29 | where = @["id ="], 30 | # useDeleteMarker = true 31 | ) 32 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE id = ? AND tasks.is_deleted IS NULL ")) 33 | 34 | 35 | test "useDeleteMarker = true": 36 | var test: SqlQuery 37 | 38 | test = sqlSelect( 39 | table = "tasks", 40 | select = @["id", "name", "description", "created", "updated", "completed"], 41 | where = @["id ="], 42 | useDeleteMarker = true 43 | ) 44 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE id = ? AND tasks.is_deleted IS NULL ")) 45 | 46 | 47 | test "useDeleteMarker = false": 48 | var test: SqlQuery 49 | 50 | test = sqlSelect( 51 | table = "tasks", 52 | select = @["id", "name", "description", "created", "updated", "completed"], 53 | where = @["id ="], 54 | useDeleteMarker = false 55 | ) 56 | check querycompare(test, sql("SELECT id, name, description, created, updated, completed FROM tasks WHERE id = ? ")) 57 | 58 | 59 | -------------------------------------------------------------------------------- /tests/select/test_select_is.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/strutils, 10 | std/unittest 11 | 12 | import 13 | src/sqlbuilder, 14 | src/sqlbuilderpkg/utils_private 15 | 16 | 17 | 18 | 19 | 20 | suite "IS TRUE and IS FALSE": 21 | 22 | test "Simple IS TRUE": 23 | 24 | let test = sqlSelect( 25 | table = "person", 26 | tableAs = "p", 27 | select = [ 28 | "p.name", 29 | ], 30 | where = [ 31 | "p.is_active IS TRUE" 32 | ] 33 | ) 34 | 35 | check querycompare(test, sql("SELECT p.name FROM person AS p WHERE p.is_active IS TRUE")) 36 | 37 | 38 | test "Simple IS NOT TRUE": 39 | 40 | let test = sqlSelect( 41 | table = "person", 42 | tableAs = "p", 43 | select = [ 44 | "p.name", 45 | ], 46 | where = [ 47 | "p.is_active IS NOT TRUE" 48 | ] 49 | ) 50 | 51 | check querycompare(test, sql("SELECT p.name FROM person AS p WHERE p.is_active IS NOT TRUE")) 52 | 53 | 54 | test "Simple IS FALSE": 55 | 56 | let test = sqlSelect( 57 | table = "person", 58 | tableAs = "p", 59 | select = [ 60 | "p.name", 61 | ], 62 | where = [ 63 | "p.is_active IS FALSE" 64 | ] 65 | ) 66 | 67 | check querycompare(test, sql("SELECT p.name FROM person AS p WHERE p.is_active IS FALSE")) 68 | 69 | 70 | test "Simple IS NOT FALSE": 71 | 72 | let test = sqlSelect( 73 | table = "person", 74 | tableAs = "p", 75 | select = [ 76 | "p.name", 77 | ], 78 | where = [ 79 | "p.is_active IS NOT FALSE" 80 | ] 81 | ) 82 | 83 | check querycompare(test, sql("SELECT p.name FROM person AS p WHERE p.is_active IS NOT FALSE")) 84 | 85 | 86 | test "Mixed IS TRUE and IS FALSE": 87 | 88 | let test = sqlSelect( 89 | table = "person", 90 | tableAs = "p", 91 | select = [ 92 | "p.name", 93 | ], 94 | where = [ 95 | "p.is_active IS TRUE", 96 | "p.is_active IS FALSE" 97 | ] 98 | ) 99 | 100 | check querycompare(test, sql("SELECT p.name FROM person AS p WHERE p.is_active IS TRUE AND p.is_active IS FALSE")) -------------------------------------------------------------------------------- /tests/totypes/test_result_to_types.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | 4 | 5 | import std/unittest 6 | 7 | when NimMajor >= 2: 8 | import db_connector/db_sqlite 9 | else: 10 | import std/db_sqlite 11 | 12 | import 13 | std/strutils 14 | 15 | import 16 | src/sqlbuilderpkg/select, 17 | src/sqlbuilderpkg/totypes 18 | 19 | import 20 | tests/create_db 21 | 22 | type 23 | Person = ref object 24 | id: int 25 | name: string 26 | age: int 27 | ident: string 28 | is_nimmer: bool 29 | 30 | # 31 | # Set up a test database 32 | # 33 | createDB() 34 | let db = openDB() 35 | # let db = open("tests/db_types.db", "", "", "") 36 | 37 | # db.exec(sql"DROP TABLE IF EXISTS my_table") 38 | # db.exec(sql"""CREATE TABLE my_table ( 39 | # id INTEGER, 40 | # name VARCHAR(50) NOT NULL, 41 | # age INTEGER, 42 | # ident TEXT, 43 | # is_nimmer BOOLEAN 44 | # )""") 45 | 46 | # db.exec(sql"INSERT INTO my_table (id, name) VALUES (0, ?)", "Jack") 47 | 48 | # for i in 1..5: 49 | # db.exec(sql("INSERT INTO my_table (id, name, age, ident, is_nimmer) VALUES (?, ?, ?, ?, ?)"), $i, "Joe-" & $i, $i, "Nim", (if i <= 2: "true" else: "false")) 50 | 51 | # for i in 6..10: 52 | # db.exec(sql("INSERT INTO my_table (id, name, age, ident) VALUES (?, ?, ?, ?)"), $i, "Cathrine-" & $i, $i, "Lag") 53 | 54 | 55 | 56 | # 57 | # Start testing 58 | # 59 | suite "Map result to types": 60 | 61 | test "getRow() result map post call": 62 | let 63 | columns = @["id","name"] 64 | val = db.getRow(sql("SELECT " & columns.join(",") & " FROM my_table WHERE id = 1")) 65 | res = sqlToType(Person, columns, val) 66 | 67 | check res.id == 1 68 | check res.name == "Joe-1" 69 | check res.age == 0 70 | check res.ident == "" 71 | 72 | 73 | test "getRow() with mixed column order": 74 | let 75 | columns = @["name","id","ident"] 76 | val = db.getRow(sql("SELECT " & columns.join(",") & " FROM my_table WHERE id = 1")) 77 | res = sqlToType(Person, columns, val) 78 | 79 | check res.id == 1 80 | check res.name == "Joe-1" 81 | check res.age == 0 82 | check res.ident == "Nim" 83 | 84 | 85 | test "getAllRows in a seq[T]": 86 | let 87 | columns = @["id","ident","name", "age", "is_nimmer"] 88 | vals = Person.sqlToType( 89 | columns, 90 | db.getAllRows(sql("SELECT " & columns.join(",") & " FROM my_table WHERE ident = 'Nim'")) 91 | ) 92 | 93 | check vals.len == 5 94 | 95 | check vals[0].id == 1 96 | check vals[0].name == "Joe-1" 97 | 98 | check vals[1].id == 2 99 | check vals[1].name == "Joe-2" 100 | 101 | check vals[1].isNimmer == true 102 | check vals[3].isNimmer == false 103 | 104 | 105 | test "getRow() with select()": 106 | let 107 | columns = @["name","id","ident"] 108 | res = sqlToType(Person, columns, db.getRow( 109 | sqlSelect( 110 | table = "my_table", 111 | tableAs = "t", 112 | select = columns, 113 | where = @["id ="], 114 | ), 1) 115 | ) 116 | 117 | check res.id == 1 118 | check res.name == "Joe-1" 119 | check res.age == 0 120 | check res.ident == "Nim" -------------------------------------------------------------------------------- /tests/update/test_update.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/unittest 10 | 11 | import 12 | src/sqlbuilder, 13 | src/sqlbuilderpkg/utils_private 14 | 15 | 16 | 17 | suite "update - custom args": 18 | 19 | test "sqlUpdate using genArgs #1": 20 | let a3 = genArgs("hje", "") 21 | let q = sqlUpdate("my-table", ["name", "age"], ["id"], a3.query) 22 | check querycompare(q, sql("UPDATE my-table SET name = ?, age = ? WHERE id = ?")) 23 | 24 | test "sqlUpdate using genArgs #2": 25 | let a4 = genArgs("hje", dbNullVal) 26 | let q2 = sqlUpdate("my-table", ["name", "age"], ["id"], a4.query) 27 | check querycompare(q2, sql("UPDATE my-table SET name = ?, age = NULL WHERE id = ?")) 28 | 29 | test "sqlUpdate using genArgsColumns #1": 30 | let (s, a1) = genArgsColumns(SQLQueryType.UPDATE, (true, "name", ""), (true, "age", 30), (false, "nim", ""), (true, "", "154")) 31 | let q = sqlUpdate("my-table", s, ["id"], a1.query) 32 | check querycompare(q, sql("UPDATE my-table SET name = NULL, age = ? WHERE id = ?")) 33 | 34 | test "sqlUpdate using genArgsColumns #2 - empty string IS NULL (nim-field)": 35 | let (s, a1) = genArgsColumns(SQLQueryType.UPDATE, (true, "name", ""), (true, "age", 30), (true, "nim", ""), (true, "", "154")) 36 | let q = sqlUpdate("my-table", s, ["id"], a1.query) 37 | check querycompare(q, sql("UPDATE my-table SET name = NULL, age = ?, nim = NULL WHERE id = ?")) 38 | 39 | test "sqlUpdate using genArgsColumns #2 - empty string is ignored (nim-field)": 40 | let (s, a1) = genArgsColumns(SQLQueryType.UPDATE, (true, "name", ""), (true, "age", 30), (false, "nim", ""), (true, "", "154")) 41 | let q = sqlUpdate("my-table", s, ["id"], a1.query) 42 | check querycompare(q, sql("UPDATE my-table SET name = NULL, age = ? WHERE id = ?")) 43 | 44 | test "sqlUpdate using genArgsColumns #3": 45 | let (s, a1) = genArgsColumns(SQLQueryType.UPDATE, (true, "name", ""), (false, "age", 30), (false, "nim", ""), (true, "", "154")) 46 | let q = sqlUpdate("my-table", s, ["id"], a1.query) 47 | check querycompare(q, sql("UPDATE my-table SET name = NULL WHERE id = ?")) 48 | 49 | test "sqlUpdate using genArgsSetNull": 50 | let a2 = genArgsSetNull("hje", "") 51 | let q = sqlUpdate("my-table", ["name", "age"], ["id"], a2.query) 52 | check querycompare(q, sql("UPDATE my-table SET name = ?, age = NULL WHERE id = ?")) 53 | 54 | 55 | suite "update - queries": 56 | 57 | test "update value": 58 | let q = sqlUpdate( 59 | "table", 60 | ["name", "age", "info"], 61 | ["id ="], 62 | ) 63 | check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = ? WHERE id = ?")) 64 | 65 | 66 | 67 | test "update value with NULL": 68 | let q = sqlUpdate( 69 | "table", 70 | ["name", "age", "info = NULL"], 71 | ["id ="], 72 | ) 73 | check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = NULL WHERE id = ?")) 74 | 75 | 76 | 77 | test "update value with NULL multiple": 78 | let q = sqlUpdate( 79 | "table", 80 | ["name = NULL", "age", "info = NULL"], 81 | ["id ="], 82 | ) 83 | check querycompare(q, sql("UPDATE table SET name = NULL, age = ?, info = NULL WHERE id = ?")) 84 | 85 | 86 | 87 | test "update value with spaces": 88 | let q = sqlUpdate( 89 | "table", 90 | ["name = ", "age ", "info =", "hey = NULL "], 91 | ["id ="], 92 | ) 93 | check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = ?, hey = NULL WHERE id = ?")) 94 | 95 | 96 | 97 | 98 | test "update value with WHERE params": 99 | let q = sqlUpdate( 100 | "table", 101 | ["name = NULL", "age", "info = NULL"], 102 | ["id =", "epoch >", "parent IS NULL", "name IS NOT NULL", "age != 22", "age !="], 103 | ) 104 | 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 != ?")) 105 | 106 | 107 | test "update value with WHERE params with spaces": 108 | let q2 = sqlUpdate( 109 | "table", 110 | ["name = NULL", "age", "info = NULL"], 111 | ["id =", " epoch >", "parent IS NULL", "name IS NOT NULL", "age != 22 ", " age !="], 112 | ) 113 | check querycompare(q2, 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 != ?")) 114 | 115 | 116 | 117 | test "update arrays": 118 | let q = sqlUpdate( 119 | "table", 120 | ["parents = ARRAY_APPEND(id, ?)", "age = ARRAY_REMOVE(id, ?)", "info = NULL"], 121 | ["last_name NOT IN ('Anderson', 'Johnson', 'Smith')"], 122 | ) 123 | 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')")) 124 | 125 | 126 | 127 | 128 | suite "update - queries with specified NULL in data and ? in where": 129 | 130 | test "set NULL and where = ?": 131 | let q = sqlUpdate( 132 | "table", 133 | ["name", "age", "info = NULL"], 134 | ["id = ?"], 135 | ) 136 | check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = NULL WHERE id = ?")) 137 | 138 | 139 | test "set NULL and where=?": 140 | let q = sqlUpdate( 141 | "table", 142 | ["name", "age", "info = NULL"], 143 | ["id=?"], 144 | ) 145 | check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = NULL WHERE id=?")) 146 | 147 | test "set =NULL and where=?": 148 | let q = sqlUpdate( 149 | "table", 150 | ["name", "age", "info=NULL"], 151 | ["id=?"], 152 | ) 153 | check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info=NULL WHERE id=?")) 154 | 155 | 156 | 157 | 158 | suite "update macro": 159 | 160 | test "update value": 161 | let q = sqlUpdateMacro( 162 | "table", 163 | ["name", "age", "info"], 164 | ["id ="], 165 | ) 166 | check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = ? WHERE id = ?")) 167 | 168 | 169 | test "update value": 170 | let q = sqlUpdateMacro( 171 | "table", 172 | ["name", "age", "info"], 173 | ["id ="], 174 | ) 175 | check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = ? WHERE id = ?")) 176 | 177 | 178 | 179 | test "update value with NULL": 180 | let q = sqlUpdateMacro( 181 | "table", 182 | ["name", "age", "info = NULL"], 183 | ["id ="], 184 | ) 185 | check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = NULL WHERE id = ?")) 186 | 187 | 188 | 189 | test "update value with NULL multiple": 190 | let q = sqlUpdateMacro( 191 | "table", 192 | ["name = NULL", "age", "info = NULL"], 193 | ["id ="], 194 | ) 195 | check querycompare(q, sql("UPDATE table SET name = NULL, age = ?, info = NULL WHERE id = ?")) 196 | 197 | 198 | 199 | test "update value with spaces": 200 | let q = sqlUpdateMacro( 201 | "table", 202 | ["name = ", "age ", "info =", "hey = NULL "], 203 | ["id ="], 204 | ) 205 | check querycompare(q, sql("UPDATE table SET name = ?, age = ?, info = ?, hey = NULL WHERE id = ?")) 206 | 207 | 208 | 209 | 210 | test "update value with WHERE params": 211 | let q = sqlUpdateMacro( 212 | "table", 213 | ["name = NULL", "age", "info = NULL"], 214 | ["id =", "epoch >", "parent IS NULL", "name IS NOT NULL", "age != 22", "age !="], 215 | ) 216 | 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 != ?")) 217 | 218 | 219 | test "update value with WHERE params with spaces": 220 | let q2 = sqlUpdateMacro( 221 | "table", 222 | ["name = NULL", "age", "info = NULL"], 223 | ["id =", " epoch >", "parent IS NULL", "name IS NOT NULL", "age != 22 ", " age !="], 224 | ) 225 | check querycompare(q2, 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 != ?")) 226 | 227 | 228 | test "update arrays": 229 | let q = sqlUpdateMacro( 230 | "table", 231 | ["parents = ARRAY_APPEND(id, ?)", "age = ARRAY_REMOVE(id, ?)", "info = NULL"], 232 | ["last_name NOT IN ('Anderson', 'Johnson', 'Smith')"], 233 | ) 234 | 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')")) 235 | 236 | 237 | -------------------------------------------------------------------------------- /tests/update/test_update_arrays.nim: -------------------------------------------------------------------------------- 1 | # Copyright Thomas T. Jarløv (TTJ) - ttj@ttj.dk 2 | 3 | when NimMajor >= 2: 4 | import db_connector/db_common 5 | else: 6 | import std/db_common 7 | 8 | import 9 | std/unittest 10 | 11 | import 12 | src/sqlbuilder, 13 | src/sqlbuilderpkg/utils_private 14 | 15 | 16 | 17 | 18 | 19 | suite "update - arrays": 20 | 21 | test "[manual] update arrays - ARRAY_REMOVE": 22 | 23 | let q = sqlUpdate( 24 | "table", 25 | ["name", "project_ids = ARRAY_REMOVE(project_ids, ?)"], 26 | ["id ="], 27 | ) 28 | check querycompare(q, sql("UPDATE table SET name = ?, project_ids = ARRAY_REMOVE(project_ids, ?) WHERE id = ?")) 29 | 30 | 31 | test "[manual] update arrays - only ARRAY_REMOVE": 32 | 33 | let q = sqlUpdate( 34 | "table", 35 | ["project_ids = ARRAY_REMOVE(project_ids, ?)"], 36 | ["id ="], 37 | ) 38 | check querycompare(q, sql("UPDATE table SET project_ids = ARRAY_REMOVE(project_ids, ?) WHERE id = ?")) 39 | 40 | 41 | test "update array with array_cat": 42 | let q = sqlUpdate( 43 | table = "projects", 44 | data = [ 45 | "sorting = ARRAY_CAT(sorting, ?::int[])" 46 | ], 47 | where = [ 48 | "id = ANY(?::int[])" 49 | ] 50 | ) 51 | 52 | check querycompare(q, sql("UPDATE projects SET sorting = ARRAY_CAT(sorting, ?::int[]) WHERE id = ANY(?::int[])")) 53 | 54 | 55 | 56 | 57 | suite "update - arrays where cond ANY": 58 | 59 | test "array": 60 | 61 | let q = sqlUpdate( 62 | "table", 63 | ["name"], 64 | ["some_ids = ANY(?::INT[])"], 65 | ) 66 | check querycompare(q, sql("UPDATE table SET name = ? WHERE some_ids = ANY(?::INT[])")) 67 | 68 | 69 | 70 | 71 | suite "update - arrays dedicated": 72 | 73 | test "[dedicated] update array - ARRAY_REMOVE": 74 | 75 | let q = sqlUpdateArrayRemove( 76 | "table", 77 | ["project_ids"], 78 | ["id ="], 79 | ) 80 | check querycompare(q, sql("UPDATE table SET project_ids = ARRAY_REMOVE(project_ids, ?) WHERE id = ?")) 81 | 82 | 83 | test "[dedicated] update arrays - ARRAY_REMOVE": 84 | 85 | let q = sqlUpdateArrayRemove( 86 | "table", 87 | ["project_ids", "task_ids"], 88 | ["id ="], 89 | ) 90 | check querycompare(q, sql("UPDATE table SET project_ids = ARRAY_REMOVE(project_ids, ?), task_ids = ARRAY_REMOVE(task_ids, ?) WHERE id = ?")) 91 | 92 | 93 | test "[dedicated] update array - ARRAY_APPEND": 94 | 95 | let q = sqlUpdateArrayAppend( 96 | "table", 97 | ["project_ids"], 98 | ["id ="], 99 | ) 100 | check querycompare(q, sql("UPDATE table SET project_ids = ARRAY_APPEND(project_ids, ?) WHERE id = ?")) 101 | 102 | 103 | test "[dedicated] update arrays - ARRAY_APPEND": 104 | 105 | let q = sqlUpdateArrayAppend( 106 | "table", 107 | ["project_ids", "task_ids"], 108 | ["id ="], 109 | ) 110 | check querycompare(q, sql("UPDATE table SET project_ids = ARRAY_APPEND(project_ids, ?), task_ids = ARRAY_APPEND(task_ids, ?) WHERE id = ?")) 111 | 112 | 113 | 114 | test "[macro] update array - ARRAY_REMOVE": 115 | 116 | let q = sqlUpdateMacroArrayRemove( 117 | "table", 118 | ["project_ids"], 119 | ["id ="], 120 | ) 121 | check querycompare(q, sql("UPDATE table SET project_ids = ARRAY_REMOVE(project_ids, ?) WHERE id = ?")) 122 | 123 | 124 | test "[macro] update array - ARRAY_APPEND": 125 | 126 | let q = sqlUpdateMacroArrayAppend( 127 | "table", 128 | ["project_ids"], 129 | ["id ="], 130 | ) 131 | check querycompare(q, sql("UPDATE table SET project_ids = ARRAY_APPEND(project_ids, ?) WHERE id = ?")) 132 | 133 | 134 | test "[macro] update arrays - ARRAY_APPEND": 135 | 136 | let q = sqlUpdateMacroArrayAppend( 137 | "table", 138 | ["project_ids", "task_ids"], 139 | ["id ="], 140 | ) 141 | check querycompare(q, sql("UPDATE table SET project_ids = ARRAY_APPEND(project_ids, ?), task_ids = ARRAY_APPEND(task_ids, ?) WHERE id = ?")) 142 | 143 | 144 | 145 | 146 | 147 | # ORDER BY array_position(array[" & projectIDs & "], project.id) 148 | --------------------------------------------------------------------------------