summaryrefslogtreecommitdiff
path: root/sqlite.zig
diff options
context:
space:
mode:
authorGravatar Vincent Rischmann2020-12-30 15:23:56 +0100
committerGravatar Vincent Rischmann2021-02-27 12:43:22 +0100
commitb4957c7b0c1de8786b4e4f356650925422e6808d (patch)
treeeed70eddddefb7dc6a481a0ed62a77e01f347288 /sqlite.zig
parentstore the database handle in Iterator and Statement (diff)
downloadzig-sqlite-b4957c7b0c1de8786b4e4f356650925422e6808d.tar.gz
zig-sqlite-b4957c7b0c1de8786b4e4f356650925422e6808d.tar.xz
zig-sqlite-b4957c7b0c1de8786b4e4f356650925422e6808d.zip
add Diagnostics
Diffstat (limited to 'sqlite.zig')
-rw-r--r--sqlite.zig141
1 files changed, 130 insertions, 11 deletions
diff --git a/sqlite.zig b/sqlite.zig
index feb0295..7f9b837 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -192,6 +192,31 @@ pub const ThreadingMode = enum {
192 Serialized, 192 Serialized,
193}; 193};
194 194
195/// Diagnostics can be used by the library to give more information in case of failures.
196pub const Diagnostics = struct {
197 message: []const u8 = "",
198 err: ?DetailedError = null,
199
200 pub fn format(self: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
201 if (self.err) |err| {
202 if (self.message.len > 0) {
203 _ = try writer.print("{{message: {s}, error: {s}}}", .{ self.message, err.message });
204 return;
205 }
206
207 _ = try writer.write(err.message);
208 return;
209 }
210
211 if (self.message.len > 0) {
212 _ = try writer.write(self.message);
213 return;
214 }
215
216 _ = try writer.write("none");
217 }
218};
219
195pub const InitOptions = struct { 220pub const InitOptions = struct {
196 /// mode controls how the database is opened. 221 /// mode controls how the database is opened.
197 /// 222 ///
@@ -207,6 +232,9 @@ pub const InitOptions = struct {
207 /// 232 ///
208 /// Defaults to Serialized. 233 /// Defaults to Serialized.
209 threading_mode: ThreadingMode = .Serialized, 234 threading_mode: ThreadingMode = .Serialized,
235
236 /// if provided, diags will be populated in case of failures.
237 diags: ?*Diagnostics = null,
210}; 238};
211 239
212/// DetailedError contains a SQLite error code and error message. 240/// DetailedError contains a SQLite error code and error message.
@@ -267,8 +295,11 @@ pub const Db = struct {
267 create: bool = false, 295 create: bool = false,
268 }; 296 };
269 297
270 /// init creates a database with the provided `mode`. 298 /// init creates a database with the provided options.
271 pub fn init(self: *Self, options: InitOptions) !void { 299 pub fn init(self: *Self, options: InitOptions) !void {
300 var dummy_diags = Diagnostics{};
301 var diags = options.diags orelse &dummy_diags;
302
272 // Validate the threading mode 303 // Validate the threading mode
273 if (options.threading_mode != .SingleThread and !isThreadSafe()) { 304 if (options.threading_mode != .SingleThread and !isThreadSafe()) {
274 return error.CannotUseSingleThreadedSQLite; 305 return error.CannotUseSingleThreadedSQLite;
@@ -293,6 +324,11 @@ pub const Db = struct {
293 var db: ?*c.sqlite3 = undefined; 324 var db: ?*c.sqlite3 = undefined;
294 const result = c.sqlite3_open_v2(path, &db, flags, null); 325 const result = c.sqlite3_open_v2(path, &db, flags, null);
295 if (result != c.SQLITE_OK or db == null) { 326 if (result != c.SQLITE_OK or db == null) {
327 if (db) |v| {
328 diags.err = getLastDetailedErrorFromDb(v);
329 } else {
330 diags.err = getDetailedErrorFromResultCode(result);
331 }
296 return errorFromResultCode(result); 332 return errorFromResultCode(result);
297 } 333 }
298 334
@@ -306,6 +342,11 @@ pub const Db = struct {
306 var db: ?*c.sqlite3 = undefined; 342 var db: ?*c.sqlite3 = undefined;
307 const result = c.sqlite3_open_v2(":memory:", &db, flags, null); 343 const result = c.sqlite3_open_v2(":memory:", &db, flags, null);
308 if (result != c.SQLITE_OK or db == null) { 344 if (result != c.SQLITE_OK or db == null) {
345 if (db) |v| {
346 diags.err = getLastDetailedErrorFromDb(v);
347 } else {
348 diags.err = getDetailedErrorFromResultCode(result);
349 }
309 return errorFromResultCode(result); 350 return errorFromResultCode(result);
310 } 351 }
311 352
@@ -371,7 +412,7 @@ pub const Db = struct {
371 comptime var buf: [1024]u8 = undefined; 412 comptime var buf: [1024]u8 = undefined;
372 comptime var query = getPragmaQuery(&buf, name, arg); 413 comptime var query = getPragmaQuery(&buf, name, arg);
373 414
374 var stmt = try self.prepare(query); 415 var stmt = try self.prepareWithDiags(query, options);
375 defer stmt.deinit(); 416 defer stmt.deinit();
376 417
377 return try stmt.one(Type, options, .{}); 418 return try stmt.one(Type, options, .{});
@@ -385,19 +426,26 @@ pub const Db = struct {
385 } 426 }
386 427
387 /// one is a convenience function which prepares a statement and reads a single row from the result set. 428 /// one is a convenience function which prepares a statement and reads a single row from the result set.
388 pub fn one(self: *Self, comptime Type: type, comptime query: []const u8, options: anytype, values: anytype) !?Type { 429 pub fn one(self: *Self, comptime Type: type, comptime query: []const u8, options: QueryOptions, values: anytype) !?Type {
389 var stmt = try self.prepare(query); 430 var stmt = try self.prepareWithDiags(query, options);
390 defer stmt.deinit(); 431 defer stmt.deinit();
391 return try stmt.one(Type, options, values); 432 return try stmt.one(Type, options, values);
392 } 433 }
393 434
394 /// oneAlloc is like `one` but can allocate memory. 435 /// oneAlloc is like `one` but can allocate memory.
395 pub fn oneAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, comptime query: []const u8, options: anytype, values: anytype) !?Type { 436 pub fn oneAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, comptime query: []const u8, options: QueryOptions, values: anytype) !?Type {
396 var stmt = try self.prepare(query); 437 var stmt = try self.prepareWithDiags(query, options);
397 defer stmt.deinit(); 438 defer stmt.deinit();
398 return try stmt.oneAlloc(Type, allocator, options, values); 439 return try stmt.oneAlloc(Type, allocator, options, values);
399 } 440 }
400 441
442 /// prepareWithDiags is like `prepare` but takes an additional options argument.
443 pub fn prepareWithDiags(self: *Self, comptime query: []const u8, options: QueryOptions) !Statement(.{}, ParsedQuery.from(query)) {
444 @setEvalBranchQuota(10000);
445 const parsed_query = ParsedQuery.from(query);
446 return Statement(.{}, comptime parsed_query).prepare(self, options, 0);
447 }
448
401 /// prepare prepares a statement for the `query` provided. 449 /// prepare prepares a statement for the `query` provided.
402 /// 450 ///
403 /// The query is analysed at comptime to search for bind markers. 451 /// The query is analysed at comptime to search for bind markers.
@@ -411,10 +459,11 @@ pub const Db = struct {
411 /// The statement returned is only compatible with the number of bind markers in the input query. 459 /// The statement returned is only compatible with the number of bind markers in the input query.
412 /// This is done because we type check the bind parameters when executing the statement later. 460 /// This is done because we type check the bind parameters when executing the statement later.
413 /// 461 ///
462 /// If you want additional error information in case of failures, use `prepareWithDiags`.
414 pub fn prepare(self: *Self, comptime query: []const u8) !Statement(.{}, ParsedQuery.from(query)) { 463 pub fn prepare(self: *Self, comptime query: []const u8) !Statement(.{}, ParsedQuery.from(query)) {
415 @setEvalBranchQuota(10000); 464 @setEvalBranchQuota(10000);
416 const parsed_query = ParsedQuery.from(query); 465 const parsed_query = ParsedQuery.from(query);
417 return Statement(.{}, comptime parsed_query).prepare(self, 0); 466 return Statement(.{}, comptime parsed_query).prepare(self, .{}, 0);
418 } 467 }
419 468
420 /// rowsAffected returns the number of rows affected by the last statement executed. 469 /// rowsAffected returns the number of rows affected by the last statement executed.
@@ -428,6 +477,11 @@ pub const Db = struct {
428 } 477 }
429}; 478};
430 479
480pub const QueryOptions = struct {
481 /// if provided, diags will be populated in case of failures.
482 diags: ?*Diagnostics = null,
483};
484
431/// Iterator allows iterating over a result set. 485/// Iterator allows iterating over a result set.
432/// 486///
433/// Each call to `next` returns the next row of the result set, or null if the result set is exhausted. 487/// Each call to `next` returns the next row of the result set, or null if the result set is exhausted.
@@ -463,12 +517,16 @@ pub fn Iterator(comptime Type: type) type {
463 // If it returns null iterating is done. 517 // If it returns null iterating is done.
464 // 518 //
465 // This cannot allocate memory. If you need to read TEXT or BLOB columns you need to use arrays or alternatively call nextAlloc. 519 // This cannot allocate memory. If you need to read TEXT or BLOB columns you need to use arrays or alternatively call nextAlloc.
466 pub fn next(self: *Self, options: anytype) !?Type { 520 pub fn next(self: *Self, options: QueryOptions) !?Type {
521 var dummy_diags = Diagnostics{};
522 var diags = options.diags orelse &dummy_diags;
523
467 var result = c.sqlite3_step(self.stmt); 524 var result = c.sqlite3_step(self.stmt);
468 if (result == c.SQLITE_DONE) { 525 if (result == c.SQLITE_DONE) {
469 return null; 526 return null;
470 } 527 }
471 if (result != c.SQLITE_ROW) { 528 if (result != c.SQLITE_ROW) {
529 diags.err = getLastDetailedErrorFromDb(self.db);
472 return errorFromResultCode(result); 530 return errorFromResultCode(result);
473 } 531 }
474 532
@@ -503,12 +561,16 @@ pub fn Iterator(comptime Type: type) type {
503 } 561 }
504 562
505 // nextAlloc is like `next` but can allocate memory. 563 // nextAlloc is like `next` but can allocate memory.
506 pub fn nextAlloc(self: *Self, allocator: *mem.Allocator, options: anytype) !?Type { 564 pub fn nextAlloc(self: *Self, allocator: *mem.Allocator, options: QueryOptions) !?Type {
565 var dummy_diags = Diagnostics{};
566 var diags = options.diags orelse &dummy_diags;
567
507 var result = c.sqlite3_step(self.stmt); 568 var result = c.sqlite3_step(self.stmt);
508 if (result == c.SQLITE_DONE) { 569 if (result == c.SQLITE_DONE) {
509 return null; 570 return null;
510 } 571 }
511 if (result != c.SQLITE_ROW) { 572 if (result != c.SQLITE_ROW) {
573 diags.err = getLastDetailedErrorFromDb(self.db);
512 return errorFromResultCode(result); 574 return errorFromResultCode(result);
513 } 575 }
514 576
@@ -847,7 +909,10 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
847 db: *c.sqlite3, 909 db: *c.sqlite3,
848 stmt: *c.sqlite3_stmt, 910 stmt: *c.sqlite3_stmt,
849 911
850 fn prepare(db: *Db, flags: c_uint) !Self { 912 fn prepare(db: *Db, options: QueryOptions, flags: c_uint) !Self {
913 var dummy_diags = Diagnostics{};
914 var diags = options.diags orelse &dummy_diags;
915
851 var stmt = blk: { 916 var stmt = blk: {
852 const real_query = query.getQuery(); 917 const real_query = query.getQuery();
853 918
@@ -861,6 +926,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
861 null, 926 null,
862 ); 927 );
863 if (result != c.SQLITE_OK) { 928 if (result != c.SQLITE_OK) {
929 diags.err = getLastDetailedErrorFromDb(db.db);
864 return errorFromResultCode(result); 930 return errorFromResultCode(result);
865 } 931 }
866 break :blk tmp.?; 932 break :blk tmp.?;
@@ -1795,18 +1861,25 @@ test "sqlite: blob open, reopen" {
1795} 1861}
1796 1862
1797test "sqlite: failing open" { 1863test "sqlite: failing open" {
1864 var diags: Diagnostics = undefined;
1865
1798 var db: Db = undefined; 1866 var db: Db = undefined;
1799 const res = db.init(.{ 1867 const res = db.init(.{
1868 .diags = &diags,
1800 .open_flags = .{}, 1869 .open_flags = .{},
1801 .mode = .{ .File = "/tmp/not_existing.db" }, 1870 .mode = .{ .File = "/tmp/not_existing.db" },
1802 }); 1871 });
1803 testing.expectError(error.SQLiteCantOpen, res); 1872 testing.expectError(error.SQLiteCantOpen, res);
1873 testing.expectEqual(@as(usize, 14), diags.err.?.code);
1874 testing.expectEqualStrings("unable to open database file", diags.err.?.message);
1804} 1875}
1805 1876
1806test "sqlite: failing prepare statement" { 1877test "sqlite: failing prepare statement" {
1807 var db = try getTestDb(); 1878 var db = try getTestDb();
1808 1879
1809 const result = db.prepare("SELECT id FROM foobar"); 1880 var diags: Diagnostics = undefined;
1881
1882 const result = db.prepareWithDiags("SELECT id FROM foobar", .{ .diags = &diags });
1810 testing.expectError(error.SQLiteError, result); 1883 testing.expectError(error.SQLiteError, result);
1811 1884
1812 const detailed_err = db.getDetailedError(); 1885 const detailed_err = db.getDetailedError();
@@ -1814,6 +1887,52 @@ test "sqlite: failing prepare statement" {
1814 testing.expectEqualStrings("no such table: foobar", detailed_err.message); 1887 testing.expectEqualStrings("no such table: foobar", detailed_err.message);
1815} 1888}
1816 1889
1890test "sqlite: diagnostics format" {
1891 const TestCase = struct {
1892 input: Diagnostics,
1893 exp: []const u8,
1894 };
1895
1896 const testCases = &[_]TestCase{
1897 .{
1898 .input = .{},
1899 .exp = "my diagnostics: none",
1900 },
1901 .{
1902 .input = .{
1903 .message = "foobar",
1904 },
1905 .exp = "my diagnostics: foobar",
1906 },
1907 .{
1908 .input = .{
1909 .err = .{
1910 .code = 20,
1911 .message = "barbaz",
1912 },
1913 },
1914 .exp = "my diagnostics: barbaz",
1915 },
1916 .{
1917 .input = .{
1918 .message = "foobar",
1919 .err = .{
1920 .code = 20,
1921 .message = "barbaz",
1922 },
1923 },
1924 .exp = "my diagnostics: {message: foobar, error: barbaz}",
1925 },
1926 };
1927
1928 inline for (testCases) |tc| {
1929 var buf: [1024]u8 = undefined;
1930 const str = try std.fmt.bufPrint(&buf, "my diagnostics: {s}", .{tc.input});
1931
1932 testing.expectEqualStrings(tc.exp, str);
1933 }
1934}
1935
1817fn getTestDb() !Db { 1936fn getTestDb() !Db {
1818 var buf: [1024]u8 = undefined; 1937 var buf: [1024]u8 = undefined;
1819 var fba = std.heap.FixedBufferAllocator.init(&buf); 1938 var fba = std.heap.FixedBufferAllocator.init(&buf);