├── .github ├── FUNDING.yml └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── create_table.png ├── docs ├── index.html ├── nimdoc.out.css ├── sugar.html └── sugar.jpg ├── examples ├── create_table_example.nim ├── database_fields_example.nim ├── drop_table_example.nim ├── expect_fail.nim ├── gatabase_example.nim ├── get_concrete_types_value_example.nim ├── minimal.nim ├── sqlalchemy_example.png └── sqlalchemy_example.py ├── gatabase.nimble ├── gatabase.png ├── multigata.png ├── sql_checking.png ├── src ├── gatabase.nim └── gatabase │ ├── sugar.nim │ └── templates.nim ├── temp.jpg └── tests ├── test.nim ├── test_js.nim └── test_multigata.nim /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://gist.github.com/juancarlospaco/37da34ed13a609663f55f4466c4dbc3e"] 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Nim 👑 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-node@v1 13 | - uses: harmon758/postgresql-action@v1 14 | with: 15 | postgresql version: '12' 16 | postgresql db: 'postgres' 17 | postgresql user: 'postgres' 18 | postgresql password: 'postgres' 19 | - uses: jiro4989/setup-nim-action@v1.0.2 20 | with: 21 | nim-version: '1.4.0' 22 | 23 | - name: Nimble Check package validity 24 | run: | 25 | export PATH=$HOME/.nimble/bin:$PATH 26 | nimble check 27 | 28 | - name: Install project 29 | run: | 30 | export PATH=$HOME/.nimble/bin:$PATH 31 | nimble install 32 | 33 | - name: Unittest C 34 | run: | 35 | export PATH=$HOME/.nimble/bin:$PATH 36 | nim c --panics:on --gc:orc --experimental:strictFuncs tests/test.nim 37 | 38 | - name: Unittest CPP 39 | run: | 40 | export PATH=$HOME/.nimble/bin:$PATH 41 | nim cpp --panics:on --gc:orc --experimental:strictFuncs tests/test.nim 42 | 43 | - name: Unittest C Release 44 | run: | 45 | export PATH=$HOME/.nimble/bin:$PATH 46 | nim c -d:release --panics:on --gc:orc --experimental:strictFuncs tests/test.nim 47 | 48 | - name: Unittest CPP Release 49 | run: | 50 | export PATH=$HOME/.nimble/bin:$PATH 51 | nim cpp -d:release --panics:on --gc:orc --experimental:strictFuncs tests/test.nim 52 | 53 | - name: Unittest JavaScript 54 | run: | 55 | export PATH=$HOME/.nimble/bin:$PATH 56 | nim js -r --experimental:strictFuncs tests/test_js.nim 57 | 58 | - name: Project X 59 | run: | 60 | export PATH=$HOME/.nimble/bin:$PATH 61 | nim c -r -d:postgres --gc:orc --panics:on tests/test_multigata.nim 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests/test 2 | *.sql 3 | *.c 4 | *.h 5 | *.out.css 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Juan Carlos 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 | # Gatabase 2 | 3 | ![screenshot](https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/master/gatabase.png "Connection-Pooling Compile-Time ORM for Nim") 4 | 5 | ![](https://img.shields.io/github/languages/count/juancarlospaco/nim-gatabase?logoColor=green&style=for-the-badge) 6 | ![](https://img.shields.io/github/languages/top/juancarlospaco/nim-gatabase?style=for-the-badge) 7 | ![](https://img.shields.io/github/stars/juancarlospaco/nim-gatabase?style=for-the-badge) 8 | ![](https://img.shields.io/github/languages/code-size/juancarlospaco/nim-gatabase?style=for-the-badge) 9 | ![](https://img.shields.io/github/issues-raw/juancarlospaco/nim-gatabase?style=for-the-badge) 10 | ![](https://img.shields.io/github/issues-pr-raw/juancarlospaco/nim-gatabase?style=for-the-badge) 11 | ![](https://img.shields.io/github/last-commit/juancarlospaco/nim-gatabase?style=for-the-badge) 12 | ![](https://img.shields.io/liberapay/patrons/juancarlospaco?style=for-the-badge) 13 | ![](https://img.shields.io/twitch/status/juancarlospaco?style=for-the-badge) 14 | 15 | ![screenshot](https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/master/temp.jpg "Connection-Pooling Compile-Time ORM for Nim") 16 | 17 | - Works with ARC, ORC, `--panics:on`, `--experimental:strictFuncs`. 18 | 19 | 20 | # Use 21 | 22 | - Gatabase is designed as 1 simplified [Strong Static Typed](https://en.wikipedia.org/wiki/Type_system#Static_type_checking) Connection-Pooling [Compile-Time](https://wikipedia.org/wiki/Compile_time) [SQL](https://wikipedia.org/wiki/SQL) [DSL](https://wikipedia.org/wiki/Domain-specific_language) [Sugar](https://en.wikipedia.org/wiki/Syntactic_sugar). Nim mimics SQL. ~1000 LoC. 23 | - Gatabase syntax is almost the same as SQL syntax, [no new ORM to learn ever again](https://pgexercises.com/questions/basic/selectall.html), any [SQL WYSIWYG is your GUI](https://pgmodeler.io/screenshots). 24 | - You can literally [Copy&Paste a SQL query from StackOverflow](https://stackoverflow.com/questions/tagged/postgresql?tab=Frequent) to Gatabase and with few tiny syntax tweaks is running. 25 | - SQL is Minified when build for Release, Pretty-Printed when build for Debug. It can be assigned to `let` and `const`. 26 | - **Static Connection Pooling Array with 100+ ORM Queries.** 27 | - **Uses only `system.nim`, everything done via `template`/`macro`, `strutils` is not imported, future-proof your code.** 28 | 29 | ![](https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/master/multigata.png) 30 | 31 | 32 | ### Support 33 | 34 | - All SQL standard syntax is supported. 35 | - ✅ `--` Human readable comments, multi-line comments produce multi-line SQL comments, requires [Stropping](https://en.wikipedia.org/wiki/Stropping_(syntax)#Modern_use). 36 | - ✅ `COMMENT`, Postgres-only. 37 | - ✅ `UNION`, `UNION ALL`. 38 | - ✅ `INTERSECT`, `INTERSECT ALL`. 39 | - ✅ `EXCEPT`, `EXCEPT ALL`, requires [Stropping](https://en.wikipedia.org/wiki/Stropping_(syntax)#Modern_use). 40 | - ✅ `CASE` with multiple `WHEN` and 1 `ELSE` with correct indentation, requires [Stropping](https://en.wikipedia.org/wiki/Stropping_(syntax)#Modern_use). 41 | - ✅ `INNER JOIN`, `LEFT JOIN`, `RIGHT JOIN`, `FULL JOIN`. 42 | - ✅ `OFFSET`. 43 | - ✅ `LIMIT`. 44 | - ✅ `FROM`, requires [Stropping](https://en.wikipedia.org/wiki/Stropping_(syntax)#Modern_use). 45 | - ✅ `WHERE`, `WHERE NOT`, `WHERE EXISTS`, `WHERE NOT EXISTS`. 46 | - ✅ `ORDER BY`. 47 | - ✅ `SELECT`, `SELECT *`, `SELECT DISTINCT`. 48 | - ✅ `SELECT TOP`, `SELECT MIN`, `SELECT MAX`, `SELECT AVG`, `SELECT SUM`, `SELECT COUNT`. 49 | - ✅ `SELECT trim(lower( ))` for strings, `SELECT round( )` for floats, useful shortcuts. 50 | - ✅ `DELETE FROM`. 51 | - ✅ `LIKE`, `NOT LIKE`. 52 | - ✅ `BETWEEN`, `NOT BETWEEN`. 53 | - ✅ `HAVING`. 54 | - ✅ `INSERT INTO`. 55 | - ✅ `IS NULL`, `IS NOT NULL`. 56 | - ✅ `UPDATE`, `SET`. 57 | - ✅ `VALUES`. 58 | - ✅ `DROP TABLE IF EXISTS`. 59 | - ✅ `CREATE TABLE IF NOT EXISTS`. 60 | 61 | Not supported: 62 | - Deep complex nested SubQueries are not supported, because KISS. 63 | - `TRUNCATE`, because is the same as `DELETE FROM` without a `WHERE`. 64 | - `WHERE IN`, `WHERE NOT IN`, because is the same as `JOIN`, but `JOIN` is a lot faster. 65 | 66 | 67 | ## API Equivalents 68 | 69 | Nim StdLib API | Gatabase ORM API 70 | -------------------|------------------ 71 | `tryExec` | [`tryExec`](https://juancarlospaco.github.io/nim-gatabase/#tryExec.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 72 | `exec` | [`exec`](https://juancarlospaco.github.io/nim-gatabase/#exec.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 73 | `getRow` | [`getRow`](https://juancarlospaco.github.io/nim-gatabase/#getRow.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 74 | `getAllRows` | [`getAllRows`](https://juancarlospaco.github.io/nim-gatabase/#getAllRows.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 75 | `getValue` | [`getValue`](https://juancarlospaco.github.io/nim-gatabase/#getValue.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 76 | `tryInsertID` | [`tryInsertID`](https://juancarlospaco.github.io/nim-gatabase/#tryInsertID.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 77 | `insertID` | [`insertID`](https://juancarlospaco.github.io/nim-gatabase/#insertID.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 78 | `execAffectedRows` | [`execAffectedRows`](https://juancarlospaco.github.io/nim-gatabase/#execAffectedRows.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 79 | 80 | 81 | # Output 82 | 83 | Output | Gatabase ORM API 84 | -----------|------------------ 85 | `bool` | [`tryExec`](https://juancarlospaco.github.io/nim-gatabase/#tryExec.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 86 | `Row` | [`getRow`](https://juancarlospaco.github.io/nim-gatabase/#getRow.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 87 | `seq[Row]` | [`getAllRows`](https://juancarlospaco.github.io/nim-gatabase/#getAllRows.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 88 | `int64` | [`tryInsertID`](https://juancarlospaco.github.io/nim-gatabase/#tryInsertID.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 89 | `int64` | [`insertID`](https://juancarlospaco.github.io/nim-gatabase/#insertID.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 90 | `int64` | [`execAffectedRows`](https://juancarlospaco.github.io/nim-gatabase/#execAffectedRows.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 91 | `SqlQuery` | [`sqls`](https://juancarlospaco.github.io/nim-gatabase/#sqls.t%2Cuntyped) 92 | `any` | [`getValue`](https://juancarlospaco.github.io/nim-gatabase/#getValue.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 93 | | | [`exec`](https://juancarlospaco.github.io/nim-gatabase/#exec.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) 94 | 95 | - [`getValue`](https://juancarlospaco.github.io/nim-gatabase/#getValue.t%2Cvarargs%5Bstring%2C%5D%2Cuntyped) can return any specific arbitrary concrete type, depending on the arguments used (Optional). 96 | - [Gatabase Sugar can return very specific concrete types (Optional).](https://juancarlospaco.github.io/nim-gatabase/sugar.html#18) 97 | 98 | 99 | # Install 100 | 101 | - [`nimble install gatabase`](https://nimble.directory/pkg/gatabase "nimble install gatabase 👑 https://nimble.directory/pkg/gatabase") 102 | 103 | 104 | ### Comments 105 | 106 | ```sql 107 | -- SQL Comments are supported, but stripped when build for Release. This is SQL. 108 | ``` 109 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 110 | ```nim 111 | `--` "SQL Comments are supported, but stripped when build for Release. This is Nim." 112 | ``` 113 | 114 | 115 | ### SELECT & FROM 116 | 117 | ```sql 118 | SELECT * 119 | FROM sometable 120 | ``` 121 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 122 | ```nim 123 | select '*' 124 | `from` "sometable" 125 | ``` 126 | 127 | --- 128 | 129 | ```sql 130 | SELECT somecolumn 131 | FROM sometable 132 | ``` 133 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 134 | ```nim 135 | select "somecolumn" 136 | `from` "sometable" 137 | ``` 138 | 139 | --- 140 | 141 | ```sql 142 | SELECT DISTINCT somecolumn 143 | ``` 144 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 145 | ```nim 146 | selectdistinct "somecolumn" 147 | ``` 148 | 149 | 150 | ### MIN & MAX 151 | 152 | ```sql 153 | SELECT MIN(somecolumn) 154 | ``` 155 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 156 | ```nim 157 | selectmin "somecolumn" 158 | ``` 159 | 160 | --- 161 | 162 | ```sql 163 | SELECT MAX(somecolumn) 164 | ``` 165 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 166 | ```nim 167 | selectmax "somecolumn" 168 | ``` 169 | 170 | 171 | ### COUNT & AVG & SUM 172 | 173 | ```sql 174 | SELECT COUNT(somecolumn) 175 | ``` 176 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 177 | ```nim 178 | selectcount "somecolumn" 179 | ``` 180 | 181 | --- 182 | 183 | ```sql 184 | SELECT AVG(somecolumn) 185 | ``` 186 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 187 | ```nim 188 | selectavg "somecolumn" 189 | ``` 190 | 191 | --- 192 | 193 | ```sql 194 | SELECT SUM(somecolumn) 195 | ``` 196 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 197 | ```nim 198 | selectsum "somecolumn" 199 | ``` 200 | 201 | 202 | ### TRIM & LOWER 203 | 204 | ```sql 205 | SELECT trim(lower(somestringcolumn)) 206 | ``` 207 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 208 | ```nim 209 | selecttrim "somestringcolumn" 210 | ``` 211 | 212 | 213 | ### ROUND 214 | 215 | ```sql 216 | SELECT round(somefloatcolumn, 2) 217 | ``` 218 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 219 | ```nim 220 | selectround2 "somefloatcolumn" 221 | ``` 222 | 223 | --- 224 | 225 | ```sql 226 | SELECT round(somefloatcolumn, 4) 227 | ``` 228 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 229 | ```nim 230 | selectround4 "somefloatcolumn" 231 | ``` 232 | 233 | --- 234 | 235 | ```sql 236 | SELECT round(somefloatcolumn, 6) 237 | ``` 238 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 239 | ```nim 240 | selectround6 "somefloatcolumn" 241 | ``` 242 | 243 | 244 | ### TOP 245 | 246 | ```sql 247 | SELECT TOP 5 * 248 | ``` 249 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 250 | ```nim 251 | selecttop 5 252 | ``` 253 | 254 | 255 | ### WHERE 256 | 257 | ```sql 258 | SELECT somecolumn 259 | FROM sometable 260 | WHERE power > 9000 261 | ``` 262 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 263 | ```nim 264 | select "somecolumn" 265 | `from` "sometable" 266 | where "power > 9000" 267 | ``` 268 | 269 | 270 | ### LIMIT & OFFSET 271 | 272 | ```sql 273 | OFFSET 9 274 | LIMIT 42 275 | ``` 276 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 277 | ```nim 278 | offset 9 279 | limit 42 280 | ``` 281 | 282 | 283 | ### INSERT 284 | 285 | ```sql 286 | INSERT INTO person 287 | VALUES (42, 'Nikola Tesla', true, 'nikola.tesla@nim-lang.org', 9.6) 288 | ``` 289 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 290 | ```nim 291 | insertinto "person" 292 | values 5 293 | ``` 294 | 295 | **Example:** 296 | ```nim 297 | insertinto "person" 298 | values 5 299 | ``` 300 | ⬆️ Nim ⬆️          ⬇️ Generated SQL ⬇️ 301 | ```sql 302 | INSERT INTO person 303 | VALUES ( ?, ?, ?, ?, ? ) 304 | ``` 305 | 306 | * The actual values are passed via `varargs` directly using stdlib, Gatabase does not format values ever. 307 | * Nim code `values 5` generates `VALUES ( ?, ?, ?, ?, ? )`. 308 | 309 | 310 | ### UPDATE 311 | 312 | ```sql 313 | UPDATE person 314 | SET name = 'Nikola Tesla', mail = 'nikola.tesla@nim-lang.org' 315 | ``` 316 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 317 | ```nim 318 | update "person" 319 | set ["name", "mail"] 320 | ``` 321 | 322 | **Example:** 323 | ```nim 324 | update "person" 325 | set ["name", "mail"] 326 | ``` 327 | ⬆️ Nim ⬆️          ⬇️ Generated SQL ⬇️ 328 | ```sql 329 | UPDATE person 330 | SET name = ?, mail = ? 331 | ``` 332 | 333 | * The actual values are passed via `varargs` directly using stdlib, Gatabase does not format values ever. 334 | * Nim code `set ["key", "other", "another"]` generates `SET key = ?, other = ?, another = ?`. 335 | 336 | 337 | ### DELETE 338 | 339 | ```sql 340 | DELETE debts 341 | ``` 342 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 343 | ```nim 344 | delete "debts" 345 | ``` 346 | 347 | 348 | ### ORDER BY 349 | 350 | ```sql 351 | ORDER BY ASC 352 | ``` 353 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 354 | ```nim 355 | orderby "asc" 356 | ``` 357 | 358 | --- 359 | 360 | ```sql 361 | ORDER BY DESC 362 | ``` 363 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 364 | ```nim 365 | orderby "desc" 366 | ``` 367 | 368 | 369 | ### CASE 370 | 371 | ```sql 372 | CASE 373 | WHEN foo > 10 THEN 9 374 | WHEN bar < 42 THEN 5 375 | ELSE 0 376 | END 377 | ``` 378 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 379 | ```nim 380 | `case` { 381 | "foo > 10": "9", 382 | "bar < 42": "5", 383 | "else": "0" 384 | } 385 | ``` 386 | 387 | 388 | ### COMMENT 389 | 390 | ```sql 391 | COMMENT ON TABLE myTable IS 'This is an SQL COMMENT on a TABLE' 392 | ``` 393 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 394 | ```nim 395 | commentontable {"myTable": "This is an SQL COMMENT on a TABLE"} 396 | ``` 397 | 398 | --- 399 | 400 | ```sql 401 | COMMENT ON COLUMN myColumn IS 'This is an SQL COMMENT on a COLUMN' 402 | ``` 403 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 404 | ```nim 405 | commentoncolumn {"myColumn": "This is an SQL COMMENT on a COLUMN"} 406 | ``` 407 | 408 | --- 409 | 410 | ```sql 411 | COMMENT ON DATABASE myDatabase IS 'This is an SQL COMMENT on a DATABASE' 412 | ``` 413 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 414 | ```nim 415 | commentondatabase {"myDatabase": "This is an SQL COMMENT on a DATABASE"} 416 | ``` 417 | 418 | 419 | ### GROUP BY 420 | 421 | ```sql 422 | GROUP BY country 423 | ``` 424 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 425 | ```nim 426 | groupby "country" 427 | ``` 428 | 429 | 430 | ### JOIN 431 | 432 | ```sql 433 | FULL JOIN tablename 434 | ``` 435 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 436 | ```nim 437 | fulljoin "tablename" 438 | ``` 439 | 440 | --- 441 | 442 | ```sql 443 | INNER JOIN tablename 444 | ``` 445 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 446 | ```nim 447 | innerjoin "tablename" 448 | ``` 449 | 450 | --- 451 | 452 | ```sql 453 | LEFT JOIN tablename 454 | ``` 455 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 456 | ```nim 457 | leftjoin "tablename" 458 | ``` 459 | 460 | --- 461 | 462 | ```sql 463 | RIGHT JOIN tablename 464 | ``` 465 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 466 | ```nim 467 | rightjoin "tablename" 468 | ``` 469 | 470 | 471 | ### HAVING 472 | 473 | ```sql 474 | HAVING beer > 5 475 | ``` 476 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 477 | ```nim 478 | having "beer > 5" 479 | ``` 480 | 481 | 482 | ### UNION 483 | 484 | ```sql 485 | UNION ALL 486 | ``` 487 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 488 | ```nim 489 | union true 490 | ``` 491 | 492 | --- 493 | 494 | ```sql 495 | UNION 496 | ``` 497 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 498 | ```nim 499 | union false 500 | ``` 501 | 502 | 503 | ### INTERSECT 504 | 505 | ```sql 506 | INTERSECT ALL 507 | ``` 508 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 509 | ```nim 510 | intersect true 511 | ``` 512 | 513 | --- 514 | 515 | ```sql 516 | INTERSECT 517 | ``` 518 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 519 | ```nim 520 | intersect false 521 | ``` 522 | 523 | 524 | ### EXCEPT 525 | 526 | ```sql 527 | EXCEPT ALL 528 | ``` 529 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 530 | ```nim 531 | `except` true 532 | ``` 533 | 534 | --- 535 | 536 | ```sql 537 | EXCEPT 538 | ``` 539 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 540 | ```nim 541 | `except` false 542 | ``` 543 | 544 | 545 | ### IS NULL 546 | 547 | ```sql 548 | IS NULL 549 | ``` 550 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 551 | ```nim 552 | isnull true 553 | ``` 554 | 555 | --- 556 | 557 | ```sql 558 | IS NOT NULL 559 | ``` 560 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 561 | ```nim 562 | isnull false 563 | ``` 564 | 565 | --- 566 | 567 | ### DROP TABLE 568 | 569 | ```sql 570 | DROP TABLE IF EXISTS tablename 571 | ``` 572 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 573 | ```nim 574 | dropTable "tablename" 575 | ``` 576 | 577 | - `dropTable` is part of [Gatabase Sugar (Optional).](https://juancarlospaco.github.io/nim-gatabase/sugar.html) 578 | 579 | --- 580 | 581 | ### CREATE TABLE 582 | 583 | ![](https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/master/create_table.png) 584 | 585 | 586 | ```sql 587 | CREATE TABLE IF NOT EXISTS kitten( 588 | id INTEGER PRIMARY KEY, 589 | age INTEGER NOT NULL DEFAULT 1, 590 | sex VARCHAR(1) NOT NULL DEFAULT 'f', 591 | name TEXT NOT NULL DEFAULT 'fluffy', 592 | rank REAL NOT NULL DEFAULT 3.14, 593 | ); 594 | ``` 595 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 596 | ```nim 597 | let myTable = createTable "kitten": [ 598 | "age" := 1, 599 | "sex" := 'f', 600 | "name" := "fluffy", 601 | "rank" := 3.14, 602 | ] 603 | ``` 604 | 605 | No default values: 606 | 607 | ```sql 608 | CREATE TABLE IF NOT EXISTS kitten( 609 | id INTEGER PRIMARY KEY, 610 | age INTEGER, 611 | sex VARCHAR(1), 612 | name TEXT, 613 | rank REAL, 614 | ); 615 | ``` 616 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 617 | ```nim 618 | let myTable = createTable "kitten": [ 619 | "age" := int, 620 | "sex" := char, 621 | "name" := string, 622 | "rank" := float, 623 | ] 624 | ``` 625 | 626 | More examples: 627 | 628 | ```sql 629 | CREATE TABLE IF NOT EXISTS kitten( 630 | id INTEGER PRIMARY KEY, 631 | age INTEGER NOT NULL DEFAULT 1, 632 | sex VARCHAR(1), 633 | ); 634 | ``` 635 | ⬆️ SQL ⬆️          ⬇️ Nim ⬇️ 636 | ```nim 637 | let myTable = createTable "kitten": [ 638 | "age" := 1, 639 | "sex" := char, 640 | ] 641 | ``` 642 | 643 | And more examples: https://github.com/juancarlospaco/nim-gatabase/blob/master/examples/database_fields_example.nim#L1 644 | 645 | - `createTable` is part of [Gatabase Sugar (Optional).](https://juancarlospaco.github.io/nim-gatabase/sugar.html) 646 | 647 | --- 648 | 649 | ### Wildcards 650 | 651 | - Nim `'*'` ➡️ SQL `*`. 652 | - Nim `'?'` ➡️ SQL `?`. 653 | 654 | 655 | # Anti-Obfuscation 656 | 657 | Gatabase wont like Obfuscation, its code is easy to read and similar to Pretty-Printed SQL. [`nimpretty` friendly](https://nim-lang.github.io/Nim/tools.html). Very [KISS](https://en.wikipedia.org/wiki/KISS_principle). 658 | 659 | **Compiles Ok:** 660 | ```nim 661 | let variable = sqls: 662 | select '*' 663 | `from` "clients" 664 | groupby "country" 665 | orderby AscNullsLast 666 | ``` 667 | 668 | **Fails to Compile:** 669 | 670 | - `let variable = sqls: select('*') from("clients") groupby("country") orderby(AscNullsLast)` 671 | - `let variable = sqls: '*'.select() "clients".from() "country".groupby() AscNullsLast.orderby()` 672 | - `let variable = sqls: select '*' from "clients" groupby "country" orderby AscNullsLast` 673 | - `let variable = sqls:select'*' from"clients" groupby"country" orderby AscNullsLast` 674 | 675 | *This helps on big projects where each developer tries to use a different code style.* 676 | 677 | 678 | # Your data, your way 679 | 680 | Nim has `template` is like a literal copy&paste of code in-place with no performance cost, 681 | that allows you to create your own custom ORM function callbacks on-the-fly, 682 | like the ones used on scripting languages. 683 | 684 | ```nim 685 | template getMemes(): string = 686 | result = [].getValue: 687 | select "url" 688 | `from` "memes" 689 | limit 1 690 | ``` 691 | 692 | Then you do `getMemes()` when you need it❕. The API that fits your ideas. 693 | 694 | From this `MyClass.meta.Session.query(Memes).all().filter().first()` to this `getMemes()`. 695 | 696 | 697 | # For Python Devs 698 | 699 | Remember on Python2 you had like `print "value"`?, on Nim you can do the same for any function, 700 | then we made functions to mimic basic standard SQL, like `select "value"` and it worked, 701 | its Type-Safe and valid Nim code, you have an ORM that gives you the freedom and power, 702 | this allows to support interesting features, like `CASE`, `UNION`, `INTERSECT`, `COMMENT`, etc. 703 | 704 | When you get used to `template` it requires a lot less code to do the same than SQLAlchemy. 705 | 706 | 707 | ```python 708 | #!/usr/bin/env python3 709 | # -*- coding: utf-8 -*- 710 | from sqlalchemy import create_engine, MetaData, Table 711 | from sqlalchemy import Column, Integer, String, Boolean, Float 712 | 713 | engine = create_engine("sqlite:///:memory:", echo=False) 714 | engine.execute(""" 715 | create table if not exists person( 716 | id integer primary key, 717 | name varchar(9) not null unique, 718 | active bool not null default true, 719 | rank float not null default 0.0 720 | ); """ 721 | ) 722 | 723 | 724 | meta = MetaData() 725 | persons = Table( 726 | "person", meta, 727 | Column("id", Integer, primary_key = True), 728 | Column("name", String, nullable = False, unique = True), 729 | Column("active", Boolean, nullable = False, default = True), 730 | Column("rank", Float, nullable = False, default = 0.0), 731 | ) 732 | 733 | 734 | conn = engine.connect() 735 | 736 | 737 | ins = persons.insert() 738 | ins = persons.insert().values(id = 42, name = "Pepe", active = True, rank = 9.6) 739 | result = conn.execute(ins) 740 | 741 | 742 | persons_query = persons.select() 743 | result = conn.execute(persons_query) 744 | row = result.fetchone() 745 | 746 | print(row) 747 | 748 | ``` 749 | ⬆️ CPython 3 + SQLAlchemy ⬆️          ⬇️ Nim 1.0 + Gatabase ⬇️ 750 | ```nim 751 | import db_sqlite, gatabase 752 | 753 | let db = open(":memory:", "", "", "") 754 | db.exec(sql""" 755 | create table if not exists person( 756 | id integer primary key, 757 | name varchar(9) not null unique, 758 | active bool not null default true, 759 | rank float not null default 0.0 760 | ); """) 761 | 762 | 763 | exec [42, "Pepe", true, 9.6]: 764 | insertinto "person" 765 | values 4 766 | 767 | 768 | let row = [].getRow: 769 | select '*' 770 | `from` "person" 771 | 772 | echo row 773 | ``` 774 | 775 | 776 | # Smart SQL Checking 777 | 778 | ![screenshot](https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/master/sql_checking.png "Smart SQL Checking") 779 | 780 | It will perform a SQL Syntax checking at compile-time. Examples here Fail **intentionally** as expected: 781 | 782 | ```nim 783 | exec []: 784 | where "failure" 785 | ``` 786 | 787 | Fails to compile as expected, with a friendly error: 788 | ``` 789 | gatabase.nim(48, 16) Warning: WHERE without SELECT nor INSERT nor UPDATE nor DELETE. 790 | ``` 791 | 792 | Typical error of making a `DELETE FROM` without `WHERE` that deletes all your data: 793 | ```nim 794 | exec []: 795 | delete "users" 796 | ``` 797 | 798 | Compiles but prints a friendly warning: 799 | ``` 800 | gatabase.nim(207, 57) Warning: DELETE FROM without WHERE. 801 | ``` 802 | 803 | Typical [bad practice of using `SELECT *` everywhere](https://stackoverflow.com/a/3639964): 804 | ```nim 805 | exec []: 806 | select '*' 807 | ``` 808 | 809 | Compiles but prints a friendly warning: 810 | ``` 811 | gatabase.nim(20, 50) Warning: SELECT * is bad practice. 812 | ``` 813 | 814 | Non-SQL wont compile, even if its valid Nim: 815 | ```nim 816 | sqls: 817 | discard 818 | 819 | sqls: 820 | echo "This is not SQL, wont compile" 821 | ``` 822 | 823 | 824 | ### Gatabase Diagrams 825 | 826 | - https://twitter.com/drawio/status/1271774136126275584 827 | 828 | 829 | ### Tests 830 | 831 | ```console 832 | $ nimble test 833 | 834 | [Suite] Gatabase ORM Tests 835 | [OK] let INSERT INTO 836 | [OK] let SELECT ... FROM ... WHERE 837 | [OK] let SELECT ... (comment) ... FROM ... COMMENT 838 | [OK] let SELECT ... FROM ... LIMIT ... OFFSET 839 | [OK] let INSERT INTO 840 | [OK] let UNION ALL ... ORBER BY ... IS NOT NULL 841 | [OK] let SELECT DISTINCT ... FROM ... WHERE 842 | [OK] let INSERT INTO 843 | [OK] const SELECT ... FROM ... WHERE 844 | [OK] const SELECT ... (comment) ... FROM ... COMMENT 845 | [OK] const SELECT ... FROM ... LIMIT ... OFFSET 846 | [OK] const INSERT INTO 847 | [OK] const UNION ALL ... ORBER BY ... IS NOT NULL 848 | [OK] const INTERSECT ALL 849 | [OK] const EXCEPT ALL 850 | [OK] const SELECT DISTINCT ... FROM ... WHERE 851 | [OK] var CASE 852 | [OK] var SELECT MAX .. WHERE EXISTS ... OFFSET ... LIMIT ... ORDER BY 853 | [OK] SELECT TRIM 854 | [OK] SELECT ROUND 855 | [OK] var DELETE FROM WHERE 856 | ``` 857 | 858 | - Tests use a real database SQLite on RAM `":memory:"` with a `"person"` table. +20 Tests. 859 | - [CI uses GitHub Actions CI.](https://github.com/juancarlospaco/nim-gatabase/actions) 860 | 861 | 862 | # Requisites 863 | 864 | - **None.** 865 | 866 | 867 | ## Stars 868 | 869 | ![Stars over time](https://starchart.cc/juancarlospaco/nim-gatabase.svg) 870 | 871 | 872 | # FAQ 873 | 874 |
875 | 876 | - This is not an ORM ?. 877 | 878 | [Wikipedia defines ORM](https://en.wikipedia.org/wiki/Object-relational_mapping) as: 879 | 880 | > Object-relational mapping in computer science is a programming technique for converting 881 | > data between incompatible type systems using object-oriented programming languages. 882 | 883 | Feel free to contribute to Wikipedia. 884 | 885 | - Supports SQLite ?. 886 | 887 | Yes. 888 | 889 | - Supports MySQL ?. 890 | 891 | No. 892 | 893 | - Will support MySQL someday ?. 894 | 895 | No. 896 | 897 | - Supports Mongo ?. 898 | 899 | No. 900 | 901 | - Will support Mongo someday ?. 902 | 903 | No. 904 | 905 | - How is Parameter substitution done ?. 906 | 907 | It does NOT make Parameter substitution internally, its delegated to standard library. 908 | 909 | - This works with Synchronous code ?. 910 | 911 | Yes. 912 | 913 | - This works with Asynchronous code ?. 914 | 915 | Yes. 916 | 917 | - SQLite mode dont support some stuff ?. 918 | 919 | We try to keep as similar as possible, but SQLite is very limited. 920 | 921 |
922 | 923 | 924 | [ ⬆️ ⬆️ ⬆️ ⬆️ ](#Gatabase "Go to top") 925 | -------------------------------------------------------------------------------- /create_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/3aaba860e3090504c77faa419e14c239dfd46045/create_table.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | gatabase 21 | 22 | 23 | 24 | 25 | 69 | 70 | 71 | 72 |
73 |
74 |

gatabase

75 |
76 |
77 |
78 | 82 |     Dark Mode 83 |
84 | 91 |
92 | Search: 94 |
95 |
96 | Group by: 97 | 101 |
102 | 136 | 137 |
138 |
139 |
140 | 141 |

Gatabase: Connection-Pooling Compile-time lightweight ORM for Postgres or SQLite.

  • SQL DSL mimics SQL syntax!, API mimics stdlib!, Simple just 9 Templates!.
  • 142 |
  • Uses only system.nim, everything is done via template and macro, 0 Dependencies.
  • 143 |
  • Static Connection Pooling Array with 100+ ORM Queries.
  • 144 |
145 | 146 |

More Documentation

149 |

150 |
151 |

Templates

152 |
153 | 154 |
template exec(args: varargs[string, `$`] or seq[string]; inner: untyped)
155 |
156 | 157 | Mimics exec but using Gatabase DSL.
  • args are passed as-is to exec(), if no args use [], example [42, "OwO", true].
  • 158 |
159 |
exec []:
160 |   delete "person"
161 |   where "active = false"
162 | 163 |
164 | 165 |
template tryExec(args: varargs[string, `$`] or seq[string]; inner: untyped): bool
166 |
167 | 168 | Mimics tryExec but using Gatabase DSL.
  • args are passed as-is to tryExec(), if no args use [], example [42, "OwO", true].
  • 169 |
170 |
let killUser: bool = tryExec []:
171 |   delete "person"
172 |   where "id = 42"
let killUser: bool = tryExec []:
173 |   select "name"
174 |   `from` "person"
175 |   wherenot "active = true"
176 | 177 |
178 | 179 |
template getRow(args: varargs[string, `$`] or seq[string]; inner: untyped): auto
180 |
181 | 182 | Mimics getRow but using Gatabase DSL.
  • args are passed as-is to getRow(), if no args use [], example [42, "OwO", true].
  • 183 |
184 |
let topUser: Row = getAllRows []:
185 |   selecttop "username"
186 |   `from` "person"
187 |   limit 1
188 | 189 |
190 | 191 |
template getAllRows(args: varargs[string, `$`] or seq[string]; inner: untyped): auto
192 |
193 | 194 | Mimics getAllRows but using Gatabase DSL.
  • args are passed as-is to getAllRows(), if no args use [], example [42, "OwO", true].
  • 195 |
196 |
let allUsers: seq[Row] = [].getAllRows:
197 |   select '*'
198 |   `from` "person"
var allUsers: seq[Row] = getAllRows []:
199 |   selectdistinct "names"
200 |   `from` "person"
201 | 202 |
203 | 204 |
template getValue(args: varargs[string, `$`] or seq[string]; inner: untyped): string
205 |
206 | 207 | Mimics getValue but using Gatabase DSL.
  • args are passed as-is to getValue(), if no args use [], example [42, "OwO", true].
  • 208 |
209 |
let userName: string = [].getValue:
210 |   select "name"
211 |   `from` "person"
212 |   where  "id = 42"
let age: string = getValue []:
213 |   select "age"
214 |   `from` "person"
215 |   orderby DescNullsLast
216 |   limit 1
217 | 218 |
219 | 220 |
template tryInsertID(args: varargs[string, `$`] or seq[string]; inner: untyped): int64
221 |
222 | 223 | Mimics tryInsertID but using Gatabase DSL.
  • args are passed as-is to tryInsertID(), if no args use [], example [42, "OwO", true].
  • 224 |
225 |
let newUser: int64 = tryInsertID ["Graydon Hoare", "graydon.hoare@nim-lang.org"]:
226 |   insertinto "person"
227 |   values 2
228 | 229 |
230 | 231 |
template insertID(args: varargs[string, `$`] or seq[string]; inner: untyped): int64
232 |
233 | 234 | Mimics insertID but using Gatabase DSL.
  • args are passed as-is to insertID(), if no args use [], example [42, "OwO", true].
  • 235 |
236 |
let newUser: int64 = ["Ryan Dahl", "ryan.dahl@nim-lang.org"].insertID:
237 |   insertinto "person"
238 |   values 2
239 | 240 |
241 | 242 |
template tryInsert(pkName: string; args: varargs[string, `$`] or seq[string];
243 |                   inner: untyped): int64
244 |
245 | 246 | Mimics tryInsert but using Gatabase DSL.
  • args are passed as-is to tryInsert(), if no args use [], example [42, "OwO", true].
  • 247 |
248 | 249 | 250 |
251 | 252 |
template insert(pkName: string; args: varargs[string, `$`] or seq[string];
253 |                inner: untyped): int64
254 |
255 | 256 | Mimics insert but using Gatabase DSL.
  • args are passed as-is to insertID(), if no args use [], example [42, "OwO", true].
  • 257 |
258 | 259 | 260 |
261 | 262 |
template execAffectedRows(args: varargs[string, `$`] or seq[string]; inner: untyped): auto
263 |
264 | 265 | Mimics execAffectedRows but using Gatabase DSL.
  • args are passed as-is to execAffectedRows(), if no args use [], example [42, "OwO", true].
  • 266 |
267 |
let activeUsers: int64 = execAffectedRows []:
268 |   select "status"
269 |   `from` "users"
270 |   `--`  "This is a SQL comment"
271 |   where "status = true"
272 |   isnull false
let distinctNames: int64 = execAffectedRows []:
273 |   selectdistinct "name"
274 |   `from` "users"
275 | 276 |
277 | 278 |
template getValue(args: varargs[string, `$`] or seq[string]; parseProc: proc;
279 |                  inner: untyped): auto
280 |
281 | 282 | Alias for parseProc(getValue(db, sql("..."), args)). Returns actual value instead of string.
  • parseProc is whatever proc parses the value of getValue(), any proc should work.
  • 283 |
  • args are passed as-is to getValue(), if no args use [], example [42, "OwO", true].
  • 284 |
285 |
let age: int = getValue([], parseInt):
286 |   select "age"
287 |   `from` "users"
288 |   limit 1
let ranking: float = getValue([], parseFloat):
289 |   select "ranking"
290 |   `from` "users"
291 |   where "id = 42"
let preferredColor: string = [].getValue(parseHexStr):
292 |   select "color"
293 |   `from` "users"
294 |   limit 1
295 | 296 |
297 | 298 |
template sqls(inner: untyped): auto
299 |
300 | 301 | Build a SqlQuery using Gatabase ORM DSL, returns a vanilla SqlQuery.
const data: SqlQuery = sqls:
302 |   select '*'
303 |   `from` "users"
let data: SqlQuery = sqls:
304 |   select "name"
305 |   `from` "users"
306 |   limit 9
var data: SqlQuery = sqls:
307 |   delete '*'
308 |   `from` "users"
309 | 310 |
311 | 312 |
313 | 314 |
315 |
316 | 317 |
318 | 323 |
324 |
325 |
326 | 327 | 328 | 329 | -------------------------------------------------------------------------------- /docs/nimdoc.out.css: -------------------------------------------------------------------------------- 1 | /* 2 | Stylesheet for use with Docutils/rst2html. 3 | 4 | See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to 5 | customize this style sheet. 6 | 7 | Modified from Chad Skeeters' rst2html-style 8 | https://bitbucket.org/cskeeters/rst2html-style/ 9 | 10 | Modified by Boyd Greenfield and narimiran 11 | */ 12 | 13 | :root { 14 | --primary-background: #fff; 15 | --secondary-background: ghostwhite; 16 | --third-background: #e8e8e8; 17 | --border: #dde; 18 | --text: #222; 19 | --anchor: #07b; 20 | --anchor-focus: #607c9f; 21 | --input-focus: #1fa0eb; 22 | --strong: #3c3c3c; 23 | --hint: #9A9A9A; 24 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAN4AAAA9CAYAAADCt9ebAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjAzOjQ4KzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMzM0ZjAxYS0yMDExLWE1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"); 25 | 26 | --keyword: #5e8f60; 27 | --identifier: #222; 28 | --comment: #484a86; 29 | --operator: #155da4; 30 | --punctuation: black; 31 | --other: black; 32 | --escapeSequence: #c4891b; 33 | --number: #252dbe; 34 | --literal: #a4255b; 35 | --raw-data: #a4255b; 36 | } 37 | 38 | [data-theme="dark"] { 39 | --primary-background: #171921; 40 | --secondary-background: #1e202a; 41 | --third-background: #2b2e3b; 42 | --border: #0e1014; 43 | --text: #fff; 44 | --anchor: #8be9fd; 45 | --anchor-focus: #8be9fd; 46 | --input-focus: #8be9fd; 47 | --strong: #bd93f9; 48 | --hint: #7A7C85; 49 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARMAAABMCAYAAABOBlMuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjE4OjIyKzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDplZGViMzU3MC1iNmZjLWQy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"); 50 | 51 | --keyword: #ff79c6; 52 | --identifier: #f8f8f2; 53 | --comment: #6272a4; 54 | --operator: #ff79c6; 55 | --punctuation: #f8f8f2; 56 | --other: #f8f8f2; 57 | --escapeSequence: #bd93f9; 58 | --number: #bd93f9; 59 | --literal: #f1fa8c; 60 | --raw-data: #8be9fd; 61 | } 62 | 63 | .theme-switch-wrapper { 64 | display: flex; 65 | align-items: center; 66 | 67 | em { 68 | margin-left: 10px; 69 | font-size: 1rem; 70 | } 71 | } 72 | .theme-switch { 73 | display: inline-block; 74 | height: 22px; 75 | position: relative; 76 | width: 50px; 77 | } 78 | 79 | .theme-switch input { 80 | display: none; 81 | } 82 | 83 | .slider { 84 | background-color: #ccc; 85 | bottom: 0; 86 | cursor: pointer; 87 | left: 0; 88 | position: absolute; 89 | right: 0; 90 | top: 0; 91 | transition: .4s; 92 | } 93 | 94 | .slider:before { 95 | background-color: #fff; 96 | bottom: 4px; 97 | content: ""; 98 | height: 13px; 99 | left: 4px; 100 | position: absolute; 101 | transition: .4s; 102 | width: 13px; 103 | } 104 | 105 | input:checked + .slider { 106 | background-color: #66bb6a; 107 | } 108 | 109 | input:checked + .slider:before { 110 | transform: translateX(26px); 111 | } 112 | 113 | .slider.round { 114 | border-radius: 17px; 115 | } 116 | 117 | .slider.round:before { 118 | border-radius: 50%; 119 | } 120 | 121 | html { 122 | font-size: 100%; 123 | -webkit-text-size-adjust: 100%; 124 | -ms-text-size-adjust: 100%; } 125 | 126 | body { 127 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 128 | font-weight: 400; 129 | font-size: 1.125em; 130 | line-height: 1.5; 131 | color: var(--text); 132 | background-color: var(--primary-background); } 133 | 134 | /* Skeleton grid */ 135 | .container { 136 | position: relative; 137 | width: 100%; 138 | max-width: 1050px; 139 | margin: 0 auto; 140 | padding: 0; 141 | box-sizing: border-box; } 142 | 143 | .column, 144 | .columns { 145 | width: 100%; 146 | float: left; 147 | box-sizing: border-box; 148 | margin-left: 1%; 149 | } 150 | 151 | .column:first-child, 152 | .columns:first-child { 153 | margin-left: 0; } 154 | 155 | .three.columns { 156 | width: 19%; } 157 | 158 | .nine.columns { 159 | width: 80.0%; } 160 | 161 | .twelve.columns { 162 | width: 100%; 163 | margin-left: 0; } 164 | 165 | @media screen and (max-width: 860px) { 166 | .three.columns { 167 | display: none; 168 | } 169 | .nine.columns { 170 | width: 98.0%; 171 | } 172 | body { 173 | font-size: 1em; 174 | line-height: 1.35; 175 | } 176 | } 177 | 178 | cite { 179 | font-style: italic !important; } 180 | 181 | 182 | /* Nim search input */ 183 | div#searchInputDiv { 184 | margin-bottom: 1em; 185 | } 186 | input#searchInput { 187 | width: 80%; 188 | } 189 | 190 | /* 191 | * Some custom formatting for input forms. 192 | * This also fixes input form colors on Firefox with a dark system theme on Linux. 193 | */ 194 | input { 195 | -moz-appearance: none; 196 | background-color: var(--secondary-background); 197 | color: var(--text); 198 | border: 1px solid var(--border); 199 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 200 | font-size: 0.9em; 201 | padding: 6px; 202 | } 203 | 204 | input:focus { 205 | border: 1px solid var(--input-focus); 206 | box-shadow: 0 0 3px var(--input-focus); 207 | } 208 | 209 | select { 210 | -moz-appearance: none; 211 | background-color: var(--secondary-background); 212 | color: var(--text); 213 | border: 1px solid var(--border); 214 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 215 | font-size: 0.9em; 216 | padding: 6px; 217 | } 218 | 219 | select:focus { 220 | border: 1px solid var(--input-focus); 221 | box-shadow: 0 0 3px var(--input-focus); 222 | } 223 | 224 | /* Docgen styles */ 225 | /* Links */ 226 | a { 227 | color: var(--anchor); 228 | text-decoration: none; 229 | } 230 | 231 | a span.Identifier { 232 | text-decoration: underline; 233 | text-decoration-color: #aab; 234 | } 235 | 236 | a.reference-toplevel { 237 | font-weight: bold; 238 | } 239 | 240 | a.toc-backref { 241 | text-decoration: none; 242 | color: var(--text); } 243 | 244 | a.link-seesrc { 245 | color: #607c9f; 246 | font-size: 0.9em; 247 | font-style: italic; } 248 | 249 | a:hover, 250 | a:focus { 251 | color: var(--anchor-focus); 252 | text-decoration: underline; } 253 | 254 | a:hover span.Identifier { 255 | color: var(--anchor); 256 | } 257 | 258 | 259 | sub, 260 | sup { 261 | position: relative; 262 | font-size: 75%; 263 | line-height: 0; 264 | vertical-align: baseline; } 265 | 266 | sup { 267 | top: -0.5em; } 268 | 269 | sub { 270 | bottom: -0.25em; } 271 | 272 | img { 273 | width: auto; 274 | height: auto; 275 | max-width: 100%; 276 | vertical-align: middle; 277 | border: 0; 278 | -ms-interpolation-mode: bicubic; } 279 | 280 | @media print { 281 | * { 282 | color: black !important; 283 | text-shadow: none !important; 284 | background: transparent !important; 285 | box-shadow: none !important; } 286 | 287 | a, 288 | a:visited { 289 | text-decoration: underline; } 290 | 291 | a[href]:after { 292 | content: " (" attr(href) ")"; } 293 | 294 | abbr[title]:after { 295 | content: " (" attr(title) ")"; } 296 | 297 | .ir a:after, 298 | a[href^="javascript:"]:after, 299 | a[href^="#"]:after { 300 | content: ""; } 301 | 302 | pre, 303 | blockquote { 304 | border: 1px solid #999; 305 | page-break-inside: avoid; } 306 | 307 | thead { 308 | display: table-header-group; } 309 | 310 | tr, 311 | img { 312 | page-break-inside: avoid; } 313 | 314 | img { 315 | max-width: 100% !important; } 316 | 317 | @page { 318 | margin: 0.5cm; } 319 | 320 | h1 { 321 | page-break-before: always; } 322 | 323 | h1.title { 324 | page-break-before: avoid; } 325 | 326 | p, 327 | h2, 328 | h3 { 329 | orphans: 3; 330 | widows: 3; } 331 | 332 | h2, 333 | h3 { 334 | page-break-after: avoid; } 335 | } 336 | 337 | 338 | p { 339 | margin-top: 0.5em; 340 | margin-bottom: 0.5em; 341 | } 342 | 343 | small { 344 | font-size: 85%; } 345 | 346 | strong { 347 | font-weight: 600; 348 | font-size: 0.95em; 349 | color: var(--strong); 350 | } 351 | 352 | em { 353 | font-style: italic; } 354 | 355 | h1 { 356 | font-size: 1.8em; 357 | font-weight: 400; 358 | padding-bottom: .25em; 359 | border-bottom: 6px solid var(--third-background); 360 | margin-top: 2.5em; 361 | margin-bottom: 1em; 362 | line-height: 1.2em; } 363 | 364 | h1.title { 365 | padding-bottom: 1em; 366 | border-bottom: 0px; 367 | font-size: 2.5em; 368 | text-align: center; 369 | font-weight: 900; 370 | margin-top: 0.75em; 371 | margin-bottom: 0em; 372 | } 373 | 374 | h2 { 375 | font-size: 1.3em; 376 | margin-top: 2em; } 377 | 378 | h2.subtitle { 379 | text-align: center; } 380 | 381 | h3 { 382 | font-size: 1.125em; 383 | font-style: italic; 384 | margin-top: 1.5em; } 385 | 386 | h4 { 387 | font-size: 1.125em; 388 | margin-top: 1em; } 389 | 390 | h5 { 391 | font-size: 1.125em; 392 | margin-top: 0.75em; } 393 | 394 | h6 { 395 | font-size: 1.1em; } 396 | 397 | 398 | ul, 399 | ol { 400 | padding: 0; 401 | margin-top: 0.5em; 402 | margin-left: 0.75em; } 403 | 404 | ul ul, 405 | ul ol, 406 | ol ol, 407 | ol ul { 408 | margin-bottom: 0; 409 | margin-left: 1.25em; } 410 | 411 | li { 412 | list-style-type: circle; 413 | } 414 | 415 | ul.simple-boot li { 416 | list-style-type: none; 417 | margin-left: 0em; 418 | margin-bottom: 0.5em; 419 | } 420 | 421 | ol.simple > li, ul.simple > li { 422 | margin-bottom: 0.25em; 423 | margin-left: 0.4em } 424 | 425 | ul.simple.simple-toc > li { 426 | margin-top: 1em; 427 | } 428 | 429 | ul.simple-toc { 430 | list-style: none; 431 | font-size: 0.9em; 432 | margin-left: -0.3em; 433 | margin-top: 1em; } 434 | 435 | ul.simple-toc > li { 436 | list-style-type: none; 437 | } 438 | 439 | ul.simple-toc-section { 440 | list-style-type: circle; 441 | margin-left: 1em; 442 | color: #6c9aae; } 443 | 444 | 445 | ol.arabic { 446 | list-style: decimal; } 447 | 448 | ol.loweralpha { 449 | list-style: lower-alpha; } 450 | 451 | ol.upperalpha { 452 | list-style: upper-alpha; } 453 | 454 | ol.lowerroman { 455 | list-style: lower-roman; } 456 | 457 | ol.upperroman { 458 | list-style: upper-roman; } 459 | 460 | ul.auto-toc { 461 | list-style-type: none; } 462 | 463 | 464 | dl { 465 | margin-bottom: 1.5em; } 466 | 467 | dt { 468 | margin-bottom: -0.5em; 469 | margin-left: 0.0em; } 470 | 471 | dd { 472 | margin-left: 2.0em; 473 | margin-bottom: 3.0em; 474 | margin-top: 0.5em; } 475 | 476 | 477 | hr { 478 | margin: 2em 0; 479 | border: 0; 480 | border-top: 1px solid #aaa; } 481 | 482 | blockquote { 483 | font-size: 0.9em; 484 | font-style: italic; 485 | padding-left: 0.5em; 486 | margin-left: 0; 487 | border-left: 5px solid #bbc; 488 | } 489 | 490 | .pre { 491 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 492 | font-weight: 500; 493 | font-size: 0.85em; 494 | color: var(--text); 495 | background-color: var(--third-background); 496 | padding-left: 3px; 497 | padding-right: 3px; 498 | border-radius: 4px; 499 | } 500 | 501 | pre { 502 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 503 | color: var(--text); 504 | font-weight: 500; 505 | display: inline-block; 506 | box-sizing: border-box; 507 | min-width: 100%; 508 | padding: 0.5em; 509 | margin-top: 0.5em; 510 | margin-bottom: 0.5em; 511 | font-size: 0.85em; 512 | white-space: pre !important; 513 | overflow-y: hidden; 514 | overflow-x: visible; 515 | background-color: var(--secondary-background); 516 | border: 1px solid var(--border); 517 | -webkit-border-radius: 6px; 518 | -moz-border-radius: 6px; 519 | border-radius: 6px; } 520 | 521 | .pre-scrollable { 522 | max-height: 340px; 523 | overflow-y: scroll; } 524 | 525 | 526 | /* Nim line-numbered tables */ 527 | .line-nums-table { 528 | width: 100%; 529 | table-layout: fixed; } 530 | 531 | table.line-nums-table { 532 | border-radius: 4px; 533 | border: 1px solid #cccccc; 534 | background-color: ghostwhite; 535 | border-collapse: separate; 536 | margin-top: 15px; 537 | margin-bottom: 25px; } 538 | 539 | .line-nums-table tbody { 540 | border: none; } 541 | 542 | .line-nums-table td pre { 543 | border: none; 544 | background-color: transparent; } 545 | 546 | .line-nums-table td.blob-line-nums { 547 | width: 28px; } 548 | 549 | .line-nums-table td.blob-line-nums pre { 550 | color: #b0b0b0; 551 | -webkit-filter: opacity(75%); 552 | text-align: right; 553 | border-color: transparent; 554 | background-color: transparent; 555 | padding-left: 0px; 556 | margin-left: 0px; 557 | padding-right: 0px; 558 | margin-right: 0px; } 559 | 560 | 561 | table { 562 | max-width: 100%; 563 | background-color: transparent; 564 | margin-top: 0.5em; 565 | margin-bottom: 1.5em; 566 | border-collapse: collapse; 567 | border-color: var(--third-background); 568 | border-spacing: 0; 569 | font-size: 0.9em; 570 | } 571 | 572 | table th, table td { 573 | padding: 0px 0.5em 0px; 574 | border-color: var(--third-background); 575 | } 576 | 577 | table th { 578 | background-color: var(--third-background); 579 | border-color: var(--third-background); 580 | font-weight: bold; } 581 | 582 | table th.docinfo-name { 583 | background-color: transparent; 584 | } 585 | 586 | table tr:hover { 587 | background-color: var(--third-background); } 588 | 589 | 590 | /* rst2html default used to remove borders from tables and images */ 591 | .borderless, table.borderless td, table.borderless th { 592 | border: 0; } 593 | 594 | table.borderless td, table.borderless th { 595 | /* Override padding for "table.docutils td" with "! important". 596 | The right padding separates the table cells. */ 597 | padding: 0 0.5em 0 0 !important; } 598 | 599 | .first { 600 | /* Override more specific margin styles with "! important". */ 601 | margin-top: 0 !important; } 602 | 603 | .last, .with-subtitle { 604 | margin-bottom: 0 !important; } 605 | 606 | .hidden { 607 | display: none; } 608 | 609 | blockquote.epigraph { 610 | margin: 2em 5em; } 611 | 612 | dl.docutils dd { 613 | margin-bottom: 0.5em; } 614 | 615 | object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { 616 | overflow: hidden; } 617 | 618 | 619 | div.figure { 620 | margin-left: 2em; 621 | margin-right: 2em; } 622 | 623 | div.footer, div.header { 624 | clear: both; 625 | text-align: center; 626 | color: #666; 627 | font-size: smaller; } 628 | 629 | div.footer { 630 | padding-top: 5em; 631 | } 632 | 633 | div.line-block { 634 | display: block; 635 | margin-top: 1em; 636 | margin-bottom: 1em; } 637 | 638 | div.line-block div.line-block { 639 | margin-top: 0; 640 | margin-bottom: 0; 641 | margin-left: 1.5em; } 642 | 643 | div.topic { 644 | margin: 2em; } 645 | 646 | div.search_results { 647 | background-color: var(--third-background); 648 | margin: 3em; 649 | padding: 1em; 650 | border: 1px solid #4d4d4d; 651 | } 652 | 653 | div#global-links ul { 654 | margin-left: 0; 655 | list-style-type: none; 656 | } 657 | 658 | div#global-links > simple-boot { 659 | margin-left: 3em; 660 | } 661 | 662 | hr.docutils { 663 | width: 75%; } 664 | 665 | img.align-left, .figure.align-left, object.align-left { 666 | clear: left; 667 | float: left; 668 | margin-right: 1em; } 669 | 670 | img.align-right, .figure.align-right, object.align-right { 671 | clear: right; 672 | float: right; 673 | margin-left: 1em; } 674 | 675 | img.align-center, .figure.align-center, object.align-center { 676 | display: block; 677 | margin-left: auto; 678 | margin-right: auto; } 679 | 680 | .align-left { 681 | text-align: left; } 682 | 683 | .align-center { 684 | clear: both; 685 | text-align: center; } 686 | 687 | .align-right { 688 | text-align: right; } 689 | 690 | /* reset inner alignment in figures */ 691 | div.align-right { 692 | text-align: inherit; } 693 | 694 | p.attribution { 695 | text-align: right; 696 | margin-left: 50%; } 697 | 698 | p.caption { 699 | font-style: italic; } 700 | 701 | p.credits { 702 | font-style: italic; 703 | font-size: smaller; } 704 | 705 | p.label { 706 | white-space: nowrap; } 707 | 708 | p.rubric { 709 | font-weight: bold; 710 | font-size: larger; 711 | color: maroon; 712 | text-align: center; } 713 | 714 | p.topic-title { 715 | font-weight: bold; } 716 | 717 | pre.address { 718 | margin-bottom: 0; 719 | margin-top: 0; 720 | font: inherit; } 721 | 722 | pre.literal-block, pre.doctest-block, pre.math, pre.code { 723 | margin-left: 2em; 724 | margin-right: 2em; } 725 | 726 | pre.code .ln { 727 | color: grey; } 728 | 729 | /* line numbers */ 730 | pre.code, code { 731 | background-color: #eeeeee; } 732 | 733 | pre.code .comment, code .comment { 734 | color: #5c6576; } 735 | 736 | pre.code .keyword, code .keyword { 737 | color: #3B0D06; 738 | font-weight: bold; } 739 | 740 | pre.code .literal.string, code .literal.string { 741 | color: #0c5404; } 742 | 743 | pre.code .name.builtin, code .name.builtin { 744 | color: #352b84; } 745 | 746 | pre.code .deleted, code .deleted { 747 | background-color: #DEB0A1; } 748 | 749 | pre.code .inserted, code .inserted { 750 | background-color: #A3D289; } 751 | 752 | span.classifier { 753 | font-style: oblique; } 754 | 755 | span.classifier-delimiter { 756 | font-weight: bold; } 757 | 758 | span.option { 759 | white-space: nowrap; } 760 | 761 | span.problematic { 762 | color: #b30000; } 763 | 764 | span.section-subtitle { 765 | /* font-size relative to parent (h1..h6 element) */ 766 | font-size: 80%; } 767 | 768 | span.DecNumber { 769 | color: var(--number); } 770 | 771 | span.BinNumber { 772 | color: var(--number); } 773 | 774 | span.HexNumber { 775 | color: var(--number); } 776 | 777 | span.OctNumber { 778 | color: var(--number); } 779 | 780 | span.FloatNumber { 781 | color: var(--number); } 782 | 783 | span.Identifier { 784 | color: var(--identifier); } 785 | 786 | span.Keyword { 787 | font-weight: 600; 788 | color: var(--keyword); } 789 | 790 | span.StringLit { 791 | color: var(--literal); } 792 | 793 | span.LongStringLit { 794 | color: var(--literal); } 795 | 796 | span.CharLit { 797 | color: var(--literal); } 798 | 799 | span.EscapeSequence { 800 | color: var(--escapeSequence); } 801 | 802 | span.Operator { 803 | color: var(--operator); } 804 | 805 | span.Punctuation { 806 | color: var(--punctuation); } 807 | 808 | span.Comment, span.LongComment { 809 | font-style: italic; 810 | font-weight: 400; 811 | color: var(--comment); } 812 | 813 | span.RegularExpression { 814 | color: darkviolet; } 815 | 816 | span.TagStart { 817 | color: darkviolet; } 818 | 819 | span.TagEnd { 820 | color: darkviolet; } 821 | 822 | span.Key { 823 | color: #252dbe; } 824 | 825 | span.Value { 826 | color: #252dbe; } 827 | 828 | span.RawData { 829 | color: var(--raw-data); } 830 | 831 | span.Assembler { 832 | color: #252dbe; } 833 | 834 | span.Preprocessor { 835 | color: #252dbe; } 836 | 837 | span.Directive { 838 | color: #252dbe; } 839 | 840 | span.Command, span.Rule, span.Hyperlink, span.Label, span.Reference, 841 | span.Other { 842 | color: var(--other); } 843 | 844 | /* Pop type, const, proc, and iterator defs in nim def blocks */ 845 | dt pre > span.Identifier, dt pre > span.Operator { 846 | color: var(--identifier); 847 | font-weight: 700; } 848 | 849 | dt pre > span.Keyword ~ span.Identifier, dt pre > span.Identifier ~ span.Identifier, 850 | dt pre > span.Operator ~ span.Identifier, dt pre > span.Other ~ span.Identifier { 851 | color: var(--identifier); 852 | font-weight: inherit; } 853 | 854 | /* Nim sprite for the footer (taken from main page favicon) */ 855 | .nim-sprite { 856 | display: inline-block; 857 | width: 51px; 858 | height: 14px; 859 | background-position: 0 0; 860 | background-size: 51px 14px; 861 | -webkit-filter: opacity(50%); 862 | background-repeat: no-repeat; 863 | background-image: var(--nim-sprite-base64); 864 | margin-bottom: 5px; } 865 | 866 | span.pragmadots { 867 | /* Position: relative frees us up to make the dots 868 | look really nice without fucking up the layout and 869 | causing bulging in the parent container */ 870 | position: relative; 871 | /* 1px down looks slightly nicer */ 872 | top: 1px; 873 | padding: 2px; 874 | background-color: var(--third-background); 875 | border-radius: 4px; 876 | margin: 0 2px; 877 | cursor: pointer; 878 | font-size: 0.8em; 879 | } 880 | 881 | span.pragmadots:hover { 882 | background-color: var(--hint); 883 | } 884 | span.pragmawrap { 885 | display: none; 886 | } 887 | 888 | span.attachedType { 889 | display: none; 890 | visibility: hidden; 891 | } 892 | -------------------------------------------------------------------------------- /docs/sugar.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | sugar 21 | 22 | 23 | 24 | 25 | 60 | 61 | 62 | 63 |
64 |
65 |

sugar

66 |
67 |
68 |
69 | 73 |     Dark Mode 74 |
75 | 82 |
83 | Search: 85 |
86 |
87 | Group by: 88 | 92 |
93 | 154 | 155 |
156 | 157 |
158 |
159 | 160 |

161 |

Gatabase Sugar

Syntax Sugar for Gatabase using template.

162 |

include or import after importing db_sqlite or db_postgres to use it on your code.

163 |
import db_sqlite
164 | include gatabase/sugar
import db_postgres
165 | include gatabase/sugar

All Gatabase sugar is always optional. The templates are very efficient, no stdlib imports, no object heap alloc, no string formatting, just primitives, no more than 1 variable used.

166 |

167 |
168 |

Templates

169 |
170 | 171 |
template createTable(name: static string; code: untyped): SqlQuery
172 |
173 | 174 | Create a new database table name with fields from code, returns 1 SqlQuery. Works with Postgres and Sqlite. SqlQuery is pretty-printed when not built for release.
import db_sqlite
175 | include gatabase/sugar
176 | let myTable = createTable "kitten": [
177 |   "age"    := 1,
178 |   "sex"    := 'f',
179 |   "name"   := "unnamed",
180 |   "rank"   := 3.14,
181 |   "weight" := int,
182 |   "color"  := char,
183 |   "owner"  := string,
184 |   "food"   := float,
185 | ]

Generates the SQL Query:

186 |
CREATE TABLE IF NOT EXISTS kitten(
187 |   id      INTEGER      PRIMARY KEY,
188 |   age     INTEGER      NOT NULL      DEFAULT 1,
189 |   sex     VARCHAR(1)   NOT NULL      DEFAULT 'f',
190 |   name    TEXT         NOT NULL      DEFAULT 'unnamed',
191 |   rank    REAL         NOT NULL      DEFAULT 3.14,
192 |   weight  INTEGER,
193 |   color   VARCHAR(1),
194 |   owner   TEXT,
195 |   food    REAL,
196 | );

More examples:

197 | 199 | 200 | 201 |
202 | 203 |
template dropTable(db; name: string): bool
204 |
205 | 206 | Alias for tryExec(db, sql("DROP TABLE IF EXISTS ?"), name). Requires a db of DbConn type. Works with Postgres and Sqlite. Deleted tables can not be restored, be careful. 207 | 208 |
209 | 210 |
template withSqlite(path: static[string]; initTableSql: static[string];
211 |                     closeOnQuit: static[bool]; closeOnCtrlC: static[bool];
212 |                     code: untyped): untyped
213 |
214 | 215 | Open, run initTableSql and Auto-Close a SQLite database.
  • path path to SQLite database file.
  • 216 |
  • initTableSql SQL query string to initialize the database, create table if not exists alike.
  • 217 |
  • closeOnQuit if true then addQuitProc(db.close()) is set.
  • 218 |
  • closeOnCtrlC if true then setControlCHook(db.close()) is set.
  • 219 |
220 |
import db_sqlite
221 | include gatabase/sugar
222 | const exampleTable = """
223 |   create table if not exists person(
224 |     id      integer primary key,
225 |     name    text,
226 |     active  bool,
227 |     rank    float
228 | ); """
229 | 
230 | withSqlite(":memory:", exampleTable, false):  ## This is just an example.
231 |   db.exec(sql"insert into person(name, active, rank) values('pepe', true, 42.0)")
232 | 233 |
234 | 235 |
template withPostgres(host, user, password, dbname: string;
236 |                       initTableSql: static[string]; closeOnQuit: static[bool];
237 |                       closeOnCtrlC: static[bool]; code: untyped): untyped
238 |
239 | 240 | Open, run initTableSql and Auto-Close a Postgres database. See withSqlite for an example.
  • host host of Postgres Server, string type, must not be empty string.
  • 241 |
  • user user of Postgres Server, string type, must not be empty string.
  • 242 |
  • password password of Postgres Server, string type, must not be empty string.
  • 243 |
  • dbname database name of Postgres Server, string type, must not be empty string.
  • 244 |
  • initTableSql SQL query string to initialize the database, create table if not exists alike.
  • 245 |
  • closeOnQuit if true then addQuitProc(db.close()) is set.
  • 246 |
  • closeOnCtrlC if true then setControlCHook(db.close()) is set.
  • 247 |
248 | 249 | 250 |
251 | 252 |
template `.`(indx: int; data: Row): int
253 |
254 | 255 | 9.row convenience alias for strutils.parseInt(row[9]) (row is Row type). 256 | 257 |
258 | 259 |
template `.`(indx: char; data: Row): char
260 |
261 | 262 | '9'.row convenience alias for char(row[strutils.parseInt($indx)][0]) (row is Row type). 263 | 264 |
265 | 266 |
template `.`(indx: uint; data: Row): uint
267 |
268 | 269 | 9'u.row convenience alias for uint(strutils.parseInt(row[9])) (row is Row type). 270 | 271 |
272 | 273 |
template `.`(indx: cint; data: Row): cint
274 |
275 | 276 | cint(9).row convenience alias for cint(strutils.parseInt(row[9])) (row is Row type). 277 | 278 |
279 | 280 |
template `.`(indx: int8; data: Row): int8
281 |
282 | 283 | 9'i8.row convenience alias for int8(strutils.parseInt(row[9])) (row is Row type). 284 | 285 |
286 | 287 |
template `.`(indx: byte; data: Row): byte
288 |
289 | 290 | byte(9).row convenience alias for byte(strutils.parseInt(row[9])) (row is Row type). 291 | 292 |
293 | 294 |
template `.`(indx: int16; data: Row): int16
295 |
296 | 297 | 9'i16.row convenience alias for int16(strutils.parseInt(row[9])) (row is Row type). 298 | 299 |
300 | 301 |
template `.`(indx: int32; data: Row): int32
302 |
303 | 304 | 9'i32.row convenience alias for int32(strutils.parseInt(row[9])) (row is Row type). 305 | 306 |
307 | 308 |
template `.`(indx: int64; data: Row): int64
309 |
310 | 311 | 9'i64.row convenience alias for int64(strutils.parseInt(row[9])) (row is Row type). 312 | 313 |
314 | 315 |
template `.`(indx: uint8; data: Row): uint8
316 |
317 | 318 | 9'u8.row convenience alias for uint8(strutils.parseInt(row[9])) (row is Row type). 319 | 320 |
321 | 322 |
template `.`(indx: uint16; data: Row): uint16
323 |
324 | 325 | 9'u16.row convenience alias for uint16(strutils.parseInt(row[9])) (row is Row type). 326 | 327 |
328 | 329 |
template `.`(indx: uint32; data: Row): uint32
330 |
331 | 332 | 9'u32.row convenience alias for uint32(strutils.parseInt(row[9])) (row is Row type). 333 | 334 |
335 | 336 |
template `.`(indx: uint64; data: Row): uint64
337 |
338 | 339 | 9'u64.row convenience alias for uint64(strutils.parseInt(row[9])) (row is Row type). 340 | 341 |
342 | 343 |
template `.`(indx: float; data: Row): float
344 |
345 | 346 | 9.0.row convenience alias for strutils.parseFloat(row[int(9)]) (row is Row type). 347 | 348 |
349 | 350 |
template `.`(indx: Natural; data: Row): Natural
351 |
352 | 353 | Natural(9).row convenience alias for Natural(strutils.parseInt(row[9])) (row is Row type). 354 | 355 |
356 | 357 |
template `.`(indx: cstring; data: Row): cstring
358 |
359 | 360 | cstring("9").row convenience alias for cstring(row[9]) (row is Row type). 361 | 362 |
363 | 364 |
template `.`(indx: Positive; data: Row): Positive
365 |
366 | 367 | Positive(9).row convenience alias for Positive(strutils.parseInt(row[9])) (row is Row type). 368 | 369 |
370 | 371 |
template `.`(indx: BiggestInt; data: Row): BiggestInt
372 |
373 | 374 | BiggestInt(9).row convenience alias for BiggestInt(strutils.parseInt(row[9])) (row is Row type). 375 | 376 |
377 | 378 |
template `.`(indx: BiggestUInt; data: Row): BiggestUInt
379 |
380 | 381 | BiggestUInt(9).row convenience alias for BiggestUInt(strutils.parseInt(row[9])) (row is Row type). 382 | 383 |
384 | 385 |
template `.`(indx: float32; data: Row): float32
386 |
387 | 388 | 9.0'f32.row convenience alias for float32(strutils.parseFloat(row[int(9)])) (row is Row type). 389 | 390 |
391 | 392 |
393 | 394 |
395 |
396 | 397 |
398 | 403 |
404 |
405 |
406 | 407 | 408 | 409 | -------------------------------------------------------------------------------- /docs/sugar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/3aaba860e3090504c77faa419e14c239dfd46045/docs/sugar.jpg -------------------------------------------------------------------------------- /examples/create_table_example.nim: -------------------------------------------------------------------------------- 1 | import db_sqlite 2 | import gatabase 3 | include gatabase/sugar 4 | 5 | let myTable = createTable "kitten": [ 6 | "age" := 1, 7 | "sex" := 'f', 8 | "name" := "fluffy", 9 | "rank" := 3.14, 10 | ] 11 | 12 | echo myTable.string 13 | -------------------------------------------------------------------------------- /examples/database_fields_example.nim: -------------------------------------------------------------------------------- 1 | # Gatabase: individual database fields creator is a Walrus operator. KISS. 2 | const nl = when defined(release): " " else: "\n" 3 | 4 | template `:=`(dbfield: static string; value: static char): string = 5 | assert dbfield.len > 0, "Table field name must not be empty string" 6 | "\t" & dbfield & "\t" & "VARCHAR(1)\tNOT NULL\tDEFAULT '" & $value & "'," & nl 7 | 8 | template `:=`(dbfield: static string; value: static SomeFloat): string = 9 | assert dbfield.len > 0, "Table field name must not be empty string" 10 | "\t" & dbfield & "\t" & "REAL\tNOT NULL\tDEFAULT " & $value & "," & nl 11 | 12 | template `:=`(dbfield: static string; value: static SomeInteger): string = 13 | assert dbfield.len > 0, "Table field name must not be empty string" 14 | "\t" & dbfield & "\t" & "INTEGER\tNOT NULL\tDEFAULT " & $value & "," & nl 15 | 16 | template `:=`(dbfield: static string; value: static bool): string = 17 | assert dbfield.len > 0, "Table field name must not be empty string" 18 | "\t" & dbfield & "\t" & "BOOLEAN\tNOT NULL\tDEFAULT " & (if $value == "true": "1" else: "0") & "," & nl 19 | 20 | template `:=`(dbfield: static string; value: static string): string = 21 | assert dbfield.len > 0, "Table field name must not be empty string" 22 | "\t" & dbfield & "\t" & "TEXT\tNOT NULL\tDEFAULT '" & $value & "'," & nl 23 | 24 | template `:=`(dbfield: static string; value: typedesc[char]): string = 25 | assert dbfield.len > 0, "Table field name must not be empty string" 26 | "\t" & $dbfield & "\t" & "VARCHAR(1)," & nl 27 | 28 | template `:=`(dbfield: static string; value: typedesc[SomeFloat]): string = 29 | assert dbfield.len > 0, "Table field name must not be empty string" 30 | "\t" & $dbfield & "\t" & "REAL," & nl 31 | 32 | template `:=`(dbfield: static string; value: typedesc[SomeInteger]): string = 33 | assert dbfield.len > 0, "Table field name must not be empty string" 34 | "\t" & $dbfield & "\t" & "INTEGER," & nl 35 | 36 | template `:=`(dbfield: static string; value: typedesc[bool]): string = 37 | assert dbfield.len > 0, "Table field name must not be empty string" 38 | "\t" & $dbfield & "\t" & "BOOLEAN," & nl 39 | 40 | template `:=`(dbfield: static string; value: typedesc[string]): string = 41 | assert dbfield.len > 0, "Table field name must not be empty string" 42 | "\t" & $dbfield & "\t" & "TEXT," & nl 43 | 44 | template `:=`(dbfield: static cstring; value: typedesc[char]): string = 45 | assert dbfield.len > 0, "Table field name must not be empty string" 46 | "\t" & $dbfield & "\t" & "VARCHAR(1)\tUNIQUE," & nl 47 | 48 | template `:=`(dbfield: static cstring; value: typedesc[SomeFloat]): string = 49 | assert dbfield.len > 0, "Table field name must not be empty string" 50 | "\t" & $dbfield & "\t" & "REAL\tUNIQUE," & nl 51 | 52 | template `:=`(dbfield: static cstring; value: typedesc[SomeInteger]): string = 53 | assert dbfield.len > 0, "Table field name must not be empty string" 54 | "\t" & $dbfield & "\t" & "INTEGER\tUNIQUE," & nl 55 | 56 | template `:=`(dbfield: static cstring; value: typedesc[bool]): string = 57 | assert dbfield.len > 0, "Table field name must not be empty string" 58 | "\t" & $dbfield & "\t" & "BOOLEAN\tUNIQUE," & nl 59 | 60 | template `:=`(dbfield: static cstring; value: typedesc[string]): string = 61 | assert dbfield.len > 0, "Table field name must not be empty string" 62 | "\t" & $dbfield & "\t" & "TEXT\tUNIQUE," & nl 63 | 64 | 65 | echo "Gatabase fields with default values" 66 | echo "field0" := 'z' 67 | echo "field1" := 2.0 68 | echo "field2" := 42 69 | echo "field3" := false 70 | echo "field4" := "hello" 71 | 72 | echo "Gatabase fields without default values" 73 | echo "field5" := char 74 | echo "field6" := float 75 | echo "field7" := int 76 | echo "field8" := bool 77 | echo "field9" := string 78 | 79 | echo "Gatabase fields without default values and UNIQUE restriction" 80 | echo cstring"fielda" := char 81 | echo cstring"fieldb" := float 82 | echo cstring"fieldc" := int 83 | echo cstring"fieldd" := bool 84 | echo cstring"fielde" := string 85 | -------------------------------------------------------------------------------- /examples/drop_table_example.nim: -------------------------------------------------------------------------------- 1 | import db_sqlite 2 | import gatabase 3 | include gatabase/sugar 4 | 5 | 6 | let db = open(":memory:", "", "", "") 7 | db.exec(sql""" 8 | create table if not exists person( 9 | id integer primary key, 10 | name varchar(9) not null unique, 11 | ); """) 12 | 13 | assert db.dropTable "person" 14 | -------------------------------------------------------------------------------- /examples/expect_fail.nim: -------------------------------------------------------------------------------- 1 | ## Examples here Fail intentionally to desmostrate "Smart" SQL Syntax Checkings. 2 | ## You can try uncommenting some of them and running it, they will fail. 3 | import ../src/gatabase 4 | 5 | 6 | # discard sqls: 7 | # `from` "failure" 8 | 9 | 10 | # discard sqls: 11 | # where "failure" 12 | 13 | 14 | # discard sqls: 15 | # wherenot "failure" 16 | 17 | 18 | # discard sqls: 19 | # whereexists "failure" 20 | 21 | 22 | # discard sqls: 23 | # orderby "failure" 24 | 25 | 26 | # discard sqls: 27 | # like "failure" 28 | 29 | 30 | # discard sqls: 31 | # between "failure" 32 | -------------------------------------------------------------------------------- /examples/gatabase_example.nim: -------------------------------------------------------------------------------- 1 | import db_sqlite, ../src/gatabase 2 | 3 | let db = open(":memory:", "", "", "") 4 | db.exec(sql""" 5 | create table if not exists person( 6 | id integer primary key, 7 | name varchar(9) not null unique, 8 | active bool not null default true, 9 | rank float not null default 0.0 10 | ); """) 11 | 12 | 13 | exec ["42", "Pepe", "true", "9.6"]: 14 | insertinto "person" 15 | values 4 16 | 17 | 18 | let row = [].getRow: 19 | select '*' 20 | `from` "person" 21 | 22 | doAssert row == @["42", "Pepe", "true", "9.6"] 23 | -------------------------------------------------------------------------------- /examples/get_concrete_types_value_example.nim: -------------------------------------------------------------------------------- 1 | import db_sqlite 2 | import ../src/gatabase 3 | include prelude, ../src/gatabase/sugar 4 | let db = db_sqlite.open(":memory:", "", "", "") 5 | db.exec(sql""" 6 | create table if not exists person( 7 | id integer primary key, 8 | name varchar(9) not null unique, 9 | active bool not null default true, 10 | rank float not null default 0.0, 11 | sex varchar(1) not null default 'f', 12 | age integer not null default 18 13 | ); """) 14 | db.exec(sql"insert into person values (42, 'pepe', true, 9.6, 'm', 25);") 15 | let myRow: Row = [].getRow: 16 | select '*' 17 | `from` "person" 18 | doAssert myRow == @["42", "pepe", "1", "9.6", "m", "25"] 19 | 20 | 21 | # ^ Boilerplate for the example, ignore it ### Get concrete types: 22 | 23 | 24 | # Get a byte 25 | doAssert byte(0).myRow is byte # byte(42) 26 | # Get a char 27 | doAssert '4'.myRow is char # char('m') 28 | # Get a byte 29 | doAssert cstring"1".myRow is cstring # cstring("pepe") 30 | # Get a float 31 | doAssert 3.0.myRow is float # float(9.6) 32 | # Get a float32 33 | doAssert 3.0'f32.myRow is float32 # float32(9.6) 34 | # Get an int 35 | doAssert 0.myRow is int # 42 36 | # Get a Natural 37 | doAssert Natural(0).myRow is Natural # Natural(42) 38 | # Get a Positive 39 | doAssert Positive(2).myRow is Positive # Positive(1) 40 | # Get a cint 41 | doAssert cint(0).myRow is cint # cint(42) 42 | # Get a int8 43 | doAssert 0'i8.myRow is int8 # int8(42) 44 | # Get a int16 45 | doAssert 0'i16.myRow is int16 # int16(42) 46 | # Get a int32 47 | doAssert 0'i32.myRow is int32 # int32(42) 48 | # Get a int64 49 | doAssert 0'i64.myRow is int64 # int64(42) 50 | # Get a uint8 51 | doAssert 0'u8.myRow is uint8 # uint8(42) 52 | # Get a uint16 53 | doAssert 0'u16.myRow is uint16 # uint16(42) 54 | # Get a uint32 55 | doAssert 0'u32.myRow is uint32 # uint32(42) 56 | # Get a uint64 57 | doAssert 0'u64.myRow is uint64 # uint64(42) 58 | # Get a BiggestInt 59 | doAssert BiggestInt(0).myRow is BiggestInt # BiggestInt(42) 60 | # Get a BiggestUInt 61 | doAssert BiggestUInt(0).myRow is BiggestUInt # BiggestUInt(42) 62 | -------------------------------------------------------------------------------- /examples/minimal.nim: -------------------------------------------------------------------------------- 1 | import db_common, ../src/gatabase 2 | 3 | let variable = sqls: 4 | delete "debts" 5 | 6 | echo variable.string 7 | -------------------------------------------------------------------------------- /examples/sqlalchemy_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/3aaba860e3090504c77faa419e14c239dfd46045/examples/sqlalchemy_example.png -------------------------------------------------------------------------------- /examples/sqlalchemy_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | from sqlalchemy import create_engine, MetaData, Table 4 | from sqlalchemy import Column, Integer, String, Boolean, Float 5 | 6 | engine = create_engine("sqlite:///:memory:", echo=False) 7 | engine.execute(""" 8 | create table if not exists person( 9 | id integer primary key, 10 | name varchar(9) not null unique, 11 | active bool not null default true, 12 | rank float not null default 0.0 13 | ); """) 14 | 15 | 16 | meta = MetaData() 17 | persons = Table( 18 | "person", meta, 19 | Column("id", Integer, primary_key = True), 20 | Column("name", String, nullable = False, unique = True), 21 | Column("active", Boolean, nullable = False, default = True), 22 | Column("rank", Float, nullable = False, default = 0.0), 23 | ) 24 | 25 | 26 | conn = engine.connect() 27 | 28 | 29 | ins = persons.insert() 30 | ins = persons.insert().values(id = 42, name = "Pepe", active = True, rank = 9.6) 31 | result = conn.execute(ins) 32 | 33 | 34 | persons_query = persons.select() 35 | result = conn.execute(persons_query) 36 | row = result.fetchone() 37 | 38 | print(row) 39 | -------------------------------------------------------------------------------- /gatabase.nimble: -------------------------------------------------------------------------------- 1 | version = "0.9.9" 2 | author = "Juan Carlos" 3 | description = "Compile-Time ORM for Nim" 4 | license = "MIT" 5 | srcDir = "src" 6 | skipDirs = @["tests", "examples", "docs"] 7 | 8 | requires "nim >= 1.2.6" 9 | -------------------------------------------------------------------------------- /gatabase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/3aaba860e3090504c77faa419e14c239dfd46045/gatabase.png -------------------------------------------------------------------------------- /multigata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/3aaba860e3090504c77faa419e14c239dfd46045/multigata.png -------------------------------------------------------------------------------- /sql_checking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/3aaba860e3090504c77faa419e14c239dfd46045/sql_checking.png -------------------------------------------------------------------------------- /src/gatabase.nim: -------------------------------------------------------------------------------- 1 | ## **Gatabase:** Connection-Pooling Compile-time lightweight ORM for Postgres or SQLite. 2 | ## * SQL DSL mimics SQL syntax!, API mimics stdlib!, Simple just 9 Templates!. 3 | ## * **Uses only system.nim, everything is done via template and macro, 0 Dependencies.** 4 | ## * **Static Connection Pooling Array with 100+ ORM Queries.** 5 | ## 6 | ## .. image:: https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/master/temp.jpg 7 | ## 8 | ## More Documentation 9 | ## ------------------ 10 | ## 11 | ## * `Gatabase Sugar `_ **Recommended**, but Optional, all Templates. 12 | ## * DSL use https://github.com/juancarlospaco/nim-gatabase#gatabase 13 | ## .. image:: https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/master/multigata.png 14 | import macros 15 | include gatabase/templates # Tiny compile-time internal templates that do 1 thing. 16 | 17 | 18 | when defined(postgres): 19 | import asyncdispatch # ,db_postgres 20 | include db_postgres 21 | 22 | const gataPool {.intdefine.}: Positive = 100 23 | type Gatabase* = ref object ## Gatabase 24 | pool*: array[gataPool, tuple[db: DbConn, ok: bool]] 25 | 26 | proc newGatabase*(connection, user, password, database: sink string, unroll: static[Positive] = 1): Gatabase {.inline.} = 27 | assert connection.len > 0 and user.len > 0 and password.len > 0 and database.len > 0 and gataPool > unroll 28 | result = Gatabase() 29 | for i in forU(0, static(gataPool - 1), unroll): # Cant use db_postgres.* here 30 | result.pool[i][0] = open(connection, user, password, database) 31 | result.pool[i][1] = false 32 | 33 | template len*(self: Gatabase): int = gataPool 34 | 35 | template `$`*(self: Gatabase): string = $(@(self.pool)) 36 | 37 | template close*(self: Gatabase, unroll: static[Positive] = 1) = 38 | assert gataPool > unroll 39 | for i in forU(0, static(gataPool - 1), unroll): 40 | self.pool[i][1] = false 41 | close(self.pool[i][0]) # is this required with ARC?. 42 | 43 | template getIdle(self: Gatabase): int = 44 | var jobless = -1 45 | while on: 46 | for i in 0.. static(gataPool - 1): 47 | if not self.pool[i][1]: 48 | self.pool[i][1] = true 49 | jobless = i 50 | break 51 | cpuRelax() 52 | if jobless != -1: break 53 | cpuRelax() 54 | jobless 55 | 56 | template internalRows(db: DbConn, query: SqlQuery, args: seq[string]): seq[Row] = 57 | var rows: seq[Row] 58 | if likely(db.status == CONNECTION_OK): 59 | let sent = create(int32, sizeOf int32) 60 | sent[] = pqsendQuery(db, dbFormat(query, args)) 61 | if unlikely(sent[] != 1): dbError(db) # doAssert 62 | while on: 63 | sent[] = pqconsumeInput(db) 64 | if unlikely(sent[] != 1): dbError(db) # doAssert 65 | if pqisBusy(db) == 1: 66 | cpuRelax() 67 | continue 68 | let pepe = create(PPGresult, sizeOf PPGresult) 69 | pepe[] = pqgetResult(db) # lib/wrappers/postgres.nim#L251 70 | if unlikely(pepe[] == nil): break 71 | let col = create(int32, sizeOf int32) 72 | col[] = pqnfields(pepe[]) 73 | let row = create(Row, sizeOf Row) 74 | row[] = newRow(int(col[])) 75 | for i in 0 ..< pqNtuples(pepe[]): 76 | setRow(pepe[], row[], i, col[]) 77 | rows.add row[] 78 | pqclear(pepe[]) 79 | cpuRelax() 80 | dealloc pepe 81 | dealloc col 82 | dealloc row 83 | dealloc sent 84 | rows 85 | 86 | proc getAllRows*(self: Gatabase, query: SqlQuery, args: seq[string]): Future[seq[Row]] {.async, inline.} = 87 | let i = create(int, sizeOf int) # Error: 'args' is of type which cannot be captured as it would violate memory safety. 88 | i[] = getIdle(self) 89 | result = internalRows(self.pool[i[]][0], query, args) 90 | self.pool[i[]][1] = false 91 | dealloc i 92 | 93 | proc execAffectedRows*(self: Gatabase, query: SqlQuery, args: seq[string]): Future[int64] {.async, inline.} = 94 | let i = create(int, sizeOf int) 95 | i[] = getIdle(self) 96 | result = int64(len(internalRows(self.pool[i[]][0], query, args))) 97 | self.pool[i[]][1] = false 98 | dealloc i 99 | 100 | proc exec*(self: Gatabase, query: SqlQuery, args: seq[string]) {.async, inline.} = 101 | let i = create(int, sizeOf int) 102 | i[] = getIdle(self) 103 | discard internalRows(self.pool[i[]][0], query, args) 104 | self.pool[i[]][1] = false 105 | dealloc i 106 | 107 | 108 | macro cueri(inner: untyped): auto = 109 | var 110 | offsetUsed, limitUsed, fromUsed, whereUsed, orderUsed, selectUsed, 111 | deleteUsed, likeUsed, valuesUsed, betweenUsed, joinUsed, groupbyUsed, 112 | havingUsed, intoUsed, insertUsed, isnullUsed, resetUsed, updateUsed: bool 113 | sqls: string 114 | const err0 = "Wrong Syntax, nested SubQueries not supported, repeated call found. " 115 | for node in inner: 116 | doAssert node.kind == nnkCommand, "Wrong DSL Syntax, must be nnkCommand, but is " & $node.kind 117 | case $node[0] 118 | of "limit": 119 | doAssert not limitUsed, err0 120 | doAssert fromUsed, err0 & "LIMIT without FROM" 121 | doAssert selectUsed or insertUsed or deleteUsed, err0 & """ 122 | LIMIT without SELECT nor INSERT nor UPDATE nor DELETE""" 123 | sqls.add limits(node[1]) 124 | limitUsed = true 125 | of "offset": 126 | doAssert not offsetUsed, err0 127 | doAssert limitUsed, err0 & "OFFSET without LIMIT" 128 | doAssert selectUsed or insertUsed or deleteUsed, err0 & """ 129 | OFFSET without SELECT nor INSERT nor UPDATE nor DELETE""" 130 | sqls.add offsets(node[1]) 131 | offsetUsed = true 132 | of "from": 133 | doAssert not fromUsed, err0 134 | doAssert selectUsed or deleteUsed, err0 & "FROM without SELECT nor DELETE" 135 | sqls.add froms(node[1]) 136 | fromUsed = true 137 | of "where": 138 | doAssert not whereUsed, err0 139 | doAssert selectUsed or insertUsed or deleteUsed, err0 & """ 140 | WHERE without SELECT nor INSERT nor UPDATE nor DELETE""" 141 | sqls.add wheres(node[1]) 142 | whereUsed = true 143 | of "wherenot": 144 | doAssert not whereUsed, err0 145 | doAssert selectUsed or insertUsed or deleteUsed, err0 & """ 146 | WHERE NOT without SELECT nor INSERT nor UPDATE nor DELETE""" 147 | sqls.add whereNots(node[1]) 148 | whereUsed = true 149 | of "whereexists": 150 | doAssert not whereUsed, err0 151 | doAssert selectUsed or insertUsed or deleteUsed, err0 & """ 152 | WHERE EXISTS without SELECT nor INSERT nor UPDATE nor DELETE""" 153 | sqls.add whereExists(node[1]) 154 | whereUsed = true 155 | of "wherenotexists": 156 | doAssert not whereUsed, err0 157 | doAssert selectUsed or insertUsed or deleteUsed, err0 & """ 158 | WHERE NOT EXISTS without SELECT nor INSERT nor UPDATE nor DELETE""" 159 | sqls.add whereNotExists(node[1]) 160 | whereUsed = true 161 | of "order", "orderby": 162 | doAssert not orderUsed, err0 163 | doAssert selectUsed, err0 & "ORDER BY without SELECT" 164 | sqls.add orderbys(node[1]) 165 | orderUsed = true 166 | of "select": 167 | doAssert not selectUsed, err0 168 | sqls.add selects(node[1]) 169 | selectUsed = true 170 | of "selectdistinct": 171 | doAssert not selectUsed, err0 172 | sqls.add selectDistincts(node[1]) 173 | selectUsed = true 174 | of "delete": 175 | doAssert not deleteUsed, err0 176 | sqls.add deletes(node[1]) 177 | deleteUsed = true 178 | of "like": 179 | doAssert not likeUsed and whereUsed, err0 180 | doAssert selectUsed or whereUsed or insertUsed or deleteUsed, err0 & """ 181 | LIKE without WHERE nor SELECT nor INSERT nor UPDATE nor DELETE""" 182 | sqls.add likes(node[1]) 183 | likeUsed = true 184 | of "notlike": 185 | doAssert not likeUsed and whereUsed, err0 186 | doAssert selectUsed or whereUsed or insertUsed or deleteUsed, err0 & """ 187 | NOT LIKE without WHERE nor SELECT nor INSERT nor UPDATE nor DELETE""" 188 | sqls.add notlikes(node[1]) 189 | likeUsed = true 190 | of "between": 191 | doAssert not betweenUsed and whereUsed, err0 192 | doAssert selectUsed or insertUsed or deleteUsed, err0 & """ 193 | BETWEEN without SELECT nor INSERT nor UPDATE nor DELETE""" 194 | sqls.add betweens(node[1]) 195 | betweenUsed = true 196 | of "notbetween": 197 | doAssert not betweenUsed and whereUsed, err0 198 | doAssert selectUsed or insertUsed or deleteUsed, err0 & """ 199 | NOT BETWEEN without SELECT nor INSERT nor UPDATE nor DELETE""" 200 | sqls.add notbetweens(node[1]) 201 | betweenUsed = true 202 | of "groupby", "group": 203 | doAssert not groupbyUsed, err0 204 | doAssert selectUsed, err0 & "GROUP BY without SELECT" 205 | sqls.add groupbys(node[1]) 206 | groupbyUsed = true 207 | of "into": 208 | doAssert not intoUsed, err0 209 | doAssert selectUsed, err0 & "INTO without SELECT" 210 | sqls.add intos(node[1]) 211 | intoUsed = true 212 | of "insert", "insertinto": 213 | doAssert not insertUsed, err0 214 | sqls.add inserts(node[1]) 215 | insertUsed = true 216 | of "update": 217 | doAssert not updateUsed, err0 218 | sqls.add updates(node[1]) 219 | updateUsed = true 220 | of "set": 221 | doAssert updateUsed, "SET without UPDATE" 222 | sqls.add sets(node[1]) 223 | of "values": # This is the only ones that actually take values. 224 | {.linearScanEnd.} # https://nim-lang.github.io/Nim/manual.html#pragmas-linearscanend-pragma 225 | doAssert not valuesUsed, err0 # Below put the less frequently used case branches. 226 | doAssert insertUsed, err0 & "VALUES without INSERT INTO" 227 | sqls.add values(node[1].intVal.Positive) 228 | valuesUsed = true 229 | of "--": sqls.add sqlComment($node[1]) 230 | of "having": 231 | doAssert not havingUsed, err0 232 | doAssert groupbyUsed, err0 & "HAVING without GROUP BY" 233 | sqls.add havings(node[1]) 234 | havingUsed = true 235 | of "selecttop": 236 | doAssert not selectUsed, err0 237 | sqls.add selectTops(node[1]) 238 | selectUsed = true 239 | of "selectmin": 240 | doAssert not selectUsed, err0 241 | sqls.add selectMins(node[1]) 242 | selectUsed = true 243 | of "selectmax": 244 | doAssert not selectUsed, err0 245 | sqls.add selectMaxs(node[1]) 246 | selectUsed = true 247 | of "selectcount": 248 | doAssert not selectUsed, err0 249 | sqls.add selectCounts(node[1]) 250 | selectUsed = true 251 | of "selectavg": 252 | doAssert not selectUsed, err0 253 | sqls.add selectAvgs(node[1]) 254 | selectUsed = true 255 | of "selectsum": 256 | doAssert not selectUsed, err0 257 | sqls.add selectSums(node[1]) 258 | selectUsed = true 259 | of "selecttrim": 260 | doAssert not selectUsed, err0 261 | sqls.add selectTrims(node[1]) 262 | selectUsed = true 263 | of "selectround2": 264 | doAssert not selectUsed, err0 265 | sqls.add selectRound2(node[1]) 266 | selectUsed = true 267 | of "selectround4": 268 | doAssert not selectUsed, err0 269 | sqls.add selectRound4(node[1]) 270 | selectUsed = true 271 | of "selectround6": 272 | doAssert not selectUsed, err0 273 | sqls.add selectRound6(node[1]) 274 | selectUsed = true 275 | of "union": 276 | doAssert not resetUsed, err0 277 | resetAllGuards() 278 | sqls.add unions(node[1]) 279 | resetUsed = true 280 | of "intersect": 281 | doAssert not resetUsed, err0 282 | resetAllGuards() 283 | sqls.add intersects(node[1]) 284 | resetUsed = true 285 | of "except": 286 | doAssert not resetUsed, err0 287 | resetAllGuards() 288 | sqls.add excepts(node[1]) 289 | resetUsed = true 290 | of "isnull": 291 | doAssert not isnullUsed, err0 292 | doAssert selectUsed or insertUsed or deleteUsed, err0 & """ 293 | IS NULL without SELECT nor INSERT nor UPDATE nor DELETE""" 294 | sqls.add isnulls(node[1]) 295 | isnullUsed = true 296 | of "innerjoin": 297 | doAssert not joinUsed, err0 298 | doAssert selectUsed, err0 & "INNER JOIN without SELECT" 299 | sqls.add innerjoins(node[1]) 300 | joinUsed = true 301 | of "leftjoin": 302 | doAssert not joinUsed, err0 303 | doAssert selectUsed, err0 & "LEFT JOIN without SELECT" 304 | sqls.add leftjoins(node[1]) 305 | joinUsed = true 306 | of "rightjoin": 307 | doAssert not joinUsed, err0 308 | doAssert selectUsed, err0 & "RIGHT JOIN without SELECT" 309 | sqls.add rightjoins(node[1]) 310 | joinUsed = true 311 | of "fulljoin": 312 | doAssert not joinUsed, err0 313 | doAssert selectUsed, err0 & "FULL JOIN without SELECT" 314 | sqls.add fulljoins(node[1]) 315 | joinUsed = true 316 | of "case": sqls.add cases(node[1]) 317 | of "commentoncolumn": sqls.add comments(node[1], "COLUMN") 318 | of "commentondatabase": sqls.add comments(node[1], "DATABASE") 319 | of "commentonfunction": sqls.add comments(node[1], "FUNCTION") 320 | of "commentonindex": sqls.add comments(node[1], "INDEX") 321 | of "commentontable": sqls.add comments(node[1], "TABLE") 322 | else: doAssert false, "Unknown syntax error on ORMs DSL: " & inner.lineInfo 323 | when not defined(release) or not defined(danger): 324 | if unlikely(deleteUsed and not whereUsed): {.warning: "DELETE FROM without WHERE.".} 325 | assert sqls.len > 0, "Unknown error on SQL DSL, SQL Query must not be empty." 326 | sqls.add ";\n" 327 | # sqls.add "/* " & $inner.lineInfo & "*/\n" 328 | when defined(dev): echo sqls 329 | result = quote do: sql(`sqls`) 330 | 331 | template exec*(args: varargs[string, `$`] or seq[string]; inner: untyped) = 332 | ## Mimics `exec` but using Gatabase DSL. 333 | ## * `args` are passed as-is to `exec()`, if no `args` use `[]`, example `[42, "OwO", true]`. 334 | ## 335 | ## .. code-block::nim 336 | ## exec []: 337 | ## delete "person" 338 | ## where "active = false" 339 | exec(db, cueri(inner), args) 340 | 341 | template tryExec*(args: varargs[string, `$`] or seq[string]; inner: untyped): bool = 342 | ## Mimics `tryExec` but using Gatabase DSL. 343 | ## * `args` are passed as-is to `tryExec()`, if no `args` use `[]`, example `[42, "OwO", true]`. 344 | ## 345 | ## .. code-block::nim 346 | ## let killUser: bool = tryExec []: 347 | ## delete "person" 348 | ## where "id = 42" 349 | ## 350 | ## .. code-block::nim 351 | ## let killUser: bool = tryExec []: 352 | ## select "name" 353 | ## `from` "person" 354 | ## wherenot "active = true" 355 | tryExec(db, cueri(inner), args) 356 | 357 | template getRow*(args: varargs[string, `$`] or seq[string]; inner: untyped): auto = 358 | ## Mimics `getRow` but using Gatabase DSL. 359 | ## * `args` are passed as-is to `getRow()`, if no `args` use `[]`, example `[42, "OwO", true]`. 360 | ## 361 | ## .. code-block::nim 362 | ## let topUser: Row = getAllRows []: 363 | ## selecttop "username" 364 | ## `from` "person" 365 | ## limit 1 366 | getRow(db, cueri(inner), args) 367 | 368 | template getAllRows*(args: varargs[string, `$`] or seq[string]; inner: untyped): auto = 369 | ## Mimics `getAllRows` but using Gatabase DSL. 370 | ## * `args` are passed as-is to `getAllRows()`, if no `args` use `[]`, example `[42, "OwO", true]`. 371 | ## 372 | ## .. code-block::nim 373 | ## let allUsers: seq[Row] = [].getAllRows: 374 | ## select '*' 375 | ## `from` "person" 376 | ## 377 | ## .. code-block::nim 378 | ## var allUsers: seq[Row] = getAllRows []: 379 | ## selectdistinct "names" 380 | ## `from` "person" 381 | getAllRows(db, cueri(inner), args) 382 | 383 | template getValue*(args: varargs[string, `$`] or seq[string]; inner: untyped): string = 384 | ## Mimics `getValue` but using Gatabase DSL. 385 | ## * `args` are passed as-is to `getValue()`, if no `args` use `[]`, example `[42, "OwO", true]`. 386 | ## 387 | ## .. code-block::nim 388 | ## let userName: string = [].getValue: 389 | ## select "name" 390 | ## `from` "person" 391 | ## where "id = 42" 392 | ## 393 | ## .. code-block::nim 394 | ## let age: string = getValue []: 395 | ## select "age" 396 | ## `from` "person" 397 | ## orderby DescNullsLast 398 | ## limit 1 399 | getValue(db, cueri(inner), args) 400 | 401 | template tryInsertID*(args: varargs[string, `$`] or seq[string]; inner: untyped): int64 = 402 | ## Mimics `tryInsertID` but using Gatabase DSL. 403 | ## * `args` are passed as-is to `tryInsertID()`, if no `args` use `[]`, example `[42, "OwO", true]`. 404 | ## 405 | ## .. code-block::nim 406 | ## let newUser: int64 = tryInsertID ["Graydon Hoare", "graydon.hoare@nim-lang.org"]: 407 | ## insertinto "person" 408 | ## values 2 409 | tryInsertID(db, cueri(inner), args) 410 | 411 | template insertID*(args: varargs[string, `$`] or seq[string]; inner: untyped): int64 = 412 | ## Mimics `insertID` but using Gatabase DSL. 413 | ## * `args` are passed as-is to `insertID()`, if no `args` use `[]`, example `[42, "OwO", true]`. 414 | ## 415 | ## .. code-block::nim 416 | ## let newUser: int64 = ["Ryan Dahl", "ryan.dahl@nim-lang.org"].insertID: 417 | ## insertinto "person" 418 | ## values 2 419 | insertID(db, cueri(inner), args) 420 | 421 | template tryInsert*(pkName: string; args: varargs[string, `$`] or seq[string]; inner: untyped): int64 = 422 | ## Mimics `tryInsert` but using Gatabase DSL. 423 | ## * `args` are passed as-is to `tryInsert()`, if no `args` use `[]`, example `[42, "OwO", true]`. 424 | tryInsert(db, cueri(inner), pkName, args) 425 | 426 | template insert*(pkName: string; args: varargs[string, `$`] or seq[string]; inner: untyped): int64 = 427 | ## Mimics `insert` but using Gatabase DSL. 428 | ## * `args` are passed as-is to `insertID()`, if no `args` use `[]`, example `[42, "OwO", true]`. 429 | insert(db, cueri(inner), pkName, args) 430 | 431 | template execAffectedRows*(args: varargs[string, `$`] or seq[string]; inner: untyped): auto = 432 | ## Mimics `execAffectedRows` but using Gatabase DSL. 433 | ## * `args` are passed as-is to `execAffectedRows()`, if no `args` use `[]`, example `[42, "OwO", true]`. 434 | ## 435 | ## .. code-block::nim 436 | ## let activeUsers: int64 = execAffectedRows []: 437 | ## select "status" 438 | ## `from` "users" 439 | ## `--` "This is a SQL comment" 440 | ## where "status = true" 441 | ## isnull false 442 | ## 443 | ## .. code-block::nim 444 | ## let distinctNames: int64 = execAffectedRows []: 445 | ## selectdistinct "name" 446 | ## `from` "users" 447 | execAffectedRows(db, cueri(inner), args) 448 | 449 | template getValueParsed*(args: varargs[string, `$`] or seq[string]; parseProc: proc; inner: untyped): auto = 450 | ## Alias for `parseProc(getValue(db, sql("..."), args))`. **Returns actual value instead of string**. 451 | ## * `parseProc` is whatever proc parses the value of `getValue()`, any proc should work. 452 | ## * `args` are passed as-is to `getValue()`, if no `args` use `[]`, example `[42, "OwO", true]`. 453 | ## 454 | ## .. code-block::nim 455 | ## let age: int = getValueParsed([], parseInt): 456 | ## select "age" 457 | ## `from` "users" 458 | ## limit 1 459 | ## 460 | ## .. code-block::nim 461 | ## let ranking: float = getValueParsed([], parseFloat): 462 | ## select "ranking" 463 | ## `from` "users" 464 | ## where "id = 42" 465 | ## 466 | ## .. code-block::nim 467 | ## let preferredColor: string = [].getValueParsed(parseHexStr): 468 | ## select "color" 469 | ## `from` "users" 470 | ## limit 1 471 | parseProc(getValue(db, cueri(inner), args)) 472 | 473 | template sqls*(inner: untyped): auto = 474 | ## Build a `SqlQuery` using Gatabase ORM DSL, returns a vanilla `SqlQuery`. 475 | ## 476 | ## .. code-block::nim 477 | ## const data: SqlQuery = sqls: 478 | ## select '*' 479 | ## `from` "users" 480 | ## 481 | ## .. code-block::nim 482 | ## let data: SqlQuery = sqls: 483 | ## select "name" 484 | ## `from` "users" 485 | ## limit 9 486 | ## 487 | ## .. code-block::nim 488 | ## var data: SqlQuery = sqls: 489 | ## delete '*' 490 | ## `from` "users" 491 | cueri(inner) 492 | -------------------------------------------------------------------------------- /src/gatabase/sugar.nim: -------------------------------------------------------------------------------- 1 | ## Gatabase Sugar 2 | ## ============== 3 | ## 4 | ## .. image:: https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/master/docs/sugar.jpg 5 | ## 6 | ## Syntax Sugar for Gatabase using `template`. 7 | ## 8 | ## `include` or `import` *after* importing `db_sqlite` or `db_postgres` to use it on your code. 9 | ## 10 | ## .. code-block::nim 11 | ## import db_sqlite 12 | ## include gatabase/sugar 13 | ## 14 | ## .. code-block::nim 15 | ## import db_postgres 16 | ## include gatabase/sugar 17 | ## 18 | ## All Gatabase sugar is always optional. 19 | ## The templates are very efficient, no stdlib imports, no object heap alloc, 20 | ## no string formatting, just primitives, no more than 1 variable used. 21 | 22 | import db_common, std/exitprocs 23 | when defined(postgres): from db_postgres import Row else: from db_sqlite import Row 24 | 25 | 26 | template createTable*(name: static string; code: untyped): SqlQuery = 27 | ## Create a new database table `name` with fields from `code`, returns 1 `SqlQuery`. 28 | ## Works with Postgres and Sqlite. `SqlQuery` is pretty-printed when not built for release. 29 | ## 30 | ## .. code-block::nim 31 | ## import db_sqlite 32 | ## include gatabase/sugar 33 | ## let myTable = createTable "kitten": [ 34 | ## "age" := 1, 35 | ## "sex" := 'f', 36 | ## "name" := "unnamed", 37 | ## "rank" := 3.14, 38 | ## "weight" := int, 39 | ## "color" := char, 40 | ## "owner" := string, 41 | ## "food" := float, 42 | ## ] 43 | ## 44 | ## Generates the SQL Query: 45 | ## 46 | ## .. code-block:: 47 | ## CREATE TABLE IF NOT EXISTS kitten( 48 | ## id INTEGER PRIMARY KEY, 49 | ## age INTEGER NOT NULL DEFAULT 1, 50 | ## sex VARCHAR(1) NOT NULL DEFAULT 'f', 51 | ## name TEXT NOT NULL DEFAULT 'unnamed', 52 | ## rank REAL NOT NULL DEFAULT 3.14, 53 | ## weight INTEGER, 54 | ## color VARCHAR(1), 55 | ## owner TEXT, 56 | ## food REAL, 57 | ## ); 58 | ## 59 | ## More examples: 60 | ## * https://github.com/juancarlospaco/nim-gatabase/blob/master/examples/database_fields_example.nim#L1 61 | assert name.len > 0, "Table name must not be empty string" 62 | const nl = when defined(release): " " else: "\n" 63 | 64 | template `:=`(dbfield: static string; value: static char): string = 65 | assert dbfield.len > 0, "Table field name must not be empty string" 66 | "\t" & dbfield & "\t" & "VARCHAR(1)\tNOT NULL\tDEFAULT '" & $value & "'" 67 | 68 | template `:=`(dbfield: static string; value: static SomeFloat): string = 69 | assert dbfield.len > 0, "Table field name must not be empty string" 70 | "\t" & dbfield & "\t" & "REAL\tNOT NULL\tDEFAULT " & $value 71 | 72 | template `:=`(dbfield: static string; value: static SomeInteger): string = 73 | assert dbfield.len > 0, "Table field name must not be empty string" 74 | "\t" & dbfield & "\t" & "INTEGER\tNOT NULL\tDEFAULT " & $value 75 | 76 | template `:=`(dbfield: static string; value: static bool): string = 77 | assert dbfield.len > 0, "Table field name must not be empty string" 78 | "\t" & dbfield & "\t" & "BOOLEAN\tNOT NULL\tDEFAULT " & (if $value == "true": "1" else: "0") 79 | 80 | template `:=`(dbfield: static string; value: static string): string = 81 | assert dbfield.len > 0, "Table field name must not be empty string" 82 | "\t" & dbfield & "\t" & "TEXT\tNOT NULL\tDEFAULT '" & $value & "'" 83 | 84 | template `:=`(dbfield: static string; value: typedesc[char]): string = 85 | assert dbfield.len > 0, "Table field name must not be empty string" 86 | "\t" & $dbfield & "\t" & "VARCHAR(1)" 87 | 88 | template `:=`(dbfield: static string; value: typedesc[SomeFloat]): string = 89 | assert dbfield.len > 0, "Table field name must not be empty string" 90 | "\t" & $dbfield & "\t" & "REAL" 91 | 92 | template `:=`(dbfield: static string; value: typedesc[SomeInteger]): string = 93 | assert dbfield.len > 0, "Table field name must not be empty string" 94 | "\t" & $dbfield & "\t" & "INTEGER" 95 | 96 | template `:=`(dbfield: static string; value: typedesc[bool]): string = 97 | assert dbfield.len > 0, "Table field name must not be empty string" 98 | "\t" & $dbfield & "\t" & "BOOLEAN" 99 | 100 | template `:=`(dbfield: static string; value: typedesc[string]): string = 101 | assert dbfield.len > 0, "Table field name must not be empty string" 102 | "\t" & $dbfield & "\t" & "TEXT" 103 | 104 | template `:=`(dbfield: static cstring; value: typedesc[char]): string = 105 | assert dbfield.len > 0, "Table field name must not be empty string" 106 | "\t" & $dbfield & "\t" & "VARCHAR(1)\tUNIQUE" 107 | 108 | template `:=`(dbfield: static cstring; value: typedesc[SomeFloat]): string = 109 | assert dbfield.len > 0, "Table field name must not be empty string" 110 | "\t" & $dbfield & "\t" & "REAL\tUNIQUE" 111 | 112 | template `:=`(dbfield: static cstring; value: typedesc[SomeInteger]): string = 113 | assert dbfield.len > 0, "Table field name must not be empty string" 114 | "\t" & $dbfield & "\t" & "INTEGER\tUNIQUE" 115 | 116 | template `:=`(dbfield: static cstring; value: typedesc[bool]): string = 117 | assert dbfield.len > 0, "Table field name must not be empty string" 118 | "\t" & $dbfield & "\t" & "BOOLEAN\tUNIQUE" 119 | 120 | template `:=`(dbfield: static cstring; value: typedesc[string]): string = 121 | assert dbfield.len > 0, "Table field name must not be empty string" 122 | "\t" & $dbfield & "\t" & "TEXT\tUNIQUE" 123 | 124 | var cueri = "CREATE TABLE IF NOT EXISTS " & name & "(" & nl & ( 125 | when defined(postgres): " id\tINTEGER\tGENERATED BY DEFAULT AS IDENTITY," 126 | else: " id\tINTEGER\tPRIMARY KEY,") & nl 127 | 128 | for index, field in code: 129 | if index != code.len - 1: 130 | cueri.add field & "," & nl 131 | else: 132 | cueri.add field & nl 133 | 134 | cueri.add ");" # http://blog.2ndquadrant.com/postgresql-10-identity-columns 135 | sql(cueri) 136 | 137 | 138 | template dropTable*(db; name: string): bool = 139 | ## Alias for `tryExec(db, sql("DROP TABLE IF EXISTS ?"), name)`. 140 | ## Requires a `db` of `DbConn` type. Works with Postgres and Sqlite. 141 | ## Deleted tables can not be restored, be careful. 142 | assert name.len > 0, "Table name must not be empty string" 143 | tryExec(db, sql("DROP TABLE IF EXISTS ?" & (when defined(postgres): " CASCADE" else: "")), name) 144 | 145 | 146 | template withSqlite*(path: static[string]; initTableSql: static[string]; closeOnQuit: static[bool]; closeOnCtrlC: static[bool]; code: untyped): untyped = 147 | ## Open, run `initTableSql` and Auto-Close a SQLite database. 148 | ## * `path` path to SQLite database file. 149 | ## * `initTableSql` SQL query string to initialize the database, `create table if not exists` alike. 150 | ## * `closeOnQuit` if `true` then `addQuitProc(db.close())` is set. 151 | ## * `closeOnCtrlC` if `true` then `setControlCHook(db.close())` is set. 152 | ## 153 | ## .. code-block::nim 154 | ## import db_sqlite 155 | ## include gatabase/sugar 156 | ## const exampleTable = """ 157 | ## create table if not exists person( 158 | ## id integer primary key, 159 | ## name text, 160 | ## active bool, 161 | ## rank float 162 | ## ); """ 163 | ## 164 | ## withSqlite(":memory:", exampleTable, false): ## This is just an example. 165 | ## db.exec(sql"insert into person(name, active, rank) values('pepe', true, 42.0)") 166 | assert path.len > 0, "path must not be empty string" 167 | var db {.inject, global.} = db_sqlite.open(path, "", "", "") 168 | if initTableSql.len == 0 or db.tryExec(sql(initTableSql)): 169 | try: 170 | when closeOnQuit: addExitProc((proc () {.noconv.} = db_sqlite.close(db))) 171 | when closeOnCtrlC: system.setControlCHook((proc () {.noconv.} = db_sqlite.close(db))) 172 | code 173 | finally: 174 | db_sqlite.close(db) 175 | else: 176 | when not defined(release): echo "Error executing initTableSql:\n" & initTableSql 177 | 178 | 179 | template withPostgres*(host, user, password, dbname: string; initTableSql: static[string]; closeOnQuit: static[bool]; closeOnCtrlC: static[bool]; code: untyped): untyped = 180 | ## Open, run `initTableSql` and Auto-Close a Postgres database. See `withSqlite` for an example. 181 | ## * `host` host of Postgres Server, string type, must not be empty string. 182 | ## * `user` user of Postgres Server, string type, must not be empty string. 183 | ## * `password` password of Postgres Server, string type, must not be empty string. 184 | ## * `dbname` database name of Postgres Server, string type, must not be empty string. 185 | ## * `initTableSql` SQL query string to initialize the database, `create table if not exists` alike. 186 | ## * `closeOnQuit` if `true` then `addQuitProc(db.close())` is set. 187 | ## * `closeOnCtrlC` if `true` then `setControlCHook(db.close())` is set. 188 | assert host.len > 0, "host must not be empty string" 189 | assert user.len > 0, "user must not be empty string" 190 | assert password.len > 0, "password must not be empty string" 191 | assert dbname.len > 0, "dbname must not be empty string" 192 | var db {.inject, global.} = db_postgres.open(host, user, password, dbname) 193 | if initTableSql.len == 0 or db.tryExec(sql(initTableSql)): 194 | try: 195 | when closeOnQuit: addExitProc((proc () {.noconv.} = db_postgres.close(db))) 196 | when closeOnCtrlC: system.setControlCHook((proc () {.noconv.} = db_postgres.close(db))) 197 | code 198 | finally: 199 | db_postgres.close(db) 200 | else: 201 | when not defined(release): echo "Error executing initTableSql:\n" & initTableSql 202 | 203 | 204 | {.push experimental: "dotOperators".} 205 | template `.`*(indx: int; data: Row): int = parseInt(data[indx]) 206 | ## `9.row` convenience alias for `strutils.parseInt(row[9])` (`row` is `Row` type). 207 | template `.`*(indx: char; data: Row): char = char(data[parseInt($indx)][0]) 208 | ## `'9'.row` convenience alias for `char(row[strutils.parseInt($indx)][0])` (`row` is `Row` type). 209 | template `.`*(indx: uint; data: Row): uint = uint(parseInt(data[indx])) 210 | ## `9'u.row` convenience alias for `uint(strutils.parseInt(row[9]))` (`row` is `Row` type). 211 | template `.`*(indx: cint; data: Row): cint = cint(parseInt(data[indx])) 212 | ## `cint(9).row` convenience alias for `cint(strutils.parseInt(row[9]))` (`row` is `Row` type). 213 | template `.`*(indx: int8; data: Row): int8 = int8(parseInt(data[indx])) 214 | ## `9'i8.row` convenience alias for `int8(strutils.parseInt(row[9]))` (`row` is `Row` type). 215 | template `.`*(indx: byte; data: Row): byte = byte(parseInt(data[indx])) 216 | ## `byte(9).row` convenience alias for `byte(strutils.parseInt(row[9]))` (`row` is `Row` type). 217 | template `.`*(indx: int16; data: Row): int16 = int16(parseInt(data[indx])) 218 | ## `9'i16.row` convenience alias for `int16(strutils.parseInt(row[9]))` (`row` is `Row` type). 219 | template `.`*(indx: int32; data: Row): int32 = int32(parseInt(data[indx])) 220 | ## `9'i32.row` convenience alias for `int32(strutils.parseInt(row[9]))` (`row` is `Row` type). 221 | template `.`*(indx: int64; data: Row): int64 = int64(parseInt(data[indx])) 222 | ## `9'i64.row` convenience alias for `int64(strutils.parseInt(row[9]))` (`row` is `Row` type). 223 | template `.`*(indx: uint8; data: Row): uint8 = uint8(parseInt(data[indx])) 224 | ## `9'u8.row` convenience alias for `uint8(strutils.parseInt(row[9]))` (`row` is `Row` type). 225 | template `.`*(indx: uint16; data: Row): uint16 = uint16(parseInt(data[indx])) 226 | ## `9'u16.row` convenience alias for `uint16(strutils.parseInt(row[9]))` (`row` is `Row` type). 227 | template `.`*(indx: uint32; data: Row): uint32 = uint32(parseInt(data[indx])) 228 | ## `9'u32.row` convenience alias for `uint32(strutils.parseInt(row[9]))` (`row` is `Row` type). 229 | template `.`*(indx: uint64; data: Row): uint64 = uint64(parseInt(data[indx])) 230 | ## `9'u64.row` convenience alias for `uint64(strutils.parseInt(row[9]))` (`row` is `Row` type). 231 | template `.`*(indx: float; data: Row): float = parseFloat(data[int(indx)]) 232 | ## `9.0.row` convenience alias for `strutils.parseFloat(row[int(9)])` (`row` is `Row` type). 233 | template `.`*(indx: Natural; data: Row): Natural = Natural(parseInt(data[indx])) 234 | ## `Natural(9).row` convenience alias for `Natural(strutils.parseInt(row[9]))` (`row` is `Row` type). 235 | template `.`*(indx: cstring; data: Row): cstring = cstring(data[parseInt($indx)]) 236 | ## `cstring("9").row` convenience alias for `cstring(row[9])` (`row` is `Row` type). 237 | template `.`*(indx: Positive; data: Row): Positive = Positive(parseInt(data[indx])) 238 | ## `Positive(9).row` convenience alias for `Positive(strutils.parseInt(row[9]))` (`row` is `Row` type). 239 | template `.`*(indx: BiggestInt; data: Row): BiggestInt = BiggestInt(parseInt(data[indx])) 240 | ## `BiggestInt(9).row` convenience alias for `BiggestInt(strutils.parseInt(row[9]))` (`row` is `Row` type). 241 | template `.`*(indx: BiggestUInt; data: Row): BiggestUInt = BiggestUInt(parseInt(data[indx])) 242 | ## `BiggestUInt(9).row` convenience alias for `BiggestUInt(strutils.parseInt(row[9]))` (`row` is `Row` type). 243 | template `.`*(indx: float32; data: Row): float32 = float32(parseFloat(data[int(indx)])) 244 | ## `9.0'f32.row` convenience alias for `float32(strutils.parseFloat(row[int(9)]))` (`row` is `Row` type). 245 | {.pop.} 246 | -------------------------------------------------------------------------------- /src/gatabase/templates.nim: -------------------------------------------------------------------------------- 1 | # Tiny compile-time internal templates that do 1 thing, do NOT put other logic here. 2 | const n = when defined(release): " " else: "\n" 3 | 4 | 5 | func parseBool(s: string): bool {.inline.} = 6 | case s 7 | of "y", "Y", "1", "ON", "On", "oN", "on", 8 | "yes", "YES", "YEs", "YeS", "Yes", "yES", "yEs", "yeS", 9 | "TRUE", "TRUe", "TRuE", "TRue", "TrUE", "TrUe", "TruE", "True", "tRUE", 10 | "tRUe", "tRuE", "tRue", "trUE", "trUe", "truE", "true": result = true 11 | of "n", "N", "0", "NO", "No", "nO", "no", 12 | "OFF", "OFf", "OfF", "Off", "oFF", "oFf", "ofF", "off", 13 | "FALSE", "FALSe", "FALsE", "FALse", "FAlSE", "FAlSe", "FAlsE", "FAlse", 14 | "FaLSE", "FaLSe", "FaLsE", "FaLse", "FalSE", "FalSe", "FalsE", "False", 15 | "fALSE", "fALSe", "fALsE", "fALse", "fAlSE", "fAlSe", "fAlsE", "fAlse", 16 | "faLSE", "faLSe", "faLsE", "faLse", "falSE", "falSe", "falsE", "false": result = false 17 | else: doAssert false, "cannot interpret as a bool" 18 | 19 | template forU(a: SomeInteger; b: SomeInteger; u: Positive): untyped = 20 | when defined(gcc) and not defined(js): system.`||`(a, b, "\n\n#pragma GCC unroll " & $u) elif defined(clang) and not defined(js): system.`||`(a, b, "\n\n#pragma unroll " & $u) else: system.`..`(a, b) 21 | 22 | template isQuestionChar(value: NimNode): bool = 23 | unlikely(value.kind == nnkCharLit and value.intVal == 63) 24 | 25 | 26 | template isQuestionOrNatural(value: NimNode) = 27 | doAssert value.kind in {nnkIntLit, nnkCharLit}, "value must be Natural or '?'" 28 | if value.kind == nnkCharLit: doAssert value.intVal == 63, "value must be '?'" 29 | if value.kind == nnkIntLit: doAssert Natural(value.intVal) is int, "value must be Natural" 30 | 31 | 32 | template isQuestionOrPositive(value: NimNode) = 33 | doAssert value.kind in {nnkIntLit, nnkCharLit}, "value must be Natural or '?'" 34 | if value.kind == nnkCharLit: doAssert value.intVal == 63, "value must be '?'" 35 | if value.kind == nnkIntLit: doAssert Positive(value.intVal) is int, "value must be Positive" 36 | 37 | 38 | template isQuestionOrString(value: NimNode) = 39 | doAssert value.kind in {nnkStrLit, nnkTripleStrLit, nnkRStrLit, nnkCharLit}, "value must be string or '?'" 40 | if value.kind == nnkCharLit: doAssert value.intVal == 63, "value must be '?'" 41 | if value.kind in {nnkStrLit, nnkTripleStrLit, nnkRStrLit}: doAssert value.strVal.len > 0, "value must not be empty string" 42 | 43 | 44 | template isCharOrString(value: NimNode) = 45 | doAssert value.kind in {nnkStrLit, nnkTripleStrLit, nnkRStrLit, nnkCharLit}, "value must be string or '?' or '*'" 46 | if value.kind == nnkCharLit: doAssert value.intVal == 63 or value.intVal == 42, "value must be '?' or '*'" 47 | if value.kind in {nnkStrLit, nnkTripleStrLit, nnkRStrLit}: doAssert value.strVal.len > 0, "value must not be empty string" 48 | 49 | 50 | template isTable(value: NimNode) = 51 | doAssert value.kind == nnkTableConstr, "value must be Table" 52 | doAssert value.len > 0, "value must be 1 Non Empty Table" 53 | for t in value: doAssert t[0].strVal.len > 0, "Table keys must not be empty string" 54 | 55 | 56 | template isArrayStr(value: NimNode) = 57 | doAssert value.kind == nnkBracket, "value must be Array" 58 | doAssert value.len > 0, "value must be 1 Non Empty Array" 59 | for t in value: doAssert t.strVal.len > 0, "Array items must not be empty string" 60 | 61 | 62 | template sqlComment(comment: string): string = 63 | doAssert comment.len > 0, "SQL Comment must not be empty string" 64 | when defined(release): n else: "/* " & $comment & static(" */" & n) 65 | 66 | 67 | template offsets(value: NimNode): string = 68 | isQuestionOrNatural(value) 69 | if isQuestionChar(value): static("OFFSET ?" & n) else: "OFFSET " & $value.intVal.Natural & n 70 | 71 | 72 | template limits(value: NimNode): string = 73 | isQuestionOrPositive(value) 74 | if isQuestionChar(value): static("LIMIT ?" & n) else: "LIMIT " & $value.intVal.Positive & n 75 | 76 | 77 | template froms(value: NimNode): string = 78 | isQuestionOrString(value) 79 | if isQuestionChar(value): static("FROM ?" & n) else: "FROM " & $value.strVal & n 80 | 81 | 82 | template wheres(value: NimNode): string = 83 | isQuestionOrString(value) 84 | if isQuestionChar(value): static("WHERE ?" & n) else: "WHERE " & $value.strVal & n 85 | 86 | 87 | template whereNots(value: NimNode): string = 88 | isQuestionOrString(value) 89 | if isQuestionChar(value): static("WHERE NOT ?" & n) else: "WHERE NOT " & $value.strVal & n 90 | 91 | 92 | template whereExists(value: NimNode): string = 93 | isQuestionOrString(value) 94 | if isQuestionChar(value): static("WHERE EXISTS ?" & n) else: "WHERE EXISTS " & $value.strVal & n 95 | 96 | 97 | template whereNotExists(value: NimNode): string = 98 | isQuestionOrString(value) 99 | if isQuestionChar(value): static("WHERE NOT EXISTS ?" & n) else: "WHERE NOT EXISTS " & $value.strVal & n 100 | 101 | 102 | template orderbys(value: NimNode): string = 103 | doAssert value.strVal.len > 0, "ORDER BY must not be empty string" 104 | "ORDER BY " & $value.strVal & n 105 | 106 | 107 | template selects(value: NimNode): string = 108 | isCharOrString(value) 109 | if isQuestionChar(value): static("SELECT ?" & n) 110 | elif value.kind == nnkCharLit: 111 | when not defined(release): {.warning: "SELECT * is bad practice https://stackoverflow.com/a/3639964".} 112 | "SELECT *" & n 113 | else: "SELECT " & $value.strVal & n 114 | 115 | 116 | template selectDistincts(value: NimNode): string = 117 | isCharOrString(value) 118 | if isQuestionChar(value): static("SELECT DISTINCT ?" & n) 119 | elif value.kind == nnkCharLit: 120 | when not defined(release): {.warning: "SELECT * is bad practice https://stackoverflow.com/a/3639964".} 121 | "SELECT DISTINCT *" & n 122 | else: "SELECT DISTINCT " & $value.strVal & n 123 | 124 | 125 | template selectTops(value: NimNode): string = 126 | isQuestionOrPositive(value) 127 | if isQuestionChar(value): static("SELECT TOP ? *" & n) else: "SELECT TOP " & $value.intVal & " *" & n 128 | 129 | 130 | template selectMins(value: NimNode): string = 131 | isCharOrString(value) 132 | if isQuestionChar(value): static("SELECT MIN(?)" & n) 133 | elif value.kind == nnkCharLit: 134 | when not defined(release): {.warning: "SELECT * is bad practice https://stackoverflow.com/a/3639964".} 135 | "SELECT MIN(*)" & n 136 | else: "SELECT MIN(" & $value.strVal & ")" & n 137 | 138 | 139 | template selectMaxs(value: NimNode): string = 140 | isCharOrString(value) 141 | if isQuestionChar(value): static("SELECT MAX(?)" & n) 142 | elif value.kind == nnkCharLit: 143 | when not defined(release): {.warning: "SELECT * is bad practice https://stackoverflow.com/a/3639964".} 144 | "SELECT MAX(*)" & n 145 | else: "SELECT MAX(" & $value.strVal & ")" & n 146 | 147 | 148 | template selectCounts(value: NimNode): string = 149 | isCharOrString(value) 150 | if isQuestionChar(value): static("SELECT COUNT(?)" & n) 151 | elif value.kind == nnkCharLit: 152 | when not defined(release): {.warning: "SELECT * is bad practice https://stackoverflow.com/a/3639964".} 153 | "SELECT COUNT(*)" & n 154 | else: "SELECT COUNT(" & $value.strVal & ")" & n 155 | 156 | 157 | template selectAvgs(value: NimNode): string = 158 | isCharOrString(value) 159 | if isQuestionChar(value): static("SELECT AVG(?)" & n) 160 | elif value.kind == nnkCharLit: 161 | when not defined(release): {.warning: "SELECT * is bad practice https://stackoverflow.com/a/3639964".} 162 | "SELECT AVG(*)" & n 163 | else: "SELECT AVG(" & $value.strVal & ")" & n 164 | 165 | 166 | template selectSums(value: NimNode): string = 167 | isCharOrString(value) 168 | if isQuestionChar(value): static("SELECT SUM(?)" & n) 169 | elif value.kind == nnkCharLit: 170 | when not defined(release): {.warning: "SELECT * is bad practice https://stackoverflow.com/a/3639964".} 171 | "SELECT SUM(*)" & n 172 | else: "SELECT SUM(" & $value.strVal & ")" & n 173 | 174 | 175 | template selectTrims(value: NimNode): string = 176 | isCharOrString(value) 177 | "SELECT trim(lower(" & $value.strVal & static("))" & n) 178 | 179 | 180 | template selectRound2(value: NimNode): string = 181 | isCharOrString(value) 182 | "SELECT round(" & $value.strVal & static(", 2)" & n) 183 | 184 | 185 | template selectRound4(value: NimNode): string = 186 | isCharOrString(value) 187 | "SELECT round(" & $value.strVal & static(", 4)" & n) 188 | 189 | 190 | template selectRound6(value: NimNode): string = 191 | isCharOrString(value) 192 | "SELECT round(" & $value.strVal & static(", 6)" & n) 193 | 194 | 195 | template deletes(value: NimNode): string = 196 | isQuestionOrString(value) 197 | if isQuestionChar(value): static("DELETE FROM ?" & n) else: "DELETE FROM " & $value.strVal & n 198 | 199 | 200 | template likes(value: NimNode): string = 201 | isQuestionOrString(value) 202 | if isQuestionChar(value): static("LIKE ?" & n) else: "LIKE " & $value.strVal & n 203 | 204 | 205 | template notlikes(value: NimNode): string = 206 | isQuestionOrString(value) 207 | if isQuestionChar(value): static("NOT LIKE ?" & n) else: "NOT LIKE " & $value.strVal & n 208 | 209 | 210 | template betweens(value: NimNode): string = 211 | isQuestionOrString(value) 212 | if isQuestionChar(value): static("BETWEEN ?" & n) else: "BETWEEN " & $value.strVal & n 213 | 214 | 215 | template notbetweens(value: NimNode): string = 216 | isQuestionOrString(value) 217 | if isQuestionChar(value): static("NOT BETWEEN ?" & n) else: "NOT BETWEEN " & $value.strVal & n 218 | 219 | 220 | template innerjoins(value: NimNode): string = 221 | isQuestionOrString(value) 222 | if isQuestionChar(value): static("INNER JOIN ?" & n) else: "INNER JOIN " & $value.strVal & n 223 | 224 | 225 | template leftjoins(value: NimNode): string = 226 | isQuestionOrString(value) 227 | if isQuestionChar(value): static("LEFT JOIN ?" & n) else: "LEFT JOIN " & $value.strVal & n 228 | 229 | 230 | template rightjoins(value: NimNode): string = 231 | isQuestionOrString(value) 232 | if isQuestionChar(value): static("RIGHT JOIN ?" & n) else: "RIGHT JOIN " & $value.strVal & n 233 | 234 | 235 | template fulljoins(value: NimNode): string = 236 | isQuestionOrString(value) 237 | if isQuestionChar(value): static("FULL OUTER JOIN ?" & n) else: "FULL OUTER JOIN " & $value.strVal & n 238 | 239 | 240 | template groupbys(value: NimNode): string = 241 | isQuestionOrString(value) 242 | if isQuestionChar(value): static("GROUP BY ?" & n) else: "GROUP BY " & $value.strVal & n 243 | 244 | 245 | template havings(value: NimNode): string = 246 | isQuestionOrString(value) 247 | if isQuestionChar(value): static("HAVING ?" & n) else: "HAVING " & $value.strVal & n 248 | 249 | 250 | template intos(value: NimNode): string = 251 | isQuestionOrString(value) 252 | if isQuestionChar(value): static("INTO ?" & n) else: "INTO " & $value.strVal & n 253 | 254 | 255 | template inserts(value: NimNode): string = 256 | isQuestionOrString(value) 257 | if isQuestionChar(value): static("INSERT INTO ?" & n) else: "INSERT INTO " & $value.strVal & n 258 | 259 | 260 | template isnulls(value: NimNode): string = 261 | doAssert value.kind == nnkIdent and parseBool($value) is bool, "IS NULL must be bool" 262 | if parseBool($value): static("IS NULL" & n) else: static("IS NOT NULL" & n) 263 | 264 | 265 | template unions(value: NimNode): string = 266 | doAssert value.kind == nnkIdent and parseBool($value), "UNION must be bool" 267 | if parseBool($value): static("UNION ALL" & n) else: static("UNION" & n) 268 | 269 | 270 | template intersects(value: NimNode): string = 271 | doAssert value.kind == nnkIdent and parseBool($value), "INTERSECT must be bool" 272 | if parseBool($value): static("INTERSECT ALL" & n) else: static("INTERSECT" & n) 273 | 274 | 275 | template excepts(value: NimNode): string = 276 | doAssert value.kind == nnkIdent and parseBool($value), "EXCEPT must be bool" 277 | if parseBool($value): static("EXCEPT ALL" & n) else: static("EXCEPT" & n) 278 | 279 | 280 | template updates(value: NimNode): string = 281 | isQuestionOrString(value) 282 | if isQuestionChar(value): static("UPDATE ?" & n) else: "UPDATE " & $value.strVal & n 283 | 284 | 285 | template resetAllGuards() = 286 | # Union can "Reset" select, from, where, etc to be re-used again on new query 287 | offsetUsed = false 288 | limitUsed = false 289 | fromUsed = false 290 | whereUsed = false 291 | orderUsed = false 292 | selectUsed = false 293 | deleteUsed = false 294 | likeUsed = false 295 | valuesUsed = false 296 | betweenUsed = false 297 | joinUsed = false 298 | groupbyUsed = false 299 | havingUsed = false 300 | intoUsed = false 301 | insertUsed = false 302 | isnullUsed = false 303 | updateUsed = false 304 | 305 | 306 | template values(value: Positive): string = 307 | # Produces "VALUES (?, ?, ?)", values passed via varargs. 308 | var temp = "VALUES ( " 309 | for i in 0 ..< value: temp.add "?, " 310 | temp[0..^3] & static(" )" & n) 311 | 312 | 313 | template sets(value: NimNode): string = 314 | # Produces "SET key = ?, key = ?, key = ?", values passed via varargs. 315 | isArrayStr(value) 316 | var temp = "SET " 317 | for item in value: temp.add item.strVal & " = ?, " 318 | temp[0..^3] & n 319 | 320 | 321 | template comments(value: NimNode, what: string): string = 322 | isTable(value) 323 | when defined(postgres): 324 | doAssert value.len == 1, "COMMENT wrong SQL syntax, must have exactly 1 key" 325 | var name, coment: string 326 | for tableValue in value: 327 | name = tableValue[0].strVal 328 | coment = tableValue[1].strVal 329 | doAssert name.len > 0, "COMMENT 'name' value must not be empty string" 330 | doAssert coment.len > 0, "COMMENT value must not be empty string" 331 | "COMMENT ON " & what & " " & name & " IS '" & coment & "'" & n 332 | else: n # SQLite wont support COMMENT, is not part of SQL Standard neither. 333 | 334 | 335 | template cases(value: NimNode): string = 336 | isTable(value) 337 | doAssert value[^1][0].strVal == "else", "CASE must have 1 'else' key, as last key, is required and mandatory" 338 | var defaultFound: byte 339 | var default, branches: string 340 | for tableValue in value: 341 | if tableValue[0].strVal == "else": 342 | default = " ELSE " & tableValue[1].strVal & n 343 | inc defaultFound 344 | else: 345 | branches.add " WHEN " & tableValue[0].strVal & " THEN " & tableValue[1].strVal & n 346 | doAssert defaultFound == 1, "CASE must have 1 'else' key" 347 | n & static("(CASE" & n) & branches & default & static("END)" & n) 348 | -------------------------------------------------------------------------------- /temp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juancarlospaco/nim-gatabase/3aaba860e3090504c77faa419e14c239dfd46045/temp.jpg -------------------------------------------------------------------------------- /tests/test.nim: -------------------------------------------------------------------------------- 1 | ## Gatabase Unittests. 2 | import unittest, db_sqlite 3 | import ../src/gatabase # Import LOCAL Gatabase 4 | include ../src/gatabase/sugar 5 | 6 | 7 | const exampleTable = sql""" 8 | create table if not exists person( 9 | id integer primary key, 10 | name varchar(99) not null unique, 11 | active bool not null default true, 12 | email text not null, 13 | rank float not null default 0.0 14 | ); """ 15 | 16 | 17 | const expected0 = """INSERT INTO person 18 | VALUES ( ?, ?, ?, ?, ? ) 19 | ; 20 | """ 21 | 22 | const expected1 = """SELECT * 23 | FROM person 24 | WHERE id = 42 25 | ; 26 | """ 27 | 28 | const expected2 = """SELECT * 29 | /* This is a comment, this will be strapped for Release builds */ 30 | FROM person 31 | 32 | ; 33 | """ 34 | 35 | const expected3 = """SELECT * 36 | FROM person 37 | LIMIT 2 38 | OFFSET 0 39 | ; 40 | """ 41 | 42 | const expected4 = """INSERT INTO person 43 | VALUES ( ?, ?, ?, ?, ? ) 44 | ; 45 | """ 46 | 47 | const expected5 = """SELECT * 48 | FROM person 49 | WHERE id = 42 50 | UNION ALL 51 | SELECT * 52 | FROM person 53 | WHERE name 54 | IS NOT NULL 55 | ; 56 | """ 57 | 58 | const expected6 = """SELECT DISTINCT id 59 | FROM person 60 | WHERE rank != 666.0 61 | ; 62 | """ 63 | 64 | const expected7 = """INSERT INTO person 65 | VALUES ( ?, ?, ?, ?, ? ) 66 | ; 67 | """ 68 | 69 | const expected8 = """SELECT * 70 | FROM person 71 | WHERE id = 42 72 | ; 73 | """ 74 | 75 | 76 | suite "Gatabase ORM Tests": 77 | 78 | let db = db_sqlite.open(":memory:", "", "", "") # Setup. 79 | doAssert db.tryExec(exampleTable), "Error creating 'exampleTable'" 80 | 81 | 82 | test "let INSERT INTO": 83 | let result0 = sqls: 84 | insertinto "person" 85 | values 5 86 | check result0.string == expected0 87 | 88 | 89 | test "let SELECT ... FROM ... WHERE": 90 | let result1 = sqls: 91 | select '*' 92 | `from` "person" 93 | where "id = 42" 94 | check result1.string == expected1 95 | 96 | 97 | test "let SELECT ... (comment) ... FROM ... COMMENT": 98 | let result2 = sqls: 99 | select '*' 100 | `--` "This is a comment, this will be strapped for Release builds" 101 | `from` "person" 102 | commentontable {"person": "This is an SQL COMMENT on a TABLE"} 103 | check result2.string == expected2 104 | 105 | 106 | test "let SELECT ... FROM ... LIMIT ... OFFSET": 107 | let result3 = sqls: 108 | select '*' 109 | `from` "person" 110 | limit 2 111 | offset 0 112 | check result3.string == expected3 113 | 114 | 115 | test "let INSERT INTO": 116 | let result4 = sqls: 117 | insertinto "person" 118 | values 5 119 | check result4.string == expected4 120 | 121 | 122 | test "let UNION ALL ... ORBER BY ... IS NOT NULL": 123 | let result5 = sqls: 124 | select '*' 125 | `from` "person" 126 | where "id = 42" 127 | union true 128 | select '*' 129 | `from` "person" 130 | where "name" 131 | isnull false 132 | check result5.string == expected5 133 | 134 | 135 | test "let SELECT DISTINCT ... FROM ... WHERE": 136 | let result6 = sqls: 137 | selectdistinct "id" 138 | `from`"person" 139 | where "rank != 666.0" 140 | check result6.string == expected6 141 | 142 | 143 | test "let INSERT INTO": 144 | let result7 = sqls: 145 | insertinto "person" 146 | values 5 147 | check result7.string == expected7 148 | 149 | 150 | # test "const SELECT ... FROM ... WHERE": 151 | # const result8 = [].sqls: 152 | # select '*' 153 | # `from` "person" 154 | # where "id = 42" 155 | # check result8.string == expected8 156 | 157 | 158 | test "const SELECT ... (comment) ... FROM ... COMMENT": 159 | const example10 {.used.} = sqls: 160 | select '*' 161 | `--` "This is a comment, this will be strapped for Release builds" 162 | `from` "person" 163 | commentontable {"person": "This is an SQL COMMENT on a TABLE"} 164 | 165 | 166 | test "const SELECT ... FROM ... LIMIT ... OFFSET": 167 | const example11 {.used.} = sqls: 168 | select '*' 169 | `from` "person" 170 | limit 2 171 | offset 0 172 | 173 | 174 | test "const INSERT INTO": 175 | const example12 {.used.} = sqls: 176 | insertinto "person" 177 | values 5 178 | 179 | 180 | test "const UNION ALL ... ORBER BY ... IS NOT NULL": 181 | const example13 {.used.} = sqls: 182 | select '*' 183 | `from` "person" 184 | where "id = 42" 185 | union true 186 | select '*' 187 | `from` "person" 188 | where "name" 189 | isnull false 190 | orderby "id" 191 | 192 | 193 | test "const INTERSECT ALL": 194 | const example13a {.used.} = sqls: 195 | select '*' 196 | `from` "person" 197 | intersect true 198 | select '*' 199 | `from` "person" 200 | 201 | 202 | test "const EXCEPT ALL": 203 | const example13b {.used.} = sqls: 204 | select '*' 205 | `from` "person" 206 | `except` true 207 | select '*' 208 | `from` "person" 209 | 210 | 211 | test "const SELECT DISTINCT ... FROM ... WHERE": 212 | const example14 {.used.} = sqls: 213 | selectdistinct "id" 214 | `from` "person" 215 | where "rank != 666.0" 216 | 217 | 218 | test "var CASE": 219 | var example15 {.used.} = sqls: 220 | `case` {"foo > 10": "9", "bar < 42": "5", "else": "0"} 221 | `case` { 222 | "foo > 10": "9", 223 | "bar < 42": "5", 224 | "else": "0" 225 | } 226 | 227 | 228 | test "var SELECT MAX .. WHERE EXISTS ... OFFSET ... LIMIT ... ORDER BY": 229 | var foo {.used.} = [].tryExec: 230 | selectmax '*' 231 | `--` "This is a comment." 232 | `from` "person" 233 | `--` "This is a comment." 234 | whereexists "rank > 0.0" 235 | `--` "This is a comment." 236 | `--` "This is a comment." 237 | limit 1 238 | offset 0 239 | `--` "This is a comment." 240 | orderby "desc" 241 | 242 | 243 | test "SELECT TRIM": 244 | exec []: 245 | selecttrim "name" 246 | `from` "person" 247 | 248 | 249 | test "SELECT ROUND": 250 | exec []: 251 | selectround2 "rank" 252 | `from` "person" 253 | 254 | exec []: 255 | selectround4 "rank" 256 | `from` "person" 257 | 258 | exec []: 259 | selectround6 "rank" 260 | `from` "person" 261 | 262 | 263 | test "var DELETE FROM WHERE": 264 | exec []: 265 | delete "person" 266 | 267 | 268 | test "dropTable": 269 | doAssert db.dropTable("person") 270 | 271 | 272 | test "createTable": 273 | let myTable = createTable "kitten": [ 274 | "age" := 1, 275 | "sex" := 'f', 276 | "name" := "fluffy", 277 | "rank" := 3.14, 278 | ] 279 | echo myTable.string 280 | 281 | 282 | close db # TearDown. 283 | -------------------------------------------------------------------------------- /tests/test_js.nim: -------------------------------------------------------------------------------- 1 | ## Gatabase Unittests. 2 | import unittest, db_common, ../src/gatabase # Import LOCAL Gatabase 3 | 4 | 5 | const exampleTable = sql""" 6 | create table if not exists person( 7 | id integer primary key, 8 | name varchar(99) not null unique, 9 | active bool not null default true, 10 | email text not null, 11 | rank float not null default 0.0 12 | ); """ 13 | 14 | 15 | const expected0 = """INSERT INTO person 16 | VALUES ( ?, ?, ?, ?, ? ) 17 | ; 18 | """ 19 | 20 | const expected1 = """SELECT * 21 | FROM person 22 | WHERE id = 42 23 | ; 24 | """ 25 | 26 | const expected2 = """SELECT * 27 | /* This is a comment, this will be strapped for Release builds */ 28 | FROM person 29 | 30 | ; 31 | """ 32 | 33 | const expected3 = """SELECT * 34 | FROM person 35 | LIMIT 2 36 | OFFSET 0 37 | ; 38 | """ 39 | 40 | const expected4 = """INSERT INTO person 41 | VALUES ( ?, ?, ?, ?, ? ) 42 | ; 43 | """ 44 | 45 | const expected5 = """SELECT * 46 | FROM person 47 | WHERE id = 42 48 | UNION ALL 49 | SELECT * 50 | FROM person 51 | WHERE name 52 | IS NOT NULL 53 | ; 54 | """ 55 | 56 | const expected6 = """SELECT DISTINCT id 57 | FROM person 58 | WHERE rank != 666.0 59 | ; 60 | """ 61 | 62 | const expected7 = """INSERT INTO person 63 | VALUES ( ?, ?, ?, ?, ? ) 64 | ; 65 | """ 66 | 67 | const expected8 = """SELECT * 68 | FROM person 69 | WHERE id = 42 70 | ; 71 | """ 72 | 73 | 74 | suite "Gatabase ORM Tests": 75 | 76 | test "let INSERT INTO": 77 | let result0 = sqls: 78 | insertinto "person" 79 | values 5 80 | check result0.string == expected0 81 | 82 | 83 | test "let SELECT ... FROM ... WHERE": 84 | let result1 = sqls: 85 | select '*' 86 | `from` "person" 87 | where "id = 42" 88 | check result1.string == expected1 89 | 90 | 91 | test "let SELECT ... (comment) ... FROM ... COMMENT": 92 | let result2 = sqls: 93 | select '*' 94 | `--` "This is a comment, this will be strapped for Release builds" 95 | `from` "person" 96 | commentontable {"person": "This is an SQL COMMENT on a TABLE"} 97 | check result2.string == expected2 98 | 99 | 100 | test "let SELECT ... FROM ... LIMIT ... OFFSET": 101 | let result3 = sqls: 102 | select '*' 103 | `from` "person" 104 | limit 2 105 | offset 0 106 | check result3.string == expected3 107 | 108 | 109 | test "let INSERT INTO": 110 | let result4 = sqls: 111 | insertinto "person" 112 | values 5 113 | check result4.string == expected4 114 | 115 | 116 | test "let UNION ALL ... ORBER BY ... IS NOT NULL": 117 | let result5 = sqls: 118 | select '*' 119 | `from` "person" 120 | where "id = 42" 121 | union true 122 | select '*' 123 | `from` "person" 124 | where "name" 125 | isnull false 126 | check result5.string == expected5 127 | 128 | 129 | test "let SELECT DISTINCT ... FROM ... WHERE": 130 | let result6 = sqls: 131 | selectdistinct "id" 132 | `from`"person" 133 | where "rank != 666.0" 134 | check result6.string == expected6 135 | 136 | 137 | test "let INSERT INTO": 138 | let result7 = sqls: 139 | insertinto "person" 140 | values 5 141 | check result7.string == expected7 142 | 143 | 144 | test "const SELECT ... (comment) ... FROM ... COMMENT": 145 | const example10 {.used.} = sqls: 146 | select '*' 147 | `--` "This is a comment, this will be strapped for Release builds" 148 | `from` "person" 149 | commentontable {"person": "This is an SQL COMMENT on a TABLE"} 150 | 151 | 152 | test "const SELECT ... FROM ... LIMIT ... OFFSET": 153 | const example11 {.used.} = sqls: 154 | select '*' 155 | `from` "person" 156 | limit 2 157 | offset 0 158 | 159 | 160 | test "const INSERT INTO": 161 | const example12 {.used.} = sqls: 162 | insertinto "person" 163 | values 5 164 | 165 | 166 | test "const UNION ALL ... ORBER BY ... IS NOT NULL": 167 | const example13 {.used.} = sqls: 168 | select '*' 169 | `from` "person" 170 | where "id = 42" 171 | union true 172 | select '*' 173 | `from` "person" 174 | where "name" 175 | isnull false 176 | orderby "id" 177 | 178 | 179 | test "const INTERSECT ALL": 180 | const example13a {.used.} = sqls: 181 | select '*' 182 | `from` "person" 183 | intersect true 184 | select '*' 185 | `from` "person" 186 | 187 | 188 | test "const EXCEPT ALL": 189 | const example13b {.used.} = sqls: 190 | select '*' 191 | `from` "person" 192 | `except` true 193 | select '*' 194 | `from` "person" 195 | 196 | 197 | test "const SELECT DISTINCT ... FROM ... WHERE": 198 | const example14 {.used.} = sqls: 199 | selectdistinct "id" 200 | `from` "person" 201 | where "rank != 666.0" 202 | 203 | 204 | test "var CASE": 205 | var example15 {.used.} = sqls: 206 | `case` {"foo > 10": "9", "bar < 42": "5", "else": "0"} 207 | `case` { 208 | "foo > 10": "9", 209 | "bar < 42": "5", 210 | "else": "0" 211 | } 212 | -------------------------------------------------------------------------------- /tests/test_multigata.nim: -------------------------------------------------------------------------------- 1 | ## Gatabase Unittests. 2 | import unittest, asyncdispatch, db_common, ../src/gatabase # Import LOCAL Gatabase 3 | 4 | 5 | let db = newGatabase("localhost", "postgres", "postgres", "postgres", unroll = 9) 6 | doAssert db is Gatabase 7 | let data = wait_for getAllRows(db, query = sql"SELECT version();", @[]) 8 | doAssert data is seq[Row] 9 | doAssert len(data) > 0 and len(data[0]) > 0 10 | echo data[0] # Postgres 12 11 | doAssert execAffectedRows(db, query = sql"SELECT version();", @[]) is Future[int64] 12 | doAssert exec(db, query = sql"SELECT version();", @[]) is Future[void] 13 | for _ in 0 .. len(db) - 1: doAssert db.getAllRows(sql"SELECT version();", @[]) is Future[seq[Row]] 14 | 15 | 16 | var args: seq[string] # Just for testing, can also be @[] 17 | 18 | let dataset0: Future[seq[Row]] = args.getAllRows: 19 | select "version()" 20 | 21 | let dataset1: Future[int64] = args.execAffectedRows: 22 | select "version()" 23 | 24 | # asyncCheck exec args: 25 | # select "version()" 26 | # `--` "You can await() them too, this is just an example." 27 | 28 | 29 | echo $db 30 | db.close(unroll = 9) 31 | --------------------------------------------------------------------------------