summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md10
-rw-r--r--sqlite.zig44
2 files changed, 50 insertions, 4 deletions
diff --git a/README.md b/README.md
index 658498e..7937862 100644
--- a/README.md
+++ b/README.md
@@ -119,4 +119,12 @@ Since sqlite doesn't have many [types](https://www.sqlite.org/datatype3.html) on
119Here are the rules for bind parameters: 119Here are the rules for bind parameters:
120* any Zig `Int` or `ComptimeInt` is tread as a `INTEGER`. 120* any Zig `Int` or `ComptimeInt` is tread as a `INTEGER`.
121* any Zig `Float` or `ComptimeFloat` is treated as a `REAL`. 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`. 122* `[]const u8`, `[]u8` or any array of `u8` is treated as a `TEXT`.
123* The custom `sqlite.Bytes` type is treated as a `TEXT` or `BLOB`.
124
125Here are the resules for resultset rows:
126* `INTEGER` can be read into any Zig `Int` provided the data fits.
127* `REAL` can be read into any Zig `Float` provided the data fits.
128* `TEXT` can be read into a `[]const u8` or `[]u8`.
129* `TEXT` can be read into any array of `u8` provided the data fits.
130* `BLOB` follows the same rules as `TEXT`.
diff --git a/sqlite.zig b/sqlite.zig
index 46f2e7e..116807a 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -117,6 +117,17 @@ pub const Db = struct {
117 } 117 }
118}; 118};
119 119
120/// Bytes is used to represent a byte slice with its SQLite datatype.
121///
122/// Since Zig doesn't have strings we can't tell if a []u8 must be stored as a SQLite TEXT or BLOB,
123/// this type can be used to communicate this when executing a statement.
124///
125/// If a []u8 or []const u8 is passed as bind parameter it will be treated as TEXT.
126pub const Bytes = union(enum) {
127 Blob: []const u8,
128 Text: []const u8,
129};
130
120/// Statement is a wrapper around a SQLite statement, providing high-level functions to execute 131/// Statement is a wrapper around a SQLite statement, providing high-level functions to execute
121/// a statement and retrieve rows for SELECT queries. 132/// a statement and retrieve rows for SELECT queries.
122/// 133///
@@ -153,11 +164,17 @@ pub const Statement = struct {
153 164
154 stmt: *c.sqlite3_stmt, 165 stmt: *c.sqlite3_stmt,
155 166
167 const BytesType = enum {
168 Text,
169 Blob,
170 };
171
156 fn prepare(db: *Db, flags: c_uint, comptime query: []const u8, values: anytype) !Self { 172 fn prepare(db: *Db, flags: c_uint, comptime query: []const u8, values: anytype) !Self {
157 const StructType = @typeInfo(@TypeOf(values)).Struct; 173 const StructType = @TypeOf(values);
174 const StructTypeInfo = @typeInfo(StructType).Struct;
158 comptime { 175 comptime {
159 const bind_parameter_count = std.mem.count(u8, query, "?"); 176 const bind_parameter_count = std.mem.count(u8, query, "?");
160 if (bind_parameter_count != StructType.fields.len) { 177 if (bind_parameter_count != StructTypeInfo.fields.len) {
161 @compileError("bind parameter count != number of fields in tuple/struct"); 178 @compileError("bind parameter count != number of fields in tuple/struct");
162 } 179 }
163 } 180 }
@@ -183,7 +200,7 @@ pub const Statement = struct {
183 200
184 // Bind 201 // Bind
185 202
186 inline for (StructType.fields) |struct_field, _i| { 203 inline for (StructTypeInfo.fields) |struct_field, _i| {
187 const i = @as(usize, _i); 204 const i = @as(usize, _i);
188 const field_type_info = @typeInfo(struct_field.field_type); 205 const field_type_info = @typeInfo(struct_field.field_type);
189 const field_value = @field(values, struct_field.name); 206 const field_value = @field(values, struct_field.name);
@@ -193,6 +210,10 @@ pub const Statement = struct {
193 []const u8, []u8 => { 210 []const u8, []u8 => {
194 _ = c.sqlite3_bind_text(stmt, column, field_value.ptr, @intCast(c_int, field_value.len), null); 211 _ = c.sqlite3_bind_text(stmt, column, field_value.ptr, @intCast(c_int, field_value.len), null);
195 }, 212 },
213 Bytes => switch (field_value) {
214 .Text => |v| _ = c.sqlite3_bind_text(stmt, column, v.ptr, @intCast(c_int, v.len), null),
215 .Blob => |v| _ = c.sqlite3_bind_blob(stmt, column, v.ptr, @intCast(c_int, v.len), null),
216 },
196 else => switch (field_type_info) { 217 else => switch (field_type_info) {
197 .Int, .ComptimeInt => _ = c.sqlite3_bind_int64(stmt, column, @intCast(c_longlong, field_value)), 218 .Int, .ComptimeInt => _ = c.sqlite3_bind_int64(stmt, column, @intCast(c_longlong, field_value)),
198 .Float, .ComptimeFloat => _ = c.sqlite3_bind_double(stmt, column, field_value), 219 .Float, .ComptimeFloat => _ = c.sqlite3_bind_double(stmt, column, field_value),
@@ -512,6 +533,23 @@ test "sqlite: statement exec" {
512 533
513 testing.expectEqual(@as(usize, 33), age.?); 534 testing.expectEqual(@as(usize, 33), age.?);
514 } 535 }
536
537 // Test with a Bytes struct
538
539 {
540 // NOTE(vincent): can't yet pass an anonymous struct, the compiler crashes.
541 const Params = struct {
542 id: usize,
543 name: Bytes,
544 age: usize,
545 };
546
547 try db.exec("INSERT INTO user(id, name, age) VALUES(?, ?, ?)", Params{
548 .id = 200,
549 .name = .{ .Text = "hello" },
550 .age = 20,
551 });
552 }
515} 553}
516 554
517fn dbMode() Db.Mode { 555fn dbMode() Db.Mode {