summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Vincent Rischmann2021-02-27 13:22:12 +0100
committerGravatar GitHub2021-02-27 13:22:12 +0100
commite2269c720b79404623050b5250902a667ca091e8 (patch)
tree0e09f87e0e529aa587dbfd58651c4401a0370724
parentMerge pull request #10 from vrischmann/incremental-blob-io (diff)
parentreadme: document prepareWithDiags (diff)
downloadzig-sqlite-e2269c720b79404623050b5250902a667ca091e8.tar.gz
zig-sqlite-e2269c720b79404623050b5250902a667ca091e8.tar.xz
zig-sqlite-e2269c720b79404623050b5250902a667ca091e8.zip
Merge pull request #6 from vrischmann/diagnostics
add a way to get diagnostics
-rw-r--r--README.md13
-rw-r--r--sqlite.zig145
2 files changed, 147 insertions, 11 deletions
diff --git a/README.md b/README.md
index f1f2e09..1551cc0 100644
--- a/README.md
+++ b/README.md
@@ -104,6 +104,19 @@ defer stmt.deinit();
104 104
105The `Db.prepare` method takes a `comptime` query string. 105The `Db.prepare` method takes a `comptime` query string.
106 106
107### Diagnostics
108
109If you want failure diagnostics you can use `prepareWithDiags` like this:
110
111```zig
112var diags = sqlite.Diagnostics{};
113var stmt = db.prepareWithDiags(query, .{ .diags = &diags }) catch |err| {
114 std.log.err("unable to prepare statement, got error {s}. diagnostics: {s}", .{ err, diags });
115 return err;
116};
117defer stmt.deinit();
118```
119
107## Executing a statement 120## Executing a statement
108 121
109For queries which do not return data (`INSERT`, `UPDATE`) you can use the `exec` method: 122For queries which do not return data (`INSERT`, `UPDATE`) you can use the `exec` method:
diff --git a/sqlite.zig b/sqlite.zig
index e1e3216..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.
@@ -456,18 +510,23 @@ pub fn Iterator(comptime Type: type) type {
456 510
457 const TypeInfo = @typeInfo(Type); 511 const TypeInfo = @typeInfo(Type);
458 512
513 db: *c.sqlite3,
459 stmt: *c.sqlite3_stmt, 514 stmt: *c.sqlite3_stmt,
460 515
461 // next scans the next row using the prepared statement. 516 // next scans the next row using the prepared statement.
462 // If it returns null iterating is done. 517 // If it returns null iterating is done.
463 // 518 //
464 // 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.
465 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
466 var result = c.sqlite3_step(self.stmt); 524 var result = c.sqlite3_step(self.stmt);
467 if (result == c.SQLITE_DONE) { 525 if (result == c.SQLITE_DONE) {
468 return null; 526 return null;
469 } 527 }
470 if (result != c.SQLITE_ROW) { 528 if (result != c.SQLITE_ROW) {
529 diags.err = getLastDetailedErrorFromDb(self.db);
471 return errorFromResultCode(result); 530 return errorFromResultCode(result);
472 } 531 }
473 532
@@ -502,12 +561,16 @@ pub fn Iterator(comptime Type: type) type {
502 } 561 }
503 562
504 // nextAlloc is like `next` but can allocate memory. 563 // nextAlloc is like `next` but can allocate memory.
505 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
506 var result = c.sqlite3_step(self.stmt); 568 var result = c.sqlite3_step(self.stmt);
507 if (result == c.SQLITE_DONE) { 569 if (result == c.SQLITE_DONE) {
508 return null; 570 return null;
509 } 571 }
510 if (result != c.SQLITE_ROW) { 572 if (result != c.SQLITE_ROW) {
573 diags.err = getLastDetailedErrorFromDb(self.db);
511 return errorFromResultCode(result); 574 return errorFromResultCode(result);
512 } 575 }
513 576
@@ -843,9 +906,13 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
843 return struct { 906 return struct {
844 const Self = @This(); 907 const Self = @This();
845 908
909 db: *c.sqlite3,
846 stmt: *c.sqlite3_stmt, 910 stmt: *c.sqlite3_stmt,
847 911
848 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
849 var stmt = blk: { 916 var stmt = blk: {
850 const real_query = query.getQuery(); 917 const real_query = query.getQuery();
851 918
@@ -859,12 +926,14 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
859 null, 926 null,
860 ); 927 );
861 if (result != c.SQLITE_OK) { 928 if (result != c.SQLITE_OK) {
929 diags.err = getLastDetailedErrorFromDb(db.db);
862 return errorFromResultCode(result); 930 return errorFromResultCode(result);
863 } 931 }
864 break :blk tmp.?; 932 break :blk tmp.?;
865 }; 933 };
866 934
867 return Self{ 935 return Self{
936 .db = db.db,
868 .stmt = stmt, 937 .stmt = stmt,
869 }; 938 };
870 } 939 }
@@ -1010,6 +1079,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
1010 self.bind(values); 1079 self.bind(values);
1011 1080
1012 var res: Iterator(Type) = undefined; 1081 var res: Iterator(Type) = undefined;
1082 res.db = self.db;
1013 res.stmt = self.stmt; 1083 res.stmt = self.stmt;
1014 1084
1015 return res; 1085 return res;
@@ -1791,18 +1861,25 @@ test "sqlite: blob open, reopen" {
1791} 1861}
1792 1862
1793test "sqlite: failing open" { 1863test "sqlite: failing open" {
1864 var diags: Diagnostics = undefined;
1865
1794 var db: Db = undefined; 1866 var db: Db = undefined;
1795 const res = db.init(.{ 1867 const res = db.init(.{
1868 .diags = &diags,
1796 .open_flags = .{}, 1869 .open_flags = .{},
1797 .mode = .{ .File = "/tmp/not_existing.db" }, 1870 .mode = .{ .File = "/tmp/not_existing.db" },
1798 }); 1871 });
1799 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);
1800} 1875}
1801 1876
1802test "sqlite: failing prepare statement" { 1877test "sqlite: failing prepare statement" {
1803 var db = try getTestDb(); 1878 var db = try getTestDb();
1804 1879
1805 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 });
1806 testing.expectError(error.SQLiteError, result); 1883 testing.expectError(error.SQLiteError, result);
1807 1884
1808 const detailed_err = db.getDetailedError(); 1885 const detailed_err = db.getDetailedError();
@@ -1810,6 +1887,52 @@ test "sqlite: failing prepare statement" {
1810 testing.expectEqualStrings("no such table: foobar", detailed_err.message); 1887 testing.expectEqualStrings("no such table: foobar", detailed_err.message);
1811} 1888}
1812 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
1813fn getTestDb() !Db { 1936fn getTestDb() !Db {
1814 var buf: [1024]u8 = undefined; 1937 var buf: [1024]u8 = undefined;
1815 var fba = std.heap.FixedBufferAllocator.init(&buf); 1938 var fba = std.heap.FixedBufferAllocator.init(&buf);