├── .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 | 
4 |
5 | 
6 | 
7 | 
8 | 
9 | 
10 | 
11 | 
12 | 
13 | 
14 |
15 | 
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 | 
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 | 
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 | 
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 | 
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 |
79 |
80 |
81 |
82 |
Dark Mode
83 |
84 |
85 |
86 |
87 | Index
88 |
89 |
90 |
91 |
92 | Search:
94 |
95 |
96 | Group by:
97 |
98 | Section
99 | Type
100 |
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 |
149 |
150 |
151 |
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 |
319 |
320 |
321 | Made with Nim. Generated: 2020-06-30 03:06:52 UTC
322 |
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 |
70 |
71 |
72 |
73 |
Dark Mode
74 |
75 |
76 |
77 |
78 | Index
79 |
80 |
81 |
82 |
83 | Search:
85 |
86 |
87 | Group by:
88 |
89 | Section
90 | Type
91 |
92 |
93 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
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 |
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 ) :
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 |
399 |
400 |
401 | Made with Nim. Generated: 2020-11-06 18:17:49 UTC
402 |
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 |
--------------------------------------------------------------------------------