summaryrefslogtreecommitdiff
path: root/sqlite.zig
diff options
context:
space:
mode:
authorGravatar Vincent Rischmann2020-12-26 23:36:44 +0100
committerGravatar Vincent Rischmann2021-01-31 03:45:31 +0100
commit86207067bae4b52ff8e9914783b960f83e956e3a (patch)
tree34c59155ca377add92ea97f3e3e8f15a6810adc4 /sqlite.zig
parentadd the Db.getLastInsertRowID method (diff)
downloadzig-sqlite-86207067bae4b52ff8e9914783b960f83e956e3a.tar.gz
zig-sqlite-86207067bae4b52ff8e9914783b960f83e956e3a.tar.xz
zig-sqlite-86207067bae4b52ff8e9914783b960f83e956e3a.zip
add incremental i/o on blob
Diffstat (limited to 'sqlite.zig')
-rw-r--r--sqlite.zig204
1 files changed, 204 insertions, 0 deletions
diff --git a/sqlite.zig b/sqlite.zig
index b911e0e..82190ba 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -1,6 +1,7 @@
1const std = @import("std"); 1const std = @import("std");
2const build_options = @import("build_options"); 2const build_options = @import("build_options");
3const debug = std.debug; 3const debug = std.debug;
4const io = std.io;
4const mem = std.mem; 5const mem = std.mem;
5const testing = std.testing; 6const testing = std.testing;
6 7
@@ -13,6 +14,153 @@ usingnamespace @import("error.zig");
13 14
14const logger = std.log.scoped(.sqlite); 15const logger = std.log.scoped(.sqlite);
15 16
17pub const ZeroBlob = struct {
18 length: usize,
19};
20
21/// Blob is a wrapper for a sqlite BLOB.
22///
23/// This type is useful when reading or binding data and for doing incremental i/o.
24pub const Blob = struct {
25 const Self = @This();
26
27 pub const OpenFlags = struct {
28 read: bool = true,
29 write: bool = false,
30 };
31
32 pub const DatabaseName = union(enum) {
33 main,
34 temp,
35 attached: [:0]const u8,
36
37 fn toString(self: @This()) [:0]const u8 {
38 return switch (self) {
39 .main => "main",
40 .temp => "temp",
41 .attached => |name| name,
42 };
43 }
44 };
45
46 // Used when reading or binding data.
47 data: []const u8,
48
49 // Used for incremental i/o.
50 handle: *c.sqlite3_blob = undefined,
51 offset: c_int = 0,
52 size: c_int = 0,
53
54 /// close closes the blob.
55 pub fn close(self: *Self) !void {
56 const result = c.sqlite3_blob_close(self.handle);
57 if (result != c.SQLITE_OK) {
58 return errorFromResultCode(result);
59 }
60 }
61
62 pub const Reader = io.Reader(*Self, Error, read);
63
64 /// reader returns a io.Reader.
65 pub fn reader(self: *Self) Reader {
66 return .{ .context = self };
67 }
68
69 fn read(self: *Self, buffer: []u8) Error!usize {
70 if (self.offset >= self.size) {
71 return 0;
72 }
73
74 var tmp_buffer = blk: {
75 const remaining = @intCast(usize, self.size) - @intCast(usize, self.offset);
76 break :blk if (buffer.len > remaining) buffer[0..remaining] else buffer;
77 };
78
79 const result = c.sqlite3_blob_read(
80 self.handle,
81 tmp_buffer.ptr,
82 @intCast(c_int, tmp_buffer.len),
83 self.offset,
84 );
85 if (result != c.SQLITE_OK) {
86 return errorFromResultCode(result);
87 }
88
89 self.offset += @intCast(c_int, tmp_buffer.len);
90
91 return tmp_buffer.len;
92 }
93
94 pub const Writer = io.Writer(*Self, Error, write);
95
96 /// writer returns a io.Writer.
97 pub fn writer(self: *Self) Writer {
98 return .{ .context = self };
99 }
100
101 fn write(self: *Self, data: []const u8) Error!usize {
102 const result = c.sqlite3_blob_write(
103 self.handle,
104 data.ptr,
105 @intCast(c_int, data.len),
106 self.offset,
107 );
108 if (result != c.SQLITE_OK) {
109 return errorFromResultCode(result);
110 }
111
112 self.offset += @intCast(c_int, data.len);
113
114 return data.len;
115 }
116
117 /// open opens a blob for incremental i/o.
118 ///
119 /// You can get a std.io.Writer to write data to the blob:
120 ///
121 /// var blob = try db.openBlob(.main, "mytable", "mycolumn", 1, .{ .write = true });
122 /// var blob_writer = blob.writer();
123 ///
124 /// try blob_writer.writeAll(my_data);
125 ///
126 /// Note that a blob is not extensible, if you want to change the blob size you must use an UPDATE statement.
127 ///
128 /// You can get a std.io.Reader to read the blob data:
129 ///
130 /// var blob = try db.openBlob(.main, "mytable", "mycolumn", 1, .{});
131 /// var blob_reader = blob.reader();
132 ///
133 /// const data = try blob_reader.readAlloc(allocator);
134 ///
135 fn open(db: *c.sqlite3, db_name: DatabaseName, table: [:0]const u8, column: [:0]const u8, row: i64, comptime flags: OpenFlags) !Blob {
136 comptime if (!flags.read and !flags.write) {
137 @compileError("must open a blob for either read, write or both");
138 };
139
140 const open_flags: c_int = if (flags.write) 1 else 0;
141
142 var blob: Blob = undefined;
143 const result = c.sqlite3_blob_open(
144 db,
145 db_name.toString(),
146 table,
147 column,
148 row,
149 open_flags,
150 @ptrCast([*c]?*c.sqlite3_blob, &blob.handle),
151 );
152 if (result == c.SQLITE_MISUSE) debug.panic("sqlite misuse while opening a blob", .{});
153 if (result != c.SQLITE_OK) {
154 return error.CannotOpenBlob;
155 }
156
157 blob.size = c.sqlite3_blob_bytes(blob.handle);
158 blob.offset = 0;
159
160 return blob;
161 }
162};
163
16/// ThreadingMode controls the threading mode used by SQLite. 164/// ThreadingMode controls the threading mode used by SQLite.
17/// 165///
18/// See https://sqlite.org/threadsafe.html 166/// See https://sqlite.org/threadsafe.html
@@ -255,6 +403,11 @@ pub const Db = struct {
255 pub fn rowsAffected(self: *Self) usize { 403 pub fn rowsAffected(self: *Self) usize {
256 return @intCast(usize, c.sqlite3_changes(self.db)); 404 return @intCast(usize, c.sqlite3_changes(self.db));
257 } 405 }
406
407 /// openBlob opens a blob.
408 pub fn openBlob(self: *Self, db_name: Blob.DatabaseName, table: [:0]const u8, column: [:0]const u8, row: i64, comptime flags: Blob.OpenFlags) !Blob {
409 return Blob.open(self.db, db_name, table, column, row, flags);
410 }
258}; 411};
259 412
260/// Iterator allows iterating over a result set. 413/// Iterator allows iterating over a result set.
@@ -764,6 +917,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
764 switch (FieldType) { 917 switch (FieldType) {
765 Text => _ = c.sqlite3_bind_text(self.stmt, column, field.data.ptr, @intCast(c_int, field.data.len), null), 918 Text => _ = c.sqlite3_bind_text(self.stmt, column, field.data.ptr, @intCast(c_int, field.data.len), null),
766 Blob => _ = c.sqlite3_bind_blob(self.stmt, column, field.data.ptr, @intCast(c_int, field.data.len), null), 919 Blob => _ = c.sqlite3_bind_blob(self.stmt, column, field.data.ptr, @intCast(c_int, field.data.len), null),
920 ZeroBlob => _ = c.sqlite3_bind_zeroblob64(self.stmt, column, field.length),
767 else => switch (field_type_info) { 921 else => switch (field_type_info) {
768 .Int, .ComptimeInt => _ = c.sqlite3_bind_int64(self.stmt, column, @intCast(c_longlong, field)), 922 .Int, .ComptimeInt => _ = c.sqlite3_bind_int64(self.stmt, column, @intCast(c_longlong, field)),
769 .Float, .ComptimeFloat => _ = c.sqlite3_bind_double(self.stmt, column, field), 923 .Float, .ComptimeFloat => _ = c.sqlite3_bind_double(self.stmt, column, field),
@@ -949,6 +1103,9 @@ const test_users = &[_]TestUser{
949 1103
950fn createTestTables(db: *Db) !void { 1104fn createTestTables(db: *Db) !void {
951 const AllDDL = &[_][]const u8{ 1105 const AllDDL = &[_][]const u8{
1106 "DROP TABLE IF EXISTS user",
1107 "DROP TABLE IF EXISTS article",
1108 "DROP TABLE IF EXISTS test_blob",
952 \\CREATE TABLE user( 1109 \\CREATE TABLE user(
953 \\ id integer PRIMARY KEY, 1110 \\ id integer PRIMARY KEY,
954 \\ name text, 1111 \\ name text,
@@ -1554,6 +1711,49 @@ test "sqlite: statement iterator" {
1554 } 1711 }
1555} 1712}
1556 1713
1714test "sqlite: blob open" {
1715 var arena = std.heap.ArenaAllocator.init(testing.allocator);
1716 defer arena.deinit();
1717 var allocator = &arena.allocator;
1718
1719 var db = try getTestDb();
1720 defer db.deinit();
1721
1722 const blob_data = "\xDE\xAD\xBE\xEFabcdefghijklmnopqrstuvwxyz0123456789";
1723
1724 // Insert a new blob with a set length
1725 try db.exec("CREATE TABLE test_blob(id integer primary key, data blob)", .{});
1726
1727 try db.exec("INSERT INTO test_blob(data) VALUES(?)", .{
1728 .data = ZeroBlob{ .length = blob_data.len * 2 },
1729 });
1730
1731 const rowid = db.getLastInsertRowID();
1732
1733 // Open the blob for writing
1734 {
1735 var blob = try db.openBlob(.main, "test_blob", "data", rowid, .{ .write = true });
1736
1737 // Write the data
1738
1739 var blob_writer = blob.writer();
1740 try blob_writer.writeAll(blob_data);
1741 try blob_writer.writeAll(blob_data);
1742
1743 try blob.close();
1744 }
1745
1746 // Now read the data and check the results
1747 var blob = try db.openBlob(.main, "test_blob", "data", rowid, .{});
1748
1749 var blob_reader = blob.reader();
1750 const data = try blob_reader.readAllAlloc(allocator, 8192);
1751
1752 testing.expectEqualSlices(u8, blob_data ** 2, data);
1753
1754 try blob.close();
1755}
1756
1557test "sqlite: failing open" { 1757test "sqlite: failing open" {
1558 var db: Db = undefined; 1758 var db: Db = undefined;
1559 const res = db.init(.{ 1759 const res = db.init(.{
@@ -1610,6 +1810,10 @@ fn dbMode(allocator: *mem.Allocator) Db.Mode {
1610 return if (build_options.in_memory) blk: { 1810 return if (build_options.in_memory) blk: {
1611 break :blk .{ .Memory = {} }; 1811 break :blk .{ .Memory = {} };
1612 } else blk: { 1812 } else blk: {
1813 if (build_options.dbfile) |dbfile| {
1814 return .{ .File = allocator.dupeZ(u8, dbfile) catch unreachable };
1815 }
1816
1613 const path = tmpDbPath(allocator) catch unreachable; 1817 const path = tmpDbPath(allocator) catch unreachable;
1614 1818
1615 std.fs.cwd().deleteFile(path) catch {}; 1819 std.fs.cwd().deleteFile(path) catch {};