diff options
| -rw-r--r-- | README.md | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..658498e --- /dev/null +++ b/README.md | |||
| @@ -0,0 +1,122 @@ | |||
| 1 | # zig-sqlite | ||
| 2 | |||
| 3 | This package is a thin wrapper around [sqlite](https://sqlite.org/index.html)'s C API. | ||
| 4 | |||
| 5 | ## Requirements | ||
| 6 | |||
| 7 | * Linux | ||
| 8 | * the system and development package for sqlite | ||
| 9 | * `libsqlite3-dev` for Debian and derivatives | ||
| 10 | * `sqlite3-devel` for Fedora | ||
| 11 | |||
| 12 | ## Installation | ||
| 13 | |||
| 14 | Since there's no package manager for Zig yet, the recommended way is to use a git submodule: | ||
| 15 | |||
| 16 | $ git submodule add https://git.sr.ht/~vrischmann/zig-sqlite src/sqlite | ||
| 17 | |||
| 18 | Then add the following to your `build.zig` target(s): | ||
| 19 | |||
| 20 | exe.linkLibC(); | ||
| 21 | exe.linkSystemLibrary("sqlite3"); | ||
| 22 | exe.addPackage(.{ .name = "sqlite", .path = "src/sqlite/sqlite.zig" }); | ||
| 23 | |||
| 24 | Now you should be able to import sqlite like this: | ||
| 25 | |||
| 26 | const sqlite = @import("sqlite"); | ||
| 27 | |||
| 28 | ## Usage | ||
| 29 | |||
| 30 | ### Initialization | ||
| 31 | |||
| 32 | You must create and initialize an instance of `sqlite.Db`: | ||
| 33 | |||
| 34 | var db: sqlite.Db = undefined; | ||
| 35 | try db.init(allocator, .{ .mode = sqlite.Db.Mode{ .File = "/home/vincent/mydata.db" } }); | ||
| 36 | |||
| 37 | The `init` method takes an allocator and an optional tuple which will used to configure sqlite. | ||
| 38 | |||
| 39 | Right now the only member used in that tuple is `mode` which defines if the sqlite database is in memory or uses a file. | ||
| 40 | |||
| 41 | ### Preparing a statement | ||
| 42 | |||
| 43 | sqlite works exclusively by using prepared statements. The wrapper type is `sqlite.Statement`. Here is how you get one: | ||
| 44 | |||
| 45 | const query = | ||
| 46 | \\SELECT id, name, age, salary FROM employees WHERE age > ? AND age < ? | ||
| 47 | ; | ||
| 48 | |||
| 49 | var stmt = try db.prepare(query, .{ | ||
| 50 | .age1 = 20, | ||
| 51 | .age2 = 40, | ||
| 52 | }); | ||
| 53 | defer stmt.deinit(); | ||
| 54 | |||
| 55 | The `Db.prepare` method takes a `comptime` query string and a tuple of bind parameters. | ||
| 56 | |||
| 57 | The number of bind parameters is comptime checked against the number of bind markers in the query string, so if you have 2 bind markers | ||
| 58 | you must provide 2 bind parameters. | ||
| 59 | |||
| 60 | Note that the fields name is irrelevant, only their order is relevant. | ||
| 61 | |||
| 62 | See the section "Bind parameters and resultset rows" for more information on the types mapping rules. | ||
| 63 | |||
| 64 | ### Executing a statement | ||
| 65 | |||
| 66 | For queries which do not return data (`INSERT`, `UPDATE`) you can use the `exec` method: | ||
| 67 | |||
| 68 | const query = | ||
| 69 | \\UPDATE foo SET salary = ? WHERE id = ? | ||
| 70 | ; | ||
| 71 | |||
| 72 | var stmt = try db.prepare(query, .{ | ||
| 73 | .salary = 20000, | ||
| 74 | .id = 40, | ||
| 75 | }); | ||
| 76 | defer stmt.deinit(); | ||
| 77 | |||
| 78 | try stmt.exec(); | ||
| 79 | |||
| 80 | ### Reading data | ||
| 81 | |||
| 82 | For queries which do return data you can use the `all` method: | ||
| 83 | |||
| 84 | const query = | ||
| 85 | \\SELECT id, name, age, salary FROM employees WHERE age > ? AND age < ? | ||
| 86 | ; | ||
| 87 | |||
| 88 | var stmt = try db.prepare(query, .{ | ||
| 89 | .age1 = 20, | ||
| 90 | .age2 = 40, | ||
| 91 | }); | ||
| 92 | defer stmt.deinit(); | ||
| 93 | |||
| 94 | const rows = try stmt.all( | ||
| 95 | struct { | ||
| 96 | id: usize, | ||
| 97 | name: []const u8, | ||
| 98 | age: u16, | ||
| 99 | salary: u32, | ||
| 100 | }, | ||
| 101 | .{ .allocator = allocator }, | ||
| 102 | ); | ||
| 103 | for (rows) |row| { | ||
| 104 | std.log.debug("id: {} ; name: {}; age: {}; salary: {}", .{ row.id, row.name, row.age, row.salary }); | ||
| 105 | } | ||
| 106 | |||
| 107 | The `all` method takes a type and an optional tuple. | ||
| 108 | |||
| 109 | The type represents a "row", it can be: | ||
| 110 | * a struct where each field maps to the corresponding column in the resultset (so field 0 must map to field 1 and so on). | ||
| 111 | * a single type, in that case the resultset must only return one column. | ||
| 112 | |||
| 113 | Not all types are allowed, see the section "Bind parameters and resultset rows" for more information on the types mapping rules. | ||
| 114 | |||
| 115 | ### Bind parameters and resultset rows | ||
| 116 | |||
| 117 | Since sqlite doesn't have many [types](https://www.sqlite.org/datatype3.html) only a small number of Zig types are allowed in binding parameters and in resultset mapping types. | ||
| 118 | |||
| 119 | Here are the rules for bind parameters: | ||
| 120 | * any Zig `Int` or `ComptimeInt` is tread as a `INTEGER`. | ||
| 121 | * any Zig `Float` or `ComptimeFloat` is treated as a `REAL`. | ||
| 122 | * `[]const u8`, `[]u8` or any array of `u8` is treated as a `TEXT` or `BLOB`. | ||