sqlrite 0.5.0__tar.gz → 0.5.1__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.
- {sqlrite-0.5.0 → sqlrite-0.5.1}/Cargo.lock +7 -7
- {sqlrite-0.5.0 → sqlrite-0.5.1}/Cargo.toml +2 -2
- {sqlrite-0.5.0 → sqlrite-0.5.1}/PKG-INFO +1 -1
- {sqlrite-0.5.0 → sqlrite-0.5.1}/desktop/package.json +1 -1
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/file-format.md +2 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/pyproject.toml +1 -1
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sdk/python/Cargo.toml +1 -1
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sqlrite-ask/Cargo.toml +1 -1
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/mod.rs +195 -26
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/overflow.rs +84 -2
- {sqlrite-0.5.0 → sqlrite-0.5.1}/.github/workflows/ci.yml +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/.github/workflows/release-pr.yml +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/.github/workflows/release.yml +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/.github/workflows/rust.yml +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/.gitignore +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/CLAUDE.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/CODE_OF_CONDUCT.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/LICENSE +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/MAINTAINERS +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/Makefile +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/README.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/desktop/index.html +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/desktop/package-lock.json +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/desktop/src/App.svelte +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/desktop/src/app.css +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/desktop/src/main.ts +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/desktop/src/vite-env.d.ts +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/desktop/svelte.config.js +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/desktop/tsconfig.json +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/desktop/vite.config.ts +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/_index.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/architecture.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/ask-backend-examples.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/ask.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/design-decisions.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/desktop.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/embedding.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/fts.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/getting-started.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/mcp.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/pager.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/phase-7-plan.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/phase-8-plan.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/release-plan.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/release-secrets.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/roadmap.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/smoke-test.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/sql-engine.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/storage-model.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/supported-sql.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/docs/usage.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/examples/README.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/examples/c/Makefile +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/examples/c/hello.c +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/examples/go/go.mod +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/examples/go/hello.go +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/examples/hybrid-retrieval/README.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/examples/hybrid-retrieval/hybrid_retrieval.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/examples/nodejs/hello.mjs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/examples/python/hello.py +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/examples/rust/quickstart.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/examples/wasm/Makefile +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/examples/wasm/index.html +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/examples/wasm/server.mjs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/images/SQLRite - Desktop.png +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/images/SQLRite Data Structures.png +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/images/SQLRite Simple SQL Execution High Level Diagram.png +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/images/SQLRite Simple SQL INSERT Execution High Level Diagram (Insert Row).png +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/images/SQLRite Simple SQL INSERT Execution High Level Diagram.png +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/images/SQLRite_logo.png +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/images/architecture.png +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/rust-toolchain.toml +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/samples/AST.delete.example +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/samples/AST.insert.exemple +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/samples/AST.select.example +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/samples/AST.update.example +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/samples/CREATE TABLE sqlrite_schema.sql +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/samples/CREATE_TABLE with duplicate.sql +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/samples/CREATE_TABLE.sql +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/samples/INSERT.sql +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/scripts/bump-version.sh +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sdk/go/README.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sdk/go/ask.go +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sdk/go/ask_test.go +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sdk/go/conn.go +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sdk/go/go.mod +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sdk/go/rows.go +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sdk/go/sqlrite.go +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sdk/go/sqlrite_test.go +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sdk/go/stmt.go +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sdk/python/README.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sdk/python/src/lib.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sdk/python/tests/test_ask.py +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sdk/python/tests/test_sqlrite.py +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sqlrite-ask/README.md +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sqlrite-ask/src/lib.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sqlrite-ask/src/prompt.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sqlrite-ask/src/provider/anthropic.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sqlrite-ask/src/provider/mock.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sqlrite-ask/src/provider/mod.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/sqlrite-ask/tests/anthropic_http.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/ask/mod.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/ask/schema.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/connection.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/error.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/lib.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/main.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/meta_command/mod.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/repl/mod.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/db/database.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/db/mod.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/db/secondary_index.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/db/table.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/executor.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/fts/bm25.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/fts/mod.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/fts/posting_list.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/fts/tokenizer.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/hnsw.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/mod.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/allocator.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/cell.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/file.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/freelist.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/fts_cell.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/header.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/hnsw_cell.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/index_cell.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/interior_page.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/page.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/pager.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/table_page.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/varint.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/pager/wal.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/parser/create.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/parser/insert.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/parser/mod.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/parser/select.rs +0 -0
- {sqlrite-0.5.0 → sqlrite-0.5.1}/src/sql/tokenizer.rs +0 -0
|
@@ -3817,7 +3817,7 @@ dependencies = [
|
|
|
3817
3817
|
|
|
3818
3818
|
[[package]]
|
|
3819
3819
|
name = "sqlrite-ask"
|
|
3820
|
-
version = "0.5.
|
|
3820
|
+
version = "0.5.1"
|
|
3821
3821
|
dependencies = [
|
|
3822
3822
|
"serde",
|
|
3823
3823
|
"serde_json",
|
|
@@ -3828,7 +3828,7 @@ dependencies = [
|
|
|
3828
3828
|
|
|
3829
3829
|
[[package]]
|
|
3830
3830
|
name = "sqlrite-desktop"
|
|
3831
|
-
version = "0.5.
|
|
3831
|
+
version = "0.5.1"
|
|
3832
3832
|
dependencies = [
|
|
3833
3833
|
"serde",
|
|
3834
3834
|
"serde_json",
|
|
@@ -3840,7 +3840,7 @@ dependencies = [
|
|
|
3840
3840
|
|
|
3841
3841
|
[[package]]
|
|
3842
3842
|
name = "sqlrite-engine"
|
|
3843
|
-
version = "0.5.
|
|
3843
|
+
version = "0.5.1"
|
|
3844
3844
|
dependencies = [
|
|
3845
3845
|
"clap",
|
|
3846
3846
|
"env_logger",
|
|
@@ -3857,7 +3857,7 @@ dependencies = [
|
|
|
3857
3857
|
|
|
3858
3858
|
[[package]]
|
|
3859
3859
|
name = "sqlrite-ffi"
|
|
3860
|
-
version = "0.5.
|
|
3860
|
+
version = "0.5.1"
|
|
3861
3861
|
dependencies = [
|
|
3862
3862
|
"cbindgen",
|
|
3863
3863
|
"serde",
|
|
@@ -3867,7 +3867,7 @@ dependencies = [
|
|
|
3867
3867
|
|
|
3868
3868
|
[[package]]
|
|
3869
3869
|
name = "sqlrite-mcp"
|
|
3870
|
-
version = "0.5.
|
|
3870
|
+
version = "0.5.1"
|
|
3871
3871
|
dependencies = [
|
|
3872
3872
|
"clap",
|
|
3873
3873
|
"libc",
|
|
@@ -3878,7 +3878,7 @@ dependencies = [
|
|
|
3878
3878
|
|
|
3879
3879
|
[[package]]
|
|
3880
3880
|
name = "sqlrite-nodejs"
|
|
3881
|
-
version = "0.5.
|
|
3881
|
+
version = "0.5.1"
|
|
3882
3882
|
dependencies = [
|
|
3883
3883
|
"napi",
|
|
3884
3884
|
"napi-build",
|
|
@@ -3888,7 +3888,7 @@ dependencies = [
|
|
|
3888
3888
|
|
|
3889
3889
|
[[package]]
|
|
3890
3890
|
name = "sqlrite-python"
|
|
3891
|
-
version = "0.5.
|
|
3891
|
+
version = "0.5.1"
|
|
3892
3892
|
dependencies = [
|
|
3893
3893
|
"pyo3",
|
|
3894
3894
|
"sqlrite-engine",
|
|
@@ -27,7 +27,7 @@ resolver = "3"
|
|
|
27
27
|
# `package =` key so the import name stays `sqlrite` internally:
|
|
28
28
|
# sqlrite = { package = "sqlrite-engine", path = "…" }
|
|
29
29
|
name = "sqlrite-engine"
|
|
30
|
-
version = "0.5.
|
|
30
|
+
version = "0.5.1"
|
|
31
31
|
authors = ["Joao Henrique Machado Silva <joaoh82@gmail.com>"]
|
|
32
32
|
edition = "2024"
|
|
33
33
|
rust-version = "1.85"
|
|
@@ -138,4 +138,4 @@ fs2 = { version = "0.4", optional = true }
|
|
|
138
138
|
# crate publishes to crates.io, and a path-only dep without a
|
|
139
139
|
# version field fails the manifest verification step. See PR #58
|
|
140
140
|
# retrospective in docs/roadmap.md.
|
|
141
|
-
sqlrite-ask = { version = "0.5.
|
|
141
|
+
sqlrite-ask = { version = "0.5.1", path = "sqlrite-ask", optional = true }
|
|
@@ -139,6 +139,8 @@ body variable depends on kind_tag
|
|
|
139
139
|
|
|
140
140
|
The shared prefix means `Cell::peek_rowid` works uniformly across all kinds — useful for binary search over a page's slot directory without decoding full bodies.
|
|
141
141
|
|
|
142
|
+
**Decoder dispatch is per-B-tree, not per-cell.** Each B-Tree owns one cell kind on its leaves: table B-Trees carry `Local`/`Overflow` cells (decoded via `PagedEntry::decode`), secondary-index B-Trees carry `Index` cells (`IndexCell::decode`), HNSW carries `HNSW` cells (`HnswNodeCell::decode`), FTS carries `FTS Posting` cells (`FtsPostingCell::decode`), and every interior node — regardless of the leaf kind below it — carries `Interior` divider cells (`InteriorCell::decode`). Callers must dispatch on the *page's owning B-Tree*, not on the kind tag they happen to see; pointing the wrong decoder at a leaf yields an `Internal` error that names both the offending kind and the B-Tree it belongs to (SQLR-1).
|
|
143
|
+
|
|
142
144
|
### Local cell body
|
|
143
145
|
|
|
144
146
|
```
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sqlrite"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.1"
|
|
8
8
|
description = "Python bindings for SQLRite — a small, embeddable SQLite clone written in Rust."
|
|
9
9
|
authors = [{ name = "Joao Henrique Machado Silva", email = "joaoh82@gmail.com" }]
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
# Published to crates.io as `sqlrite-ask`. Joins the lockstep release
|
|
11
11
|
# wave (`sqlrite-ask-vX.Y.Z` tag) — see `docs/release-plan.md`.
|
|
12
12
|
name = "sqlrite-ask"
|
|
13
|
-
version = "0.5.
|
|
13
|
+
version = "0.5.1"
|
|
14
14
|
authors = ["Joao Henrique Machado Silva <joaoh82@gmail.com>"]
|
|
15
15
|
edition = "2024"
|
|
16
16
|
rust-version = "1.85"
|
|
@@ -267,6 +267,43 @@ fn save_database_with_mode(db: &mut Database, path: &Path, compact: bool) -> Res
|
|
|
267
267
|
read_old_rootpages(&pager, old_header.schema_root_page)?
|
|
268
268
|
};
|
|
269
269
|
|
|
270
|
+
// SQLR-1 — snapshot every prior B-Tree's page set NOW, before any
|
|
271
|
+
// staging starts. `Pager::read_page` shadows on-disk bytes with the
|
|
272
|
+
// current `staged` buffer, so if we deferred these walks until each
|
|
273
|
+
// object's turn in the staging loop, a *new* index added in this
|
|
274
|
+
// save would extend past the old high-water and overwrite the
|
|
275
|
+
// pages of any later-staged object whose old root sits in that
|
|
276
|
+
// range — including `sqlrite_master`, which is always staged last.
|
|
277
|
+
// The follow-up walk would then read the wrong B-Tree's bytes and
|
|
278
|
+
// either hand the allocator a bogus preferred pool or panic
|
|
279
|
+
// dispatching cells (a table-cell decoder vs. an index leaf, the
|
|
280
|
+
// shape of the original SQLR-1 panic). Walking up front pins each
|
|
281
|
+
// map to the committed bytes that were on disk before this save
|
|
282
|
+
// touched anything.
|
|
283
|
+
let old_preferred_pages: HashMap<(String, String), Vec<u32>> = if compact {
|
|
284
|
+
HashMap::new()
|
|
285
|
+
} else {
|
|
286
|
+
let mut map: HashMap<(String, String), Vec<u32>> = HashMap::new();
|
|
287
|
+
for ((kind, name), &root) in &old_rootpages {
|
|
288
|
+
// Tables can carry overflow chains; index/HNSW/FTS leaves
|
|
289
|
+
// never overflow in the current encoding, so the cheaper
|
|
290
|
+
// walk suffices for them.
|
|
291
|
+
let follow = kind == "table";
|
|
292
|
+
let pages = collect_pages_for_btree(&pager, root, follow)?;
|
|
293
|
+
map.insert((kind.clone(), name.clone()), pages);
|
|
294
|
+
}
|
|
295
|
+
map
|
|
296
|
+
};
|
|
297
|
+
let old_master_pages: Vec<u32> = if compact || old_header.schema_root_page == 0 {
|
|
298
|
+
Vec::new()
|
|
299
|
+
} else {
|
|
300
|
+
collect_pages_for_btree(
|
|
301
|
+
&pager,
|
|
302
|
+
old_header.schema_root_page,
|
|
303
|
+
/*follow_overflow=*/ true,
|
|
304
|
+
)?
|
|
305
|
+
};
|
|
306
|
+
|
|
270
307
|
pager.clear_staged();
|
|
271
308
|
|
|
272
309
|
// Allocator: in normal mode, seed with the old freelist; in compact
|
|
@@ -292,10 +329,8 @@ fn save_database_with_mode(db: &mut Database, path: &Path, compact: bool) -> Res
|
|
|
292
329
|
)));
|
|
293
330
|
}
|
|
294
331
|
if !compact {
|
|
295
|
-
if let Some(
|
|
296
|
-
|
|
297
|
-
collect_pages_for_btree(&pager, prev_root, /*follow_overflow=*/ true)?;
|
|
298
|
-
alloc.set_preferred(prev);
|
|
332
|
+
if let Some(prev) = old_preferred_pages.get(&("table".to_string(), name.to_string())) {
|
|
333
|
+
alloc.set_preferred(prev.clone());
|
|
299
334
|
}
|
|
300
335
|
}
|
|
301
336
|
let table = &db.tables[name];
|
|
@@ -322,12 +357,10 @@ fn save_database_with_mode(db: &mut Database, path: &Path, compact: bool) -> Res
|
|
|
322
357
|
.sort_by(|(ta, ia), (tb, ib)| ta.tb_name.cmp(&tb.tb_name).then(ia.name.cmp(&ib.name)));
|
|
323
358
|
for (_table, idx) in index_entries {
|
|
324
359
|
if !compact {
|
|
325
|
-
if let Some(
|
|
326
|
-
|
|
360
|
+
if let Some(prev) =
|
|
361
|
+
old_preferred_pages.get(&("index".to_string(), idx.name.to_string()))
|
|
327
362
|
{
|
|
328
|
-
|
|
329
|
-
collect_pages_for_btree(&pager, prev_root, /*follow_overflow=*/ false)?;
|
|
330
|
-
alloc.set_preferred(prev);
|
|
363
|
+
alloc.set_preferred(prev.clone());
|
|
331
364
|
}
|
|
332
365
|
}
|
|
333
366
|
let rootpage = stage_index_btree(&mut pager, idx, &mut alloc)?;
|
|
@@ -359,12 +392,10 @@ fn save_database_with_mode(db: &mut Database, path: &Path, compact: bool) -> Res
|
|
|
359
392
|
.sort_by(|(ta, ea), (tb, eb)| ta.tb_name.cmp(&tb.tb_name).then(ea.name.cmp(&eb.name)));
|
|
360
393
|
for (table, entry) in hnsw_entries {
|
|
361
394
|
if !compact {
|
|
362
|
-
if let Some(
|
|
363
|
-
|
|
395
|
+
if let Some(prev) =
|
|
396
|
+
old_preferred_pages.get(&("index".to_string(), entry.name.to_string()))
|
|
364
397
|
{
|
|
365
|
-
|
|
366
|
-
collect_pages_for_btree(&pager, prev_root, /*follow_overflow=*/ false)?;
|
|
367
|
-
alloc.set_preferred(prev);
|
|
398
|
+
alloc.set_preferred(prev.clone());
|
|
368
399
|
}
|
|
369
400
|
}
|
|
370
401
|
let rootpage = stage_hnsw_btree(&mut pager, &entry.index, &mut alloc)?;
|
|
@@ -401,12 +432,10 @@ fn save_database_with_mode(db: &mut Database, path: &Path, compact: bool) -> Res
|
|
|
401
432
|
let any_fts = !fts_entries.is_empty();
|
|
402
433
|
for (table, entry) in fts_entries {
|
|
403
434
|
if !compact {
|
|
404
|
-
if let Some(
|
|
405
|
-
|
|
435
|
+
if let Some(prev) =
|
|
436
|
+
old_preferred_pages.get(&("index".to_string(), entry.name.to_string()))
|
|
406
437
|
{
|
|
407
|
-
|
|
408
|
-
collect_pages_for_btree(&pager, prev_root, /*follow_overflow=*/ false)?;
|
|
409
|
-
alloc.set_preferred(prev);
|
|
438
|
+
alloc.set_preferred(prev.clone());
|
|
410
439
|
}
|
|
411
440
|
}
|
|
412
441
|
let rootpage = stage_fts_btree(&mut pager, &entry.index, &mut alloc)?;
|
|
@@ -442,13 +471,11 @@ fn save_database_with_mode(db: &mut Database, path: &Path, compact: bool) -> Res
|
|
|
442
471
|
],
|
|
443
472
|
)?;
|
|
444
473
|
}
|
|
445
|
-
if !compact &&
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
)?;
|
|
451
|
-
alloc.set_preferred(prev);
|
|
474
|
+
if !compact && !old_master_pages.is_empty() {
|
|
475
|
+
// Use the page list snapshotted before any staging touched
|
|
476
|
+
// disk; re-walking here would read whatever a new index
|
|
477
|
+
// already restaged on top of master's old root (SQLR-1).
|
|
478
|
+
alloc.set_preferred(old_master_pages.clone());
|
|
452
479
|
}
|
|
453
480
|
let master_root = stage_table_btree(&mut pager, &master, &mut alloc)?;
|
|
454
481
|
alloc.finish_preferred();
|
|
@@ -2723,6 +2750,148 @@ mod tests {
|
|
|
2723
2750
|
cleanup(&path);
|
|
2724
2751
|
}
|
|
2725
2752
|
|
|
2753
|
+
/// SQLR-1 — `CREATE INDEX` on a wide table must round-trip when the
|
|
2754
|
+
/// index B-tree grows past one leaf and needs an interior level.
|
|
2755
|
+
/// Before the fix, the post-DDL auto-save panicked with
|
|
2756
|
+
/// `Internal("unknown paged-entry kind tag 0x4 …")` because a
|
|
2757
|
+
/// table-cell decoder was being run against an index leaf
|
|
2758
|
+
/// (`KIND_INDEX = 0x04`).
|
|
2759
|
+
///
|
|
2760
|
+
/// 5 000 rows mirror the original repro from the issue and exceed
|
|
2761
|
+
/// every leaf-fanout cliff for the small `(rowid, value)` cells in
|
|
2762
|
+
/// a TEXT-keyed secondary index.
|
|
2763
|
+
#[test]
|
|
2764
|
+
fn secondary_index_with_interior_level_round_trips() {
|
|
2765
|
+
let path = tmp_path("sqlr1_wide_index");
|
|
2766
|
+
let mut db = Database::new("idx".to_string());
|
|
2767
|
+
db.source_path = Some(path.clone());
|
|
2768
|
+
|
|
2769
|
+
process_command(
|
|
2770
|
+
"CREATE TABLE bloat (id INTEGER PRIMARY KEY, payload TEXT);",
|
|
2771
|
+
&mut db,
|
|
2772
|
+
)
|
|
2773
|
+
.unwrap();
|
|
2774
|
+
// BEGIN/COMMIT collapses 5 000 inserts into one save (matches
|
|
2775
|
+
// `auto_vacuum_setup` and the issue's repro shape).
|
|
2776
|
+
process_command("BEGIN;", &mut db).unwrap();
|
|
2777
|
+
for i in 0..5000 {
|
|
2778
|
+
process_command(
|
|
2779
|
+
&format!("INSERT INTO bloat (payload) VALUES ('p-{i:08}');"),
|
|
2780
|
+
&mut db,
|
|
2781
|
+
)
|
|
2782
|
+
.unwrap();
|
|
2783
|
+
}
|
|
2784
|
+
process_command("COMMIT;", &mut db).unwrap();
|
|
2785
|
+
|
|
2786
|
+
// The DDL that used to panic.
|
|
2787
|
+
process_command("CREATE INDEX idx_p ON bloat (payload);", &mut db).unwrap();
|
|
2788
|
+
|
|
2789
|
+
// Reopen and verify lookups, plus that the index tree actually
|
|
2790
|
+
// grew an interior layer (otherwise this test wouldn't cover the
|
|
2791
|
+
// regression).
|
|
2792
|
+
drop(db);
|
|
2793
|
+
let loaded = open_database(&path, "idx".to_string()).unwrap();
|
|
2794
|
+
let bloat = loaded.get_table("bloat".to_string()).unwrap();
|
|
2795
|
+
let idx = bloat
|
|
2796
|
+
.index_by_name("idx_p")
|
|
2797
|
+
.expect("idx_p should survive close/reopen");
|
|
2798
|
+
assert!(!idx.is_unique);
|
|
2799
|
+
|
|
2800
|
+
// Spot-check the keyspace: first, middle, last value each map
|
|
2801
|
+
// back to exactly the row that carried them.
|
|
2802
|
+
for &(probe_i, expected_rowid) in &[(0i64, 1i64), (2500, 2501), (4999, 5000)] {
|
|
2803
|
+
let value = Value::Text(format!("p-{probe_i:08}"));
|
|
2804
|
+
let hits = idx.lookup(&value);
|
|
2805
|
+
assert_eq!(
|
|
2806
|
+
hits,
|
|
2807
|
+
vec![expected_rowid],
|
|
2808
|
+
"lookup({value:?}) should yield rowid {expected_rowid}",
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
// Confirm the index tree is multi-level (the regression's
|
|
2813
|
+
// necessary condition) — root must be an `InteriorNode` and
|
|
2814
|
+
// `find_leftmost_leaf` must reach a `TableLeaf` through it.
|
|
2815
|
+
let pager = loaded.pager.as_ref().unwrap();
|
|
2816
|
+
let mut master = build_empty_master_table();
|
|
2817
|
+
load_table_rows(pager, &mut master, pager.header().schema_root_page).unwrap();
|
|
2818
|
+
let idx_root = master
|
|
2819
|
+
.rowids()
|
|
2820
|
+
.into_iter()
|
|
2821
|
+
.find_map(
|
|
2822
|
+
|r| match (master.get_value("name", r), master.get_value("type", r)) {
|
|
2823
|
+
(Some(Value::Text(name)), Some(Value::Text(kind)))
|
|
2824
|
+
if name == "idx_p" && kind == "index" =>
|
|
2825
|
+
{
|
|
2826
|
+
match master.get_value("rootpage", r) {
|
|
2827
|
+
Some(Value::Integer(p)) => Some(p as u32),
|
|
2828
|
+
_ => None,
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
_ => None,
|
|
2832
|
+
},
|
|
2833
|
+
)
|
|
2834
|
+
.expect("idx_p should appear in sqlrite_master");
|
|
2835
|
+
let root_buf = pager.read_page(idx_root).unwrap();
|
|
2836
|
+
assert_eq!(
|
|
2837
|
+
root_buf[0],
|
|
2838
|
+
PageType::InteriorNode as u8,
|
|
2839
|
+
"5 000-entry index must have an interior root — without one this test wouldn't cover SQLR-1",
|
|
2840
|
+
);
|
|
2841
|
+
let leaf = find_leftmost_leaf(pager, idx_root).unwrap();
|
|
2842
|
+
let leaf_buf = pager.read_page(leaf).unwrap();
|
|
2843
|
+
assert_eq!(leaf_buf[0], PageType::TableLeaf as u8);
|
|
2844
|
+
|
|
2845
|
+
cleanup(&path);
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
/// SQLR-1 follow-on — the page-recycling path between two large
|
|
2849
|
+
/// versions of the same index name must not corrupt cell decoding.
|
|
2850
|
+
/// `DROP INDEX` returns its pages to the freelist; the next
|
|
2851
|
+
/// `CREATE INDEX` is free to reuse them. If the allocator hands an
|
|
2852
|
+
/// old index leaf to a *table* without zeroing it, an upstream
|
|
2853
|
+
/// table walk would see KIND_INDEX cells and panic.
|
|
2854
|
+
#[test]
|
|
2855
|
+
fn drop_then_recreate_wide_index_does_not_panic() {
|
|
2856
|
+
let path = tmp_path("sqlr1_drop_recreate");
|
|
2857
|
+
let mut db = Database::new("idx".to_string());
|
|
2858
|
+
db.source_path = Some(path.clone());
|
|
2859
|
+
|
|
2860
|
+
process_command(
|
|
2861
|
+
"CREATE TABLE bloat (id INTEGER PRIMARY KEY, payload TEXT);",
|
|
2862
|
+
&mut db,
|
|
2863
|
+
)
|
|
2864
|
+
.unwrap();
|
|
2865
|
+
process_command("BEGIN;", &mut db).unwrap();
|
|
2866
|
+
for i in 0..5000 {
|
|
2867
|
+
process_command(
|
|
2868
|
+
&format!("INSERT INTO bloat (payload) VALUES ('p-{i:08}');"),
|
|
2869
|
+
&mut db,
|
|
2870
|
+
)
|
|
2871
|
+
.unwrap();
|
|
2872
|
+
}
|
|
2873
|
+
process_command("COMMIT;", &mut db).unwrap();
|
|
2874
|
+
|
|
2875
|
+
process_command("CREATE INDEX idx_p ON bloat (payload);", &mut db).unwrap();
|
|
2876
|
+
process_command("DROP INDEX idx_p;", &mut db).unwrap();
|
|
2877
|
+
// Recreate from scratch — exercises the recycle path.
|
|
2878
|
+
process_command("CREATE INDEX idx_p ON bloat (payload);", &mut db).unwrap();
|
|
2879
|
+
|
|
2880
|
+
drop(db);
|
|
2881
|
+
let loaded = open_database(&path, "idx".to_string()).unwrap();
|
|
2882
|
+
let bloat = loaded.get_table("bloat".to_string()).unwrap();
|
|
2883
|
+
let idx = bloat
|
|
2884
|
+
.index_by_name("idx_p")
|
|
2885
|
+
.expect("idx_p should survive drop+recreate+reopen");
|
|
2886
|
+
assert_eq!(
|
|
2887
|
+
idx.lookup(&Value::Text("p-00002500".into())),
|
|
2888
|
+
vec![2501],
|
|
2889
|
+
"post-recycle lookup must still resolve correctly",
|
|
2890
|
+
);
|
|
2891
|
+
|
|
2892
|
+
cleanup(&path);
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2726
2895
|
#[test]
|
|
2727
2896
|
fn deep_tree_round_trips() {
|
|
2728
2897
|
// Force a 3-level tree by bypassing process_command (which prints
|
|
@@ -139,8 +139,17 @@ impl PagedEntry {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/// Dispatches on the kind tag and returns the appropriate variant.
|
|
142
|
+
///
|
|
143
|
+
/// Only `KIND_LOCAL` and `KIND_OVERFLOW` are valid here — `PagedEntry`
|
|
144
|
+
/// is the table-leaf-cell type, so any other kind means a caller
|
|
145
|
+
/// pointed the wrong decoder at this page (the slot directory layout
|
|
146
|
+
/// is shared across leaf B-Trees, but secondary-index, HNSW, and
|
|
147
|
+
/// FTS leaves carry kind-specific cells decoded by `IndexCell::decode`,
|
|
148
|
+
/// `HnswNodeCell::decode`, and `FtsPostingCell::decode` respectively).
|
|
149
|
+
/// The named-kind error makes that mistake obvious next time.
|
|
142
150
|
pub fn decode(buf: &[u8], pos: usize) -> Result<(PagedEntry, usize)> {
|
|
143
|
-
|
|
151
|
+
let kind = Cell::peek_kind(buf, pos)?;
|
|
152
|
+
match kind {
|
|
144
153
|
KIND_LOCAL => {
|
|
145
154
|
let (c, n) = Cell::decode(buf, pos)?;
|
|
146
155
|
Ok((PagedEntry::Local(c), n))
|
|
@@ -150,12 +159,41 @@ impl PagedEntry {
|
|
|
150
159
|
Ok((PagedEntry::Overflow(r), n))
|
|
151
160
|
}
|
|
152
161
|
other => Err(SQLRiteError::Internal(format!(
|
|
153
|
-
"
|
|
162
|
+
"PagedEntry::decode at offset {pos} got kind tag {other:#x} ({}); \
|
|
163
|
+
expected KIND_LOCAL (0x01) or KIND_OVERFLOW (0x02). \
|
|
164
|
+
The caller is reading a {} page with the table-leaf decoder.",
|
|
165
|
+
kind_name(other),
|
|
166
|
+
kind_btree_hint(other),
|
|
154
167
|
))),
|
|
155
168
|
}
|
|
156
169
|
}
|
|
157
170
|
}
|
|
158
171
|
|
|
172
|
+
/// Human-readable label for a cell kind tag — used in error messages
|
|
173
|
+
/// to make wrong-decoder mistakes self-explanatory.
|
|
174
|
+
fn kind_name(tag: u8) -> &'static str {
|
|
175
|
+
match tag {
|
|
176
|
+
crate::sql::pager::cell::KIND_LOCAL => "KIND_LOCAL",
|
|
177
|
+
crate::sql::pager::cell::KIND_OVERFLOW => "KIND_OVERFLOW",
|
|
178
|
+
crate::sql::pager::cell::KIND_INTERIOR => "KIND_INTERIOR",
|
|
179
|
+
crate::sql::pager::cell::KIND_INDEX => "KIND_INDEX",
|
|
180
|
+
crate::sql::pager::cell::KIND_HNSW => "KIND_HNSW",
|
|
181
|
+
crate::sql::pager::cell::KIND_FTS_POSTING => "KIND_FTS_POSTING",
|
|
182
|
+
_ => "unknown kind",
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// Hint pointing at which B-Tree owns a cell of the given kind.
|
|
187
|
+
fn kind_btree_hint(tag: u8) -> &'static str {
|
|
188
|
+
match tag {
|
|
189
|
+
crate::sql::pager::cell::KIND_INTERIOR => "B-Tree interior",
|
|
190
|
+
crate::sql::pager::cell::KIND_INDEX => "secondary-index",
|
|
191
|
+
crate::sql::pager::cell::KIND_HNSW => "HNSW",
|
|
192
|
+
crate::sql::pager::cell::KIND_FTS_POSTING => "FTS",
|
|
193
|
+
_ => "non-table",
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
159
197
|
/// Writes `bytes` into a chain of Overflow-typed pages, drawing each
|
|
160
198
|
/// page number from the supplied [`PageAllocator`]. Returns the page
|
|
161
199
|
/// number of the first link in the chain (the value to record in the
|
|
@@ -284,6 +322,50 @@ mod tests {
|
|
|
284
322
|
assert_eq!(decoded, PagedEntry::Overflow(overflow));
|
|
285
323
|
}
|
|
286
324
|
|
|
325
|
+
/// SQLR-1 — `PagedEntry::decode` is the table-leaf-cell decoder.
|
|
326
|
+
/// Pointing it at a secondary-index leaf used to surface as a
|
|
327
|
+
/// cryptic `unknown paged-entry kind tag 0x4`. The new error
|
|
328
|
+
/// message names the offending kind and the B-Tree the caller
|
|
329
|
+
/// is mistakenly walking.
|
|
330
|
+
#[test]
|
|
331
|
+
fn paged_entry_decode_rejects_index_kind_with_clear_error() {
|
|
332
|
+
use crate::sql::pager::index_cell::IndexCell;
|
|
333
|
+
let ic = IndexCell::new(42, Value::Text("alice".into()));
|
|
334
|
+
let bytes = ic.encode().unwrap();
|
|
335
|
+
let err = PagedEntry::decode(&bytes, 0).unwrap_err();
|
|
336
|
+
let msg = format!("{err}");
|
|
337
|
+
assert!(
|
|
338
|
+
msg.contains("KIND_INDEX"),
|
|
339
|
+
"expected error to name KIND_INDEX, got: {msg}",
|
|
340
|
+
);
|
|
341
|
+
assert!(
|
|
342
|
+
msg.contains("secondary-index"),
|
|
343
|
+
"expected error to identify the secondary-index B-Tree, got: {msg}",
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/// Symmetric coverage for HNSW and FTS — same wrong-decoder shape,
|
|
348
|
+
/// same diagnostic guarantee.
|
|
349
|
+
#[test]
|
|
350
|
+
fn paged_entry_decode_rejects_hnsw_and_fts_kinds() {
|
|
351
|
+
use crate::sql::pager::cell::{KIND_FTS_POSTING, KIND_HNSW};
|
|
352
|
+
// Build minimal byte sequences carrying the right kind tag at
|
|
353
|
+
// the right offset. Body content past the kind tag doesn't
|
|
354
|
+
// matter — we expect the dispatch to short-circuit on the tag.
|
|
355
|
+
for (tag, hint) in [(KIND_HNSW, "HNSW"), (KIND_FTS_POSTING, "FTS")] {
|
|
356
|
+
// body_len declared as 1 (the kind tag itself); body is the
|
|
357
|
+
// tag and nothing else. Honest about what's in the buffer
|
|
358
|
+
// so a future tightening of `peek_kind` doesn't bite us.
|
|
359
|
+
let bytes = vec![/*body_len varint=*/ 1u8, tag];
|
|
360
|
+
let err = PagedEntry::decode(&bytes, 0).unwrap_err();
|
|
361
|
+
let msg = format!("{err}");
|
|
362
|
+
assert!(
|
|
363
|
+
msg.contains(hint),
|
|
364
|
+
"expected error to identify the {hint} B-Tree, got: {msg}",
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
287
369
|
#[test]
|
|
288
370
|
fn peek_rowid_works_for_both_kinds() {
|
|
289
371
|
let local = Cell::new(99, vec![Some(Value::Integer(1))]);
|
|
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
|
{sqlrite-0.5.0 → sqlrite-0.5.1}/images/SQLRite Simple SQL INSERT Execution High Level Diagram.png
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
|
|
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
|