@korajs/tauri 0.3.4 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/plugin/src/lib.rs CHANGED
@@ -9,10 +9,10 @@
9
9
  //!
10
10
  //! Add the plugin to your Tauri app:
11
11
  //!
12
- //! ```rust,no_run
12
+ //! ```rust,ignore
13
13
  //! fn main() {
14
14
  //! tauri::Builder::default()
15
- //! .plugin(tauri_plugin_kora::init())
15
+ //! .plugin(tauri_plugin_kora_sqlite::init())
16
16
  //! .run(tauri::generate_context!())
17
17
  //! .expect("error while running tauri application");
18
18
  //! }
@@ -0,0 +1,555 @@
1
+ use rusqlite::Connection;
2
+
3
+ /// Helper: open an in-memory database with the same pragmas the plugin uses.
4
+ fn open_kora_db() -> Connection {
5
+ let conn = Connection::open_in_memory().unwrap();
6
+ conn.pragma_update(None, "journal_mode", "WAL").ok();
7
+ conn.pragma_update(None, "foreign_keys", "ON").unwrap();
8
+ conn.pragma_update(None, "synchronous", "NORMAL").unwrap();
9
+ conn.pragma_update(None, "busy_timeout", "5000").unwrap();
10
+ conn
11
+ }
12
+
13
+ /// Helper: create the standard Kora metadata tables.
14
+ fn create_kora_meta(conn: &Connection) {
15
+ conn.execute_batch(
16
+ "
17
+ CREATE TABLE IF NOT EXISTS _kora_meta (
18
+ key TEXT PRIMARY KEY,
19
+ value TEXT NOT NULL
20
+ );
21
+ CREATE TABLE IF NOT EXISTS _kora_version_vector (
22
+ node_id TEXT PRIMARY KEY,
23
+ max_sequence_number INTEGER NOT NULL,
24
+ last_seen_at INTEGER NOT NULL
25
+ );
26
+ ",
27
+ )
28
+ .unwrap();
29
+ }
30
+
31
+ const DDL_TODOS: &str = "
32
+ CREATE TABLE IF NOT EXISTS todos (
33
+ id TEXT PRIMARY KEY,
34
+ title TEXT NOT NULL,
35
+ completed INTEGER NOT NULL DEFAULT 0,
36
+ _created_at INTEGER NOT NULL,
37
+ _updated_at INTEGER NOT NULL
38
+ );
39
+ CREATE TABLE IF NOT EXISTS _kora_ops_todos (
40
+ id TEXT PRIMARY KEY,
41
+ node_id TEXT NOT NULL,
42
+ type TEXT NOT NULL,
43
+ collection TEXT NOT NULL,
44
+ record_id TEXT NOT NULL,
45
+ data TEXT,
46
+ previous_data TEXT,
47
+ wall_time INTEGER NOT NULL,
48
+ logical INTEGER NOT NULL,
49
+ ts_node_id TEXT NOT NULL,
50
+ sequence_number INTEGER NOT NULL,
51
+ causal_deps TEXT NOT NULL DEFAULT '[]',
52
+ schema_version INTEGER NOT NULL
53
+ );
54
+ CREATE INDEX IF NOT EXISTS idx_todos_completed ON todos(completed);
55
+ ";
56
+
57
+ // ── Database initialization tests ─────────────────────────────────────────────
58
+
59
+ #[test]
60
+ fn test_open_sets_wal_mode() {
61
+ let conn = open_kora_db();
62
+ let mode: String = conn
63
+ .pragma_query_value(None, "journal_mode", |row| row.get(0))
64
+ .unwrap();
65
+ // In-memory databases fall back to 'memory' when WAL is requested
66
+ assert!(mode == "wal" || mode == "memory", "expected wal or memory, got {}", mode);
67
+ }
68
+
69
+ #[test]
70
+ fn test_open_enables_foreign_keys() {
71
+ let conn = open_kora_db();
72
+ let fk: i64 = conn
73
+ .pragma_query_value(None, "foreign_keys", |row| row.get(0))
74
+ .unwrap();
75
+ assert_eq!(fk, 1, "foreign_keys should be ON");
76
+ }
77
+
78
+ // ── DDL execution tests ──────────────────────────────────────────────────────
79
+
80
+ #[test]
81
+ fn test_create_tables_from_ddl() {
82
+ let conn = open_kora_db();
83
+ create_kora_meta(&conn);
84
+ conn.execute_batch(DDL_TODOS).unwrap();
85
+
86
+ let tables: Vec<String> = conn
87
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
88
+ .unwrap()
89
+ .query_map([], |row| row.get(0))
90
+ .unwrap()
91
+ .collect::<Result<Vec<_>, _>>()
92
+ .unwrap();
93
+
94
+ assert!(tables.contains(&"_kora_meta".to_string()));
95
+ assert!(tables.contains(&"_kora_version_vector".to_string()));
96
+ assert!(tables.contains(&"todos".to_string()));
97
+ assert!(tables.contains(&"_kora_ops_todos".to_string()));
98
+ }
99
+
100
+ #[test]
101
+ fn test_create_indexes() {
102
+ let conn = open_kora_db();
103
+ create_kora_meta(&conn);
104
+ conn.execute_batch(DDL_TODOS).unwrap();
105
+
106
+ let indexes: Vec<String> = conn
107
+ .prepare("SELECT name FROM sqlite_master WHERE type='index' AND name NOT LIKE 'sqlite_%' ORDER BY name")
108
+ .unwrap()
109
+ .query_map([], |row| row.get(0))
110
+ .unwrap()
111
+ .collect::<Result<Vec<_>, _>>()
112
+ .unwrap();
113
+
114
+ assert!(indexes.contains(&"idx_todos_completed".to_string()));
115
+ }
116
+
117
+ // ── CRUD tests ───────────────────────────────────────────────────────────────
118
+
119
+ #[test]
120
+ fn test_insert_and_select() {
121
+ let conn = open_kora_db();
122
+ create_kora_meta(&conn);
123
+ conn.execute_batch(DDL_TODOS).unwrap();
124
+
125
+ conn.execute(
126
+ "INSERT INTO todos (id, title, completed, _created_at, _updated_at) VALUES (?1, ?2, ?3, ?4, ?5)",
127
+ ["rec-1", "Hello", "0", "1000", "1000"],
128
+ )
129
+ .unwrap();
130
+
131
+ let (title, completed): (String, i64) = conn
132
+ .query_row("SELECT title, completed FROM todos WHERE id = ?1", ["rec-1"], |row| {
133
+ Ok((row.get(0)?, row.get(1)?))
134
+ })
135
+ .unwrap();
136
+
137
+ assert_eq!(title, "Hello");
138
+ assert_eq!(completed, 0);
139
+ }
140
+
141
+ #[test]
142
+ fn test_update_record() {
143
+ let conn = open_kora_db();
144
+ create_kora_meta(&conn);
145
+ conn.execute_batch(DDL_TODOS).unwrap();
146
+
147
+ conn.execute(
148
+ "INSERT INTO todos (id, title, completed, _created_at, _updated_at) VALUES (?1, ?2, ?3, ?4, ?5)",
149
+ ["rec-1", "Old Title", "0", "1000", "1000"],
150
+ ).unwrap();
151
+
152
+ conn.execute(
153
+ "UPDATE todos SET title = ?1, completed = ?2, _updated_at = ?3 WHERE id = ?4",
154
+ ["Updated", "1", "1001", "rec-1"],
155
+ )
156
+ .unwrap();
157
+
158
+ let (title, completed): (String, i64) = conn
159
+ .query_row("SELECT title, completed FROM todos WHERE id = ?1", ["rec-1"], |row| {
160
+ Ok((row.get(0)?, row.get(1)?))
161
+ })
162
+ .unwrap();
163
+
164
+ assert_eq!(title, "Updated");
165
+ assert_eq!(completed, 1);
166
+ }
167
+
168
+ #[test]
169
+ fn test_delete_record() {
170
+ let conn = open_kora_db();
171
+ create_kora_meta(&conn);
172
+ conn.execute_batch(DDL_TODOS).unwrap();
173
+
174
+ conn.execute(
175
+ "INSERT INTO todos (id, title, completed, _created_at, _updated_at) VALUES (?1, ?2, ?3, ?4, ?5)",
176
+ ["rec-1", "Delete Me", "0", "1000", "1000"],
177
+ )
178
+ .unwrap();
179
+
180
+ conn.execute("DELETE FROM todos WHERE id = ?1", ["rec-1"])
181
+ .unwrap();
182
+
183
+ let count: i64 = conn
184
+ .query_row("SELECT COUNT(*) FROM todos WHERE id = ?1", ["rec-1"], |row| {
185
+ row.get(0)
186
+ })
187
+ .unwrap();
188
+ assert_eq!(count, 0);
189
+ }
190
+
191
+ // ── Operation log tests ──────────────────────────────────────────────────────
192
+
193
+ #[test]
194
+ fn test_insert_and_read_operation() {
195
+ let conn = open_kora_db();
196
+ create_kora_meta(&conn);
197
+ conn.execute_batch(DDL_TODOS).unwrap();
198
+
199
+ conn.execute(
200
+ "INSERT INTO _kora_ops_todos (id, node_id, type, collection, record_id, data, previous_data, wall_time, logical, ts_node_id, sequence_number, causal_deps, schema_version)
201
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
202
+ ["op-1", "node-a", "insert", "todos", "rec-1", r#"{"title":"Hello"}"#, "", "1000", "0", "node-a", "1", "[]", "1"],
203
+ ).unwrap();
204
+
205
+ let (op_id, op_type, op_record_id): (String, String, String) = conn
206
+ .query_row(
207
+ "SELECT id, type, record_id FROM _kora_ops_todos WHERE id = ?1",
208
+ ["op-1"],
209
+ |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
210
+ )
211
+ .unwrap();
212
+
213
+ assert_eq!(op_id, "op-1");
214
+ assert_eq!(op_type, "insert");
215
+ assert_eq!(op_record_id, "rec-1");
216
+ }
217
+
218
+ #[test]
219
+ fn test_operation_range_query() {
220
+ let conn = open_kora_db();
221
+ create_kora_meta(&conn);
222
+ conn.execute_batch(DDL_TODOS).unwrap();
223
+
224
+ for i in 1..=5 {
225
+ conn.execute(
226
+ "INSERT INTO _kora_ops_todos (id, node_id, type, collection, record_id, data, previous_data, wall_time, logical, ts_node_id, sequence_number, causal_deps, schema_version)
227
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
228
+ rusqlite::params![
229
+ format!("op-{}", i), "node-a", "insert", "todos", format!("rec-{}", i),
230
+ r#"{"title":"test"}"#, "", 1000 + i, 0, "node-a", i, "[]", 1,
231
+ ],
232
+ ).unwrap();
233
+ }
234
+
235
+ let ops: Vec<String> = conn
236
+ .prepare("SELECT id FROM _kora_ops_todos WHERE node_id = ?1 AND sequence_number >= ?2 AND sequence_number <= ?3 ORDER BY sequence_number")
237
+ .unwrap()
238
+ .query_map(rusqlite::params!["node-a", 2, 4], |row| row.get(0))
239
+ .unwrap()
240
+ .collect::<Result<Vec<_>, _>>()
241
+ .unwrap();
242
+
243
+ assert_eq!(ops, vec!["op-2", "op-3", "op-4"]);
244
+ }
245
+
246
+ // ── Transaction tests ─────────────────────────────────────────────────────────
247
+
248
+ #[test]
249
+ fn test_transaction_commits() {
250
+ let conn = open_kora_db();
251
+ create_kora_meta(&conn);
252
+ conn.execute_batch(DDL_TODOS).unwrap();
253
+
254
+ conn.execute_batch("BEGIN").unwrap();
255
+ conn.execute(
256
+ "INSERT INTO todos (id, title, completed, _created_at, _updated_at) VALUES (?1, ?2, ?3, ?4, ?5)",
257
+ ["rec-1", "In TX", "0", "1000", "1000"],
258
+ ).unwrap();
259
+ conn.execute_batch("COMMIT").unwrap();
260
+
261
+ let count: i64 = conn
262
+ .query_row("SELECT COUNT(*) FROM todos", [], |row| row.get(0))
263
+ .unwrap();
264
+ assert_eq!(count, 1);
265
+ }
266
+
267
+ #[test]
268
+ fn test_transaction_rollback_on_error() {
269
+ let conn = open_kora_db();
270
+ create_kora_meta(&conn);
271
+ conn.execute_batch(DDL_TODOS).unwrap();
272
+
273
+ // Start transaction, insert, then intentionally fail
274
+ conn.execute_batch("BEGIN").unwrap();
275
+ conn.execute(
276
+ "INSERT INTO todos (id, title, completed, _created_at, _updated_at) VALUES (?1, ?2, ?3, ?4, ?5)",
277
+ ["rec-1", "Rollback Me", "0", "1000", "1000"],
278
+ ).unwrap();
279
+ // Simulate an error by rolling back
280
+ conn.execute_batch("ROLLBACK").unwrap();
281
+
282
+ let count: i64 = conn
283
+ .query_row("SELECT COUNT(*) FROM todos", [], |row| row.get(0))
284
+ .unwrap();
285
+ assert_eq!(count, 0);
286
+ }
287
+
288
+ #[test]
289
+ fn test_transaction_isolation() {
290
+ let conn = open_kora_db();
291
+ create_kora_meta(&conn);
292
+ conn.execute_batch(DDL_TODOS).unwrap();
293
+
294
+ conn.execute_batch("BEGIN").unwrap();
295
+ conn.execute(
296
+ "INSERT INTO todos (id, title, completed, _created_at, _updated_at) VALUES (?1, ?2, ?3, ?4, ?5)",
297
+ ["rec-1", "Isolated", "0", "1000", "1000"],
298
+ ).unwrap();
299
+
300
+ // Changes shouldn't be visible outside the transaction
301
+ let count: i64 = conn
302
+ .query_row("SELECT COUNT(*) FROM todos", [], |row| row.get(0))
303
+ .unwrap();
304
+ assert_eq!(count, 1); // Within same connection, uncommitted data IS visible (SQLite default)
305
+
306
+ conn.execute_batch("ROLLBACK").unwrap();
307
+ }
308
+
309
+ // ── Migration tests ───────────────────────────────────────────────────────────
310
+
311
+ #[test]
312
+ fn test_migration_adds_column() {
313
+ let conn = open_kora_db();
314
+ create_kora_meta(&conn);
315
+ conn.execute_batch(DDL_TODOS).unwrap();
316
+
317
+ // Add a new column (migration)
318
+ conn.execute_batch("BEGIN").unwrap();
319
+ conn.execute_batch("ALTER TABLE todos ADD COLUMN notes TEXT").unwrap();
320
+ conn.execute_batch("COMMIT").unwrap();
321
+
322
+ // Verify the new column exists and can be written to
323
+ conn.execute(
324
+ "INSERT INTO todos (id, title, completed, notes, _created_at, _updated_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
325
+ ["rec-1", "With Notes", "0", "Some notes", "1000", "1000"],
326
+ ).unwrap();
327
+
328
+ let notes: String = conn
329
+ .query_row("SELECT notes FROM todos WHERE id = ?1", ["rec-1"], |row| {
330
+ row.get(0)
331
+ })
332
+ .unwrap();
333
+ assert_eq!(notes, "Some notes");
334
+ }
335
+
336
+ #[test]
337
+ fn test_migration_rolls_back_on_error() {
338
+ let conn = open_kora_db();
339
+ create_kora_meta(&conn);
340
+ conn.execute_batch(DDL_TODOS).unwrap();
341
+
342
+ // Try to apply invalid SQL within a migration
343
+ let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
344
+ conn.execute_batch("BEGIN").unwrap();
345
+ conn.execute_batch("INVALID SQL STATEMENT").unwrap(); // This will panic
346
+ conn.execute_batch("COMMIT").unwrap();
347
+ }));
348
+
349
+ assert!(result.is_err(), "invalid SQL should cause a panic/error");
350
+ }
351
+
352
+ #[test]
353
+ fn test_safe_alter_table_ignores_duplicate_column() {
354
+ let conn = open_kora_db();
355
+ create_kora_meta(&conn);
356
+ conn.execute_batch(DDL_TODOS).unwrap();
357
+
358
+ // First add a column
359
+ conn.execute_batch("ALTER TABLE todos ADD COLUMN priority TEXT DEFAULT 'medium'").unwrap();
360
+
361
+ // Try adding the same column again — the plugin's safe-alter pattern
362
+ // should ignore the "duplicate column name" error
363
+ let sql = "ALTER TABLE todos ADD COLUMN priority TEXT DEFAULT 'medium'";
364
+ match conn.execute_batch(sql) {
365
+ Ok(_) => {} // Should not happen — column already exists
366
+ Err(e) => {
367
+ let msg = e.to_string();
368
+ assert!(
369
+ msg.contains("duplicate column name"),
370
+ "expected duplicate column error, got: {}",
371
+ msg
372
+ );
373
+ }
374
+ }
375
+ }
376
+
377
+ // ── Data type tests ───────────────────────────────────────────────────────────
378
+
379
+ #[test]
380
+ fn test_all_sql_types_roundtrip() {
381
+ let conn = open_kora_db();
382
+ conn.execute_batch(
383
+ "CREATE TABLE types (
384
+ id TEXT PRIMARY KEY,
385
+ int_v INTEGER,
386
+ float_v REAL,
387
+ text_v TEXT,
388
+ bool_v INTEGER
389
+ )",
390
+ )
391
+ .unwrap();
392
+
393
+ conn.execute(
394
+ "INSERT INTO types (id, int_v, float_v, text_v, bool_v) VALUES (?1, ?2, ?3, ?4, ?5)",
395
+ rusqlite::params!["rec-1", 42, 3.14, "hello", 1],
396
+ )
397
+ .unwrap();
398
+
399
+ let (int_v, float_v, text_v, bool_v): (i64, f64, String, i64) = conn
400
+ .query_row(
401
+ "SELECT int_v, float_v, text_v, bool_v FROM types WHERE id = ?1",
402
+ ["rec-1"],
403
+ |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
404
+ )
405
+ .unwrap();
406
+
407
+ assert_eq!(int_v, 42);
408
+ assert!((float_v - 3.14).abs() < 1e-10);
409
+ assert_eq!(text_v, "hello");
410
+ assert_eq!(bool_v, 1);
411
+ }
412
+
413
+ #[test]
414
+ fn test_null_values() {
415
+ let conn = open_kora_db();
416
+ conn.execute_batch(
417
+ "CREATE TABLE nullable (id TEXT PRIMARY KEY, value TEXT)",
418
+ ).unwrap();
419
+
420
+ conn.execute(
421
+ "INSERT INTO nullable (id, value) VALUES (?1, ?2)",
422
+ rusqlite::params!["rec-1", rusqlite::types::Null],
423
+ )
424
+ .unwrap();
425
+
426
+ let value: Option<String> = conn
427
+ .query_row("SELECT value FROM nullable WHERE id = ?1", ["rec-1"], |row| {
428
+ row.get(0)
429
+ })
430
+ .unwrap();
431
+ assert_eq!(value, None);
432
+ }
433
+
434
+ #[test]
435
+ fn test_blob_as_hex() {
436
+ let conn = open_kora_db();
437
+ conn.execute_batch("CREATE TABLE blobs (id TEXT PRIMARY KEY, data BLOB)").unwrap();
438
+
439
+ let input: Vec<u8> = vec![0x00, 0xFF, 0xAB, 0xCD];
440
+ conn.execute(
441
+ "INSERT INTO blobs (id, data) VALUES (?1, ?2)",
442
+ rusqlite::params!["rec-1", input],
443
+ )
444
+ .unwrap();
445
+
446
+ let output: Vec<u8> = conn
447
+ .query_row("SELECT data FROM blobs WHERE id = ?1", ["rec-1"], |row| {
448
+ row.get(0)
449
+ })
450
+ .unwrap();
451
+ assert_eq!(output, input);
452
+ }
453
+
454
+ // ── Error case tests ──────────────────────────────────────────────────────────
455
+
456
+ #[test]
457
+ fn test_invalid_sql_returns_error() {
458
+ let conn = open_kora_db();
459
+ let result = conn.execute_batch("SELECT FROM nowhere");
460
+ assert!(result.is_err(), "invalid SQL should return an error");
461
+ }
462
+
463
+ #[test]
464
+ fn test_query_missing_table_returns_error() {
465
+ let conn = open_kora_db();
466
+ let result = conn.prepare("SELECT * FROM nonexistent");
467
+ assert!(result.is_err(), "query on missing table should error");
468
+ }
469
+
470
+ // ── Version vector tests ─────────────────────────────────────────────────────
471
+
472
+ #[test]
473
+ fn test_version_vector_insert_and_update() {
474
+ let conn = open_kora_db();
475
+ create_kora_meta(&conn);
476
+
477
+ conn.execute(
478
+ "INSERT INTO _kora_version_vector (node_id, max_sequence_number, last_seen_at) VALUES (?1, ?2, ?3)",
479
+ ["node-a", "1", "1000"],
480
+ ).unwrap();
481
+
482
+ let (seq, seen): (i64, i64) = conn
483
+ .query_row(
484
+ "SELECT max_sequence_number, last_seen_at FROM _kora_version_vector WHERE node_id = ?1",
485
+ ["node-a"],
486
+ |row| Ok((row.get(0)?, row.get(1)?)),
487
+ )
488
+ .unwrap();
489
+ assert_eq!(seq, 1);
490
+ assert_eq!(seen, 1000);
491
+
492
+ // Update
493
+ conn.execute(
494
+ "UPDATE _kora_version_vector SET max_sequence_number = ?1, last_seen_at = ?2 WHERE node_id = ?3",
495
+ ["5", "1002", "node-a"],
496
+ ).unwrap();
497
+
498
+ let seq: i64 = conn
499
+ .query_row(
500
+ "SELECT max_sequence_number FROM _kora_version_vector WHERE node_id = ?1",
501
+ ["node-a"],
502
+ |row| row.get(0),
503
+ )
504
+ .unwrap();
505
+ assert_eq!(seq, 5);
506
+ }
507
+
508
+ #[test]
509
+ fn test_version_vector_increments_monotonically() {
510
+ let conn = open_kora_db();
511
+ create_kora_meta(&conn);
512
+
513
+ for i in 1..=10 {
514
+ conn.execute(
515
+ "INSERT INTO _kora_version_vector (node_id, max_sequence_number, last_seen_at) VALUES (?1, ?2, ?3)
516
+ ON CONFLICT(node_id) DO UPDATE SET max_sequence_number = ?2, last_seen_at = ?3",
517
+ rusqlite::params!["node-a", i, 1000 + i],
518
+ ).unwrap();
519
+ }
520
+
521
+ let seq: i64 = conn
522
+ .query_row(
523
+ "SELECT max_sequence_number FROM _kora_version_vector WHERE node_id = ?1",
524
+ ["node-a"],
525
+ |row| row.get(0),
526
+ )
527
+ .unwrap();
528
+ assert_eq!(seq, 10);
529
+ }
530
+
531
+ // ── Bulk operation tests ─────────────────────────────────────────────────────
532
+
533
+ #[test]
534
+ fn test_bulk_insert_and_count() {
535
+ let conn = open_kora_db();
536
+ create_kora_meta(&conn);
537
+ conn.execute_batch(DDL_TODOS).unwrap();
538
+
539
+ for i in 0..100 {
540
+ conn.execute(
541
+ "INSERT INTO todos (id, title, completed, _created_at, _updated_at) VALUES (?1, ?2, ?3, ?4, ?5)",
542
+ rusqlite::params![format!("rec-{}", i), format!("Task {}", i), if i % 2 == 0 { 1 } else { 0 }, 1000 + i, 1000 + i],
543
+ ).unwrap();
544
+ }
545
+
546
+ let total: i64 = conn
547
+ .query_row("SELECT COUNT(*) FROM todos", [], |row| row.get(0))
548
+ .unwrap();
549
+ assert_eq!(total, 100);
550
+
551
+ let completed: i64 = conn
552
+ .query_row("SELECT COUNT(*) FROM todos WHERE completed = 1", [], |row| row.get(0))
553
+ .unwrap();
554
+ assert_eq!(completed, 50);
555
+ }