dirsql 0.3.56__tar.gz → 0.3.57__tar.gz
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.
- {dirsql-0.3.56 → dirsql-0.3.57}/Cargo.lock +1 -1
- {dirsql-0.3.56 → dirsql-0.3.57}/PKG-INFO +1 -1
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/Cargo.toml +1 -1
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/e2e-attestation.json +2 -2
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/db.rs +77 -2
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/persist.rs +76 -6
- {dirsql-0.3.56 → dirsql-0.3.57}/Cargo.toml +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/README.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/dirsql/__init__.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/dirsql/_async.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/dirsql/cli/binary_path.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/dirsql/cli/interpret/__init__.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/dirsql/cli/is_windows.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/dirsql/cli/main.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/dirsql/py.typed +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/dirsql/resolve_config_extensions.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/AGENTS.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/api/index.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/cli/config.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/cli/http-api.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/cli/index.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/cli/init.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/cli/server.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/getting-started.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/guide/async.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/guide/crdt.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/guide/persistence.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/guide/querying.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/guide/tables.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/guide/watching.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/index.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/migrations.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/package.json +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/playwright.config.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/tests/integration/sidebar.spec.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/docs/vitest.config.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/README.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/conftest.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/api/index.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/cli/config.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/cli/http-api.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/cli/index.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/cli/init.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/cli/server.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/guide/async.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/guide/crdt.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/guide/persistence.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/guide/querying.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/guide/tables.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/guide/watching.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/index.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/migrations.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/package.json +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/playwright.config.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/tests/integration/sidebar.spec.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/vitest.config.ts +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/src/lib.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/tests/binding/__init__.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/python/tests/smoke/__init__.py +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/Cargo.toml +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/README.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/api/index.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/cli/config.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/cli/http-api.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/cli/index.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/cli/init.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/cli/server.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/guide/async.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/guide/crdt.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/guide/persistence.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/guide/querying.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/guide/tables.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/guide/watching.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/index.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/docs/migrations.md +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/command.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/config.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/lib.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.3.56 → dirsql-0.3.57}/pyproject.toml +0 -0
|
@@ -4,7 +4,7 @@ name = "dirsql-py-ext"
|
|
|
4
4
|
# pypi/maturin handler can rewrite it via `write-version` before
|
|
5
5
|
# `maturin build`. `pyproject.toml` declares `dynamic = ["version"]`
|
|
6
6
|
# and maturin reads this field. Mirrors `packages/rust/Cargo.toml`.
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.57"
|
|
8
8
|
edition.workspace = true
|
|
9
9
|
publish = false
|
|
10
10
|
readme = "README.md"
|
|
@@ -301,11 +301,21 @@ impl Db {
|
|
|
301
301
|
/// The user-row deletes and the matching `_dirsql_internal_rows` mapping
|
|
302
302
|
/// deletes commit in ONE transaction (epic #358), so the mapping never
|
|
303
303
|
/// outlives the rows it describes.
|
|
304
|
+
///
|
|
305
|
+
/// Stage 2 (epic #358): row ownership is read from the mapping, not the
|
|
306
|
+
/// injected `_dirsql_file_path` column (now write-only). The user rows to
|
|
307
|
+
/// drop are those whose `rowid` the mapping attributes to `file_path` under
|
|
308
|
+
/// `table`; the mapping rows are then removed in the same transaction.
|
|
304
309
|
pub fn delete_rows_by_file(&self, table: &str, file_path: &str) -> Result<usize> {
|
|
305
310
|
validate_identifier(table)?;
|
|
306
311
|
let tx = self.conn.unchecked_transaction()?;
|
|
307
|
-
let sql = format!(
|
|
308
|
-
|
|
312
|
+
let sql = format!(
|
|
313
|
+
"DELETE FROM {} WHERE rowid IN \
|
|
314
|
+
(SELECT rowid_ref FROM _dirsql_internal_rows \
|
|
315
|
+
WHERE table_name = ?1 AND file_path = ?2)",
|
|
316
|
+
table
|
|
317
|
+
);
|
|
318
|
+
let count = tx.execute(&sql, rusqlite::params![table, file_path])?;
|
|
309
319
|
tx.execute(
|
|
310
320
|
"DELETE FROM _dirsql_internal_rows WHERE table_name = ?1 AND file_path = ?2",
|
|
311
321
|
rusqlite::params![table, file_path],
|
|
@@ -1593,6 +1603,71 @@ mod tests {
|
|
|
1593
1603
|
db.check_row_mapping_equivalence("t").unwrap();
|
|
1594
1604
|
}
|
|
1595
1605
|
|
|
1606
|
+
#[test]
|
|
1607
|
+
fn delete_rows_by_file_reads_mapping_not_columns() {
|
|
1608
|
+
// Stage 2 (epic #358): delete-by-file resolves ownership through the
|
|
1609
|
+
// mapping, not the now-write-only `_dirsql_file_path` column. Corrupt
|
|
1610
|
+
// the column so it disagrees with the mapping and confirm the delete
|
|
1611
|
+
// still follows the mapping.
|
|
1612
|
+
let db = Db::new().unwrap();
|
|
1613
|
+
db.create_table("CREATE TABLE t (id TEXT)").unwrap();
|
|
1614
|
+
db.insert_row(
|
|
1615
|
+
"t",
|
|
1616
|
+
&HashMap::from([("id".into(), Value::Text("a".into()))]),
|
|
1617
|
+
"real.json",
|
|
1618
|
+
0,
|
|
1619
|
+
)
|
|
1620
|
+
.unwrap();
|
|
1621
|
+
// Desync the write-only column from the mapping.
|
|
1622
|
+
db.conn
|
|
1623
|
+
.execute("UPDATE t SET _dirsql_file_path = 'wrong.json'", [])
|
|
1624
|
+
.unwrap();
|
|
1625
|
+
|
|
1626
|
+
// Deleting by the mapping's file removes the row; deleting by the
|
|
1627
|
+
// stale column value is a no-op.
|
|
1628
|
+
assert_eq!(db.delete_rows_by_file("t", "wrong.json").unwrap(), 0);
|
|
1629
|
+
assert_eq!(db.delete_rows_by_file("t", "real.json").unwrap(), 1);
|
|
1630
|
+
let remaining: i64 = db
|
|
1631
|
+
.conn
|
|
1632
|
+
.query_row("SELECT COUNT(*) FROM t", [], |r| r.get(0))
|
|
1633
|
+
.unwrap();
|
|
1634
|
+
assert_eq!(remaining, 0);
|
|
1635
|
+
db.check_row_mapping_equivalence("t").unwrap();
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
#[test]
|
|
1639
|
+
fn delete_rows_by_file_is_scoped_to_its_table() {
|
|
1640
|
+
// The mapping subquery is keyed on (table_name, file_path): a delete on
|
|
1641
|
+
// one table must not touch another table's rows for the same file path,
|
|
1642
|
+
// even when they share a rowid.
|
|
1643
|
+
let db = Db::new().unwrap();
|
|
1644
|
+
db.create_table("CREATE TABLE t1 (id TEXT)").unwrap();
|
|
1645
|
+
db.create_table("CREATE TABLE t2 (id TEXT)").unwrap();
|
|
1646
|
+
db.insert_row(
|
|
1647
|
+
"t1",
|
|
1648
|
+
&HashMap::from([("id".into(), Value::Text("x".into()))]),
|
|
1649
|
+
"shared.json",
|
|
1650
|
+
0,
|
|
1651
|
+
)
|
|
1652
|
+
.unwrap();
|
|
1653
|
+
db.insert_row(
|
|
1654
|
+
"t2",
|
|
1655
|
+
&HashMap::from([("id".into(), Value::Text("y".into()))]),
|
|
1656
|
+
"shared.json",
|
|
1657
|
+
0,
|
|
1658
|
+
)
|
|
1659
|
+
.unwrap();
|
|
1660
|
+
|
|
1661
|
+
assert_eq!(db.delete_rows_by_file("t1", "shared.json").unwrap(), 1);
|
|
1662
|
+
let t2_rows: i64 = db
|
|
1663
|
+
.conn
|
|
1664
|
+
.query_row("SELECT COUNT(*) FROM t2", [], |r| r.get(0))
|
|
1665
|
+
.unwrap();
|
|
1666
|
+
assert_eq!(t2_rows, 1, "t2's row for the same path must be untouched");
|
|
1667
|
+
db.check_row_mapping_equivalence("t1").unwrap();
|
|
1668
|
+
db.check_row_mapping_equivalence("t2").unwrap();
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1596
1671
|
#[test]
|
|
1597
1672
|
fn failed_row_insert_leaves_no_mapping_row() {
|
|
1598
1673
|
// Transactional coupling, forward direction: when the user-row insert
|
|
@@ -384,9 +384,15 @@ pub fn drop_user_tables(conn: &Connection) -> rusqlite::Result<()> {
|
|
|
384
384
|
Ok(())
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
/// Look up the
|
|
388
|
-
///
|
|
389
|
-
///
|
|
387
|
+
/// Look up the row store's rows for one file, ordered by row index. Used on
|
|
388
|
+
/// warm start to rebuild the in-memory `file_rows` cache that the watcher's
|
|
389
|
+
/// diffing path requires.
|
|
390
|
+
///
|
|
391
|
+
/// Stage 2 (epic #358): ownership and ordering are read from the
|
|
392
|
+
/// `_dirsql_internal_rows` mapping (joined on `rowid`), not the injected
|
|
393
|
+
/// `_dirsql_file_path` / `_dirsql_row_index` columns (now write-only). User
|
|
394
|
+
/// columns are qualified with the table alias so a user column named like a
|
|
395
|
+
/// mapping column stays unambiguous.
|
|
390
396
|
pub fn read_rows_for_file(
|
|
391
397
|
conn: &Connection,
|
|
392
398
|
table: &str,
|
|
@@ -395,17 +401,19 @@ pub fn read_rows_for_file(
|
|
|
395
401
|
) -> rusqlite::Result<Vec<HashMap<String, crate::db::Value>>> {
|
|
396
402
|
let mut col_list = user_columns
|
|
397
403
|
.iter()
|
|
398
|
-
.map(|c| format!("
|
|
404
|
+
.map(|c| format!("t.\"{c}\""))
|
|
399
405
|
.collect::<Vec<_>>()
|
|
400
406
|
.join(", ");
|
|
401
407
|
if col_list.is_empty() {
|
|
402
408
|
col_list = "1".to_string();
|
|
403
409
|
}
|
|
404
410
|
let sql = format!(
|
|
405
|
-
"SELECT {col_list} FROM \"{table}\"
|
|
411
|
+
"SELECT {col_list} FROM \"{table}\" AS t \
|
|
412
|
+
JOIN _dirsql_internal_rows AS m ON m.rowid_ref = t.rowid \
|
|
413
|
+
WHERE m.table_name = ?1 AND m.file_path = ?2 ORDER BY m.row_index"
|
|
406
414
|
);
|
|
407
415
|
let mut stmt = conn.prepare(&sql)?;
|
|
408
|
-
let rows = stmt.query_map(params![rel_path], |row| {
|
|
416
|
+
let rows = stmt.query_map(params![table, rel_path], |row| {
|
|
409
417
|
let mut map = HashMap::new();
|
|
410
418
|
for (i, name) in user_columns.iter().enumerate() {
|
|
411
419
|
let v: rusqlite::types::Value = row.get(i)?;
|
|
@@ -673,6 +681,18 @@ mod tests {
|
|
|
673
681
|
assert_eq!(system_time_to_ns(None), 0);
|
|
674
682
|
}
|
|
675
683
|
|
|
684
|
+
/// Declare the internal mapping table on a raw connection (the `db` helper
|
|
685
|
+
/// would break unit isolation). Stage 2 reads through this table.
|
|
686
|
+
fn create_internal_rows_table(conn: &Connection) {
|
|
687
|
+
conn.execute(
|
|
688
|
+
"CREATE TABLE _dirsql_internal_rows (
|
|
689
|
+
table_name TEXT NOT NULL, file_path TEXT NOT NULL,
|
|
690
|
+
row_index INTEGER NOT NULL, rowid_ref INTEGER NOT NULL)",
|
|
691
|
+
[],
|
|
692
|
+
)
|
|
693
|
+
.unwrap();
|
|
694
|
+
}
|
|
695
|
+
|
|
676
696
|
#[test]
|
|
677
697
|
fn read_rows_for_file_handles_empty_user_columns() {
|
|
678
698
|
// With no user columns the SELECT list collapses to `1`, so the
|
|
@@ -683,11 +703,19 @@ mod tests {
|
|
|
683
703
|
[],
|
|
684
704
|
)
|
|
685
705
|
.unwrap();
|
|
706
|
+
create_internal_rows_table(&conn);
|
|
686
707
|
conn.execute(
|
|
687
708
|
"INSERT INTO rows (_dirsql_file_path, _dirsql_row_index) VALUES ('f.csv', 0), ('f.csv', 1)",
|
|
688
709
|
[],
|
|
689
710
|
)
|
|
690
711
|
.unwrap();
|
|
712
|
+
// Map the two inserted rows (rowids 1, 2) to f.csv.
|
|
713
|
+
conn.execute(
|
|
714
|
+
"INSERT INTO _dirsql_internal_rows (table_name, file_path, row_index, rowid_ref) \
|
|
715
|
+
VALUES ('rows', 'f.csv', 0, 1), ('rows', 'f.csv', 1, 2)",
|
|
716
|
+
[],
|
|
717
|
+
)
|
|
718
|
+
.unwrap();
|
|
691
719
|
let rows = read_rows_for_file(&conn, "rows", "f.csv", &[]).unwrap();
|
|
692
720
|
assert_eq!(rows.len(), 2);
|
|
693
721
|
assert!(rows[0].is_empty());
|
|
@@ -701,17 +729,59 @@ mod tests {
|
|
|
701
729
|
[],
|
|
702
730
|
)
|
|
703
731
|
.unwrap();
|
|
732
|
+
create_internal_rows_table(&conn);
|
|
704
733
|
conn.execute(
|
|
705
734
|
"INSERT INTO rows (col, _dirsql_file_path, _dirsql_row_index) VALUES ('a', 'f.csv', 0), ('b', 'f.csv', 1), ('c', 'g.csv', 0)",
|
|
706
735
|
[],
|
|
707
736
|
)
|
|
708
737
|
.unwrap();
|
|
738
|
+
// rowids 1, 2 belong to f.csv; rowid 3 to g.csv.
|
|
739
|
+
conn.execute(
|
|
740
|
+
"INSERT INTO _dirsql_internal_rows (table_name, file_path, row_index, rowid_ref) \
|
|
741
|
+
VALUES ('rows', 'f.csv', 0, 1), ('rows', 'f.csv', 1, 2), ('rows', 'g.csv', 0, 3)",
|
|
742
|
+
[],
|
|
743
|
+
)
|
|
744
|
+
.unwrap();
|
|
709
745
|
let rows = read_rows_for_file(&conn, "rows", "f.csv", &["col".to_string()]).unwrap();
|
|
710
746
|
assert_eq!(rows.len(), 2);
|
|
711
747
|
assert_eq!(rows[0]["col"], crate::db::Value::Text("a".into()));
|
|
712
748
|
assert_eq!(rows[1]["col"], crate::db::Value::Text("b".into()));
|
|
713
749
|
}
|
|
714
750
|
|
|
751
|
+
#[test]
|
|
752
|
+
fn read_rows_for_file_reads_mapping_not_columns() {
|
|
753
|
+
// Stage 2: ownership + ordering come from the mapping, not the
|
|
754
|
+
// write-only injected columns. Store a row whose `_dirsql_file_path` /
|
|
755
|
+
// `_dirsql_row_index` columns DISAGREE with the mapping, and confirm
|
|
756
|
+
// the read follows the mapping.
|
|
757
|
+
let conn = Connection::open_in_memory().unwrap();
|
|
758
|
+
conn.execute(
|
|
759
|
+
"CREATE TABLE rows (col TEXT, _dirsql_file_path TEXT, _dirsql_row_index INTEGER)",
|
|
760
|
+
[],
|
|
761
|
+
)
|
|
762
|
+
.unwrap();
|
|
763
|
+
create_internal_rows_table(&conn);
|
|
764
|
+
// Column says wrong.csv / index 9; mapping says right.csv / index 0.
|
|
765
|
+
conn.execute(
|
|
766
|
+
"INSERT INTO rows (col, _dirsql_file_path, _dirsql_row_index) VALUES ('a', 'wrong.csv', 9)",
|
|
767
|
+
[],
|
|
768
|
+
)
|
|
769
|
+
.unwrap();
|
|
770
|
+
conn.execute(
|
|
771
|
+
"INSERT INTO _dirsql_internal_rows (table_name, file_path, row_index, rowid_ref) \
|
|
772
|
+
VALUES ('rows', 'right.csv', 0, 1)",
|
|
773
|
+
[],
|
|
774
|
+
)
|
|
775
|
+
.unwrap();
|
|
776
|
+
// Querying the mapping's file returns the row...
|
|
777
|
+
let hit = read_rows_for_file(&conn, "rows", "right.csv", &["col".to_string()]).unwrap();
|
|
778
|
+
assert_eq!(hit.len(), 1);
|
|
779
|
+
assert_eq!(hit[0]["col"], crate::db::Value::Text("a".into()));
|
|
780
|
+
// ...and the column's (stale) file returns nothing.
|
|
781
|
+
let miss = read_rows_for_file(&conn, "rows", "wrong.csv", &["col".to_string()]).unwrap();
|
|
782
|
+
assert!(miss.is_empty());
|
|
783
|
+
}
|
|
784
|
+
|
|
715
785
|
// --- rusqlite error propagation (`?` arms) ---
|
|
716
786
|
//
|
|
717
787
|
// Each sidecar accessor runs against a `_dirsql_*` table. Calling it on a
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dirsql-0.3.56 → dirsql-0.3.57}/packages/python/docs/tests/integration/language-flag.spec.ts
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|