ducpy 3.6.1__tar.gz → 4.0.0__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.
- {ducpy-3.6.1 → ducpy-4.0.0}/Cargo.lock +62 -5
- {ducpy-3.6.1 → ducpy-4.0.0}/Cargo.toml +4 -1
- {ducpy-3.6.1 → ducpy-4.0.0}/PKG-INFO +3 -2
- {ducpy-3.6.1 → ducpy-4.0.0}/README.md +2 -2
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducpy/crate/Cargo.toml +1 -1
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducpy/crate/src/lib.rs +60 -34
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/Cargo.toml +2 -2
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/build.rs +11 -3
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/schema/duc.sql +13 -6
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/schema/migrations/3000001_to_3000002.sql +18 -6
- ducpy-4.0.0/packages/ducrs/schema/migrations/3000007_to_3000008.sql +59 -0
- ducpy-4.0.0/packages/ducrs/schema/migrations/3000008_to_3000009.sql +171 -0
- ducpy-4.0.0/packages/ducrs/schema/migrations/3000009_to_4000000.sql +6 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/schema/search.sql +1 -1
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/schema/version_control.sql +28 -6
- ducpy-4.0.0/packages/ducrs/src/api/document.rs +330 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/src/api/meta.rs +6 -7
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/src/api/version_control.rs +128 -101
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/src/db/bootstrap.rs +24 -14
- ducpy-4.0.0/packages/ducrs/src/db/bootstrap_tests.rs +297 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/src/db/mod.rs +16 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/src/db/wasm.rs +29 -2
- ducpy-4.0.0/packages/ducrs/src/external_file_chunks.rs +820 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/src/lib.rs +5 -1
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/src/parse.rs +1050 -928
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/src/serialize.rs +546 -231
- ducpy-4.0.0/packages/ducrs/src/session.rs +706 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/src/types.rs +156 -17
- ducpy-4.0.0/patches/sqlite-wasm-vfs/Cargo.lock +687 -0
- ducpy-4.0.0/patches/sqlite-wasm-vfs/Cargo.toml +88 -0
- ducpy-4.0.0/patches/sqlite-wasm-vfs/LICENSE +21 -0
- ducpy-4.0.0/patches/sqlite-wasm-vfs/README.md +3 -0
- ducpy-4.0.0/patches/sqlite-wasm-vfs/src/lib.rs +13 -0
- ducpy-4.0.0/patches/sqlite-wasm-vfs/src/relaxed_idb.rs +963 -0
- ducpy-4.0.0/patches/sqlite-wasm-vfs/src/sahpool.rs +1134 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/pyproject.toml +1 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/__init__.py +4 -2
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/parse.py +55 -43
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/search/search_elements.py +2 -2
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/search/search_external_files.py +83 -43
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/serialize.py +17 -6
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/utils/io.py +2 -3
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy_native/__init__.py +9 -6
- ducpy-3.6.1/packages/ducrs/src/api/document.rs +0 -82
- {ducpy-3.6.1 → ducpy-4.0.0}/LICENSE +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/LICENSE +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/README.md +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/schema/migrations/3000000_to_3000001.sql +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/schema/migrations/3000002_to_3000003.sql +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/schema/migrations/3000003_to_3000004.sql +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/schema/migrations/3000004_to_3000005.sql +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/schema/migrations/3000005_to_3000006.sql +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/schema/migrations/3000006_to_3000007.sql +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/src/api/mod.rs +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/src/db/native.rs +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/packages/ducrs/src/serde_utils.rs +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/builders/__init__.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/builders/block_instance_builder.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/builders/block_utils.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/builders/element_builders.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/builders/mutate_builder.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/builders/sql_builder.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/builders/state_builders.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/builders/style_builders.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/classes/DataStateClass.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/classes/ElementsClass.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/classes/__init__.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/enums.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/search/__init__.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/search/image_ocr.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/search/search_pdf.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/utils/__init__.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/utils/constants.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/utils/convert.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/utils/mutate_utils.py +0 -0
- {ducpy-3.6.1 → ducpy-4.0.0}/src/ducpy/utils/rand_utils.py +0 -0
|
@@ -403,16 +403,20 @@ name = "ducjs-wasm"
|
|
|
403
403
|
version = "0.0.0-development"
|
|
404
404
|
dependencies = [
|
|
405
405
|
"duc",
|
|
406
|
+
"flate2",
|
|
406
407
|
"js-sys",
|
|
408
|
+
"rusqlite",
|
|
407
409
|
"serde",
|
|
408
410
|
"serde-wasm-bindgen",
|
|
409
411
|
"serde_json",
|
|
412
|
+
"sqlite-wasm-vfs",
|
|
410
413
|
"wasm-bindgen",
|
|
414
|
+
"wasm-bindgen-futures",
|
|
411
415
|
]
|
|
412
416
|
|
|
413
417
|
[[package]]
|
|
414
418
|
name = "ducpy-native"
|
|
415
|
-
version = "
|
|
419
|
+
version = "4.0.0"
|
|
416
420
|
dependencies = [
|
|
417
421
|
"duc",
|
|
418
422
|
"pyo3",
|
|
@@ -1127,6 +1131,16 @@ dependencies = [
|
|
|
1127
1131
|
"autocfg",
|
|
1128
1132
|
]
|
|
1129
1133
|
|
|
1134
|
+
[[package]]
|
|
1135
|
+
name = "minicov"
|
|
1136
|
+
version = "0.3.8"
|
|
1137
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1138
|
+
checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d"
|
|
1139
|
+
dependencies = [
|
|
1140
|
+
"cc",
|
|
1141
|
+
"walkdir",
|
|
1142
|
+
]
|
|
1143
|
+
|
|
1130
1144
|
[[package]]
|
|
1131
1145
|
name = "miniz_oxide"
|
|
1132
1146
|
version = "0.8.9"
|
|
@@ -1587,9 +1601,9 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
|
|
1587
1601
|
|
|
1588
1602
|
[[package]]
|
|
1589
1603
|
name = "rsqlite-vfs"
|
|
1590
|
-
version = "0.1.
|
|
1604
|
+
version = "0.1.1"
|
|
1591
1605
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1592
|
-
checksum = "
|
|
1606
|
+
checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c"
|
|
1593
1607
|
dependencies = [
|
|
1594
1608
|
"hashbrown",
|
|
1595
1609
|
"thiserror",
|
|
@@ -1655,6 +1669,15 @@ version = "1.0.20"
|
|
|
1655
1669
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1656
1670
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
|
1657
1671
|
|
|
1672
|
+
[[package]]
|
|
1673
|
+
name = "same-file"
|
|
1674
|
+
version = "1.0.6"
|
|
1675
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1676
|
+
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
|
1677
|
+
dependencies = [
|
|
1678
|
+
"winapi-util",
|
|
1679
|
+
]
|
|
1680
|
+
|
|
1658
1681
|
[[package]]
|
|
1659
1682
|
name = "sealed"
|
|
1660
1683
|
version = "0.6.0"
|
|
@@ -1831,16 +1854,16 @@ dependencies = [
|
|
|
1831
1854
|
[[package]]
|
|
1832
1855
|
name = "sqlite-wasm-vfs"
|
|
1833
1856
|
version = "0.2.0"
|
|
1834
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1835
|
-
checksum = "7f7a5c9ac229421d577bb5a9bb59048838509958b218dd4e0b3c1214a87c361e"
|
|
1836
1857
|
dependencies = [
|
|
1837
1858
|
"indexed_db_futures",
|
|
1838
1859
|
"js-sys",
|
|
1839
1860
|
"rsqlite-vfs",
|
|
1861
|
+
"sqlite-wasm-rs",
|
|
1840
1862
|
"thiserror",
|
|
1841
1863
|
"tokio",
|
|
1842
1864
|
"wasm-bindgen",
|
|
1843
1865
|
"wasm-bindgen-futures",
|
|
1866
|
+
"wasm-bindgen-test",
|
|
1844
1867
|
"web-sys",
|
|
1845
1868
|
]
|
|
1846
1869
|
|
|
@@ -2183,6 +2206,16 @@ version = "0.9.5"
|
|
|
2183
2206
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2184
2207
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|
2185
2208
|
|
|
2209
|
+
[[package]]
|
|
2210
|
+
name = "walkdir"
|
|
2211
|
+
version = "2.5.0"
|
|
2212
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2213
|
+
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
|
2214
|
+
dependencies = [
|
|
2215
|
+
"same-file",
|
|
2216
|
+
"winapi-util",
|
|
2217
|
+
]
|
|
2218
|
+
|
|
2186
2219
|
[[package]]
|
|
2187
2220
|
name = "wasip2"
|
|
2188
2221
|
version = "1.0.2+wasi-0.2.9"
|
|
@@ -2264,6 +2297,30 @@ dependencies = [
|
|
|
2264
2297
|
"unicode-ident",
|
|
2265
2298
|
]
|
|
2266
2299
|
|
|
2300
|
+
[[package]]
|
|
2301
|
+
name = "wasm-bindgen-test"
|
|
2302
|
+
version = "0.3.54"
|
|
2303
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2304
|
+
checksum = "4e381134e148c1062f965a42ed1f5ee933eef2927c3f70d1812158f711d39865"
|
|
2305
|
+
dependencies = [
|
|
2306
|
+
"js-sys",
|
|
2307
|
+
"minicov",
|
|
2308
|
+
"wasm-bindgen",
|
|
2309
|
+
"wasm-bindgen-futures",
|
|
2310
|
+
"wasm-bindgen-test-macro",
|
|
2311
|
+
]
|
|
2312
|
+
|
|
2313
|
+
[[package]]
|
|
2314
|
+
name = "wasm-bindgen-test-macro"
|
|
2315
|
+
version = "0.3.54"
|
|
2316
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2317
|
+
checksum = "b673bca3298fe582aeef8352330ecbad91849f85090805582400850f8270a2e8"
|
|
2318
|
+
dependencies = [
|
|
2319
|
+
"proc-macro2",
|
|
2320
|
+
"quote",
|
|
2321
|
+
"syn",
|
|
2322
|
+
]
|
|
2323
|
+
|
|
2267
2324
|
[[package]]
|
|
2268
2325
|
name = "web-sys"
|
|
2269
2326
|
version = "0.3.81"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[workspace]
|
|
2
2
|
resolver = "2"
|
|
3
|
-
members = ["packages/ducrs", "packages/ducpy/crate"]
|
|
3
|
+
members = ["packages/ducrs", "packages/ducpy/crate", "patches/sqlite-wasm-vfs"]
|
|
4
4
|
|
|
5
5
|
[workspace.metadata.wasm-pack]
|
|
6
6
|
"wasm-pack" = "0.14.0"
|
|
@@ -18,3 +18,6 @@ opt-level = "z"
|
|
|
18
18
|
codegen-units = 1
|
|
19
19
|
strip = true
|
|
20
20
|
panic = "abort"
|
|
21
|
+
|
|
22
|
+
[patch.crates-io]
|
|
23
|
+
sqlite-wasm-vfs = { path = "patches/sqlite-wasm-vfs" }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ducpy
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Classifier: License :: OSI Approved :: MIT License
|
|
5
5
|
Requires-Dist: nanoid>=2.0.0
|
|
6
6
|
Requires-Dist: pypdf>=4.2.0
|
|
@@ -85,7 +85,7 @@ Read and write `.duc` files using the `duc.parse` and `duc.serialize` modules.
|
|
|
85
85
|
|
|
86
86
|
See the worked examples:
|
|
87
87
|
|
|
88
|
-
- [Parsing](src/examples/parsing_demo.py) — `parse_duc` / `
|
|
88
|
+
- [Parsing](src/examples/parsing_demo.py) — `parse_duc` / `list_external_files` / streaming revision helpers.
|
|
89
89
|
- [Serialization](src/examples/serialization_demo.py) — `serialize_duc` from builder-created elements.
|
|
90
90
|
- [External files](src/examples/external_files_demo.py) — attaching binary blobs (images, PDFs) to a `.duc` document.
|
|
91
91
|
|
|
@@ -108,3 +108,4 @@ Feel free to open an issue or contact us at [Ducflair Support](https://www.ducfl
|
|
|
108
108
|
## License
|
|
109
109
|
|
|
110
110
|
This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for more details.
|
|
111
|
+
|
|
@@ -62,7 +62,7 @@ Read and write `.duc` files using the `duc.parse` and `duc.serialize` modules.
|
|
|
62
62
|
|
|
63
63
|
See the worked examples:
|
|
64
64
|
|
|
65
|
-
- [Parsing](src/examples/parsing_demo.py) — `parse_duc` / `
|
|
65
|
+
- [Parsing](src/examples/parsing_demo.py) — `parse_duc` / `list_external_files` / streaming revision helpers.
|
|
66
66
|
- [Serialization](src/examples/serialization_demo.py) — `serialize_duc` from builder-created elements.
|
|
67
67
|
- [External files](src/examples/external_files_demo.py) — attaching binary blobs (images, PDFs) to a `.duc` document.
|
|
68
68
|
|
|
@@ -84,4 +84,4 @@ Feel free to open an issue or contact us at [Ducflair Support](https://www.ducfl
|
|
|
84
84
|
|
|
85
85
|
## License
|
|
86
86
|
|
|
87
|
-
This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for more details.
|
|
87
|
+
This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for more details.
|
|
@@ -1,60 +1,85 @@
|
|
|
1
1
|
use pyo3::prelude::*;
|
|
2
|
-
use
|
|
2
|
+
use std::fs::File;
|
|
3
3
|
|
|
4
|
-
/// Parse a `.duc` file
|
|
4
|
+
/// Parse a `.duc` file path into a Python dict (ExportedDataState).
|
|
5
5
|
#[pyfunction]
|
|
6
|
-
fn parse_duc(py: Python<'_>,
|
|
7
|
-
let
|
|
6
|
+
fn parse_duc(py: Python<'_>, path: &str) -> PyResult<PyObject> {
|
|
7
|
+
let session = duc::session::DucSession::open_path(path)
|
|
8
|
+
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
|
|
9
|
+
let state = session
|
|
10
|
+
.read_document_state()
|
|
8
11
|
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
|
|
9
12
|
pythonize::pythonize(py, &state)
|
|
10
13
|
.map(|b| b.unbind())
|
|
11
14
|
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
///
|
|
15
|
-
///
|
|
16
|
-
/// Use `get_external_file()` or `list_external_files()` for on-demand access.
|
|
17
|
+
/// Serialize a Python dict (ExportedDataState) directly to a `.duc` output path.
|
|
17
18
|
#[pyfunction]
|
|
18
|
-
fn
|
|
19
|
-
let state =
|
|
19
|
+
fn serialize_duc(data: &Bound<'_, pyo3::types::PyAny>, output_path: &str) -> PyResult<()> {
|
|
20
|
+
let state: duc::types::ExportedDataState = pythonize::depythonize(data)
|
|
20
21
|
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
|
|
21
|
-
|
|
22
|
-
.
|
|
22
|
+
let mut session = duc::session::DucSession::create_export_session()
|
|
23
|
+
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
|
|
24
|
+
session
|
|
25
|
+
.write_document_state(&state)
|
|
26
|
+
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
|
|
27
|
+
session
|
|
28
|
+
.finish_to_path(output_path)
|
|
23
29
|
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
///
|
|
32
|
+
/// List metadata for all external files without loading data blobs.
|
|
27
33
|
#[pyfunction]
|
|
28
|
-
fn
|
|
29
|
-
let
|
|
34
|
+
fn list_external_files(py: Python<'_>, path: &str) -> PyResult<PyObject> {
|
|
35
|
+
let session = duc::session::DucSession::open_path(path)
|
|
30
36
|
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
|
|
31
|
-
let
|
|
37
|
+
let meta = session
|
|
38
|
+
.list_external_files()
|
|
39
|
+
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
|
|
40
|
+
pythonize::pythonize(py, &meta)
|
|
41
|
+
.map(|b| b.unbind())
|
|
42
|
+
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#[pyfunction]
|
|
46
|
+
fn stream_external_file_revision_to_path(
|
|
47
|
+
path: &str,
|
|
48
|
+
revision_id: &str,
|
|
49
|
+
output_path: &str,
|
|
50
|
+
) -> PyResult<u64> {
|
|
51
|
+
let session = duc::session::DucSession::open_path(path)
|
|
32
52
|
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
|
|
33
|
-
|
|
53
|
+
let mut out = File::create(output_path)
|
|
54
|
+
.map_err(|e| pyo3::exceptions::PyOSError::new_err(format!("{e}")))?;
|
|
55
|
+
session
|
|
56
|
+
.stream_external_file_revision_to_writer(revision_id, &mut out)
|
|
57
|
+
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))
|
|
34
58
|
}
|
|
35
59
|
|
|
36
|
-
/// Fetch a single external file from a `.duc` buffer by file ID.
|
|
37
|
-
///
|
|
38
|
-
/// Returns the file entry as a dict, or None if not found.
|
|
39
60
|
#[pyfunction]
|
|
40
|
-
fn
|
|
41
|
-
|
|
61
|
+
fn stream_checkpoint_data_to_path(
|
|
62
|
+
path: &str,
|
|
63
|
+
checkpoint_id: &str,
|
|
64
|
+
output_path: &str,
|
|
65
|
+
) -> PyResult<u64> {
|
|
66
|
+
let session = duc::session::DucSession::open_path(path)
|
|
42
67
|
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
68
|
+
let mut out = File::create(output_path)
|
|
69
|
+
.map_err(|e| pyo3::exceptions::PyOSError::new_err(format!("{e}")))?;
|
|
70
|
+
session
|
|
71
|
+
.stream_checkpoint_data_to_writer(checkpoint_id, &mut out)
|
|
72
|
+
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))
|
|
49
73
|
}
|
|
50
74
|
|
|
51
|
-
/// List metadata for all external files (without loading the heavy data blobs).
|
|
52
75
|
#[pyfunction]
|
|
53
|
-
fn
|
|
54
|
-
let
|
|
76
|
+
fn stream_delta_changeset_to_path(path: &str, delta_id: &str, output_path: &str) -> PyResult<u64> {
|
|
77
|
+
let session = duc::session::DucSession::open_path(path)
|
|
55
78
|
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
|
|
56
|
-
|
|
57
|
-
.
|
|
79
|
+
let mut out = File::create(output_path)
|
|
80
|
+
.map_err(|e| pyo3::exceptions::PyOSError::new_err(format!("{e}")))?;
|
|
81
|
+
session
|
|
82
|
+
.stream_delta_changeset_to_writer(delta_id, &mut out)
|
|
58
83
|
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))
|
|
59
84
|
}
|
|
60
85
|
|
|
@@ -101,10 +126,11 @@ fn get_migrations(_py: Python<'_>) -> PyResult<Vec<(i64, i64, String)>> {
|
|
|
101
126
|
#[pymodule]
|
|
102
127
|
fn ducpy_native(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
103
128
|
m.add_function(wrap_pyfunction!(parse_duc, m)?)?;
|
|
104
|
-
m.add_function(wrap_pyfunction!(parse_duc_lazy, m)?)?;
|
|
105
129
|
m.add_function(wrap_pyfunction!(serialize_duc, m)?)?;
|
|
106
|
-
m.add_function(wrap_pyfunction!(get_external_file, m)?)?;
|
|
107
130
|
m.add_function(wrap_pyfunction!(list_external_files, m)?)?;
|
|
131
|
+
m.add_function(wrap_pyfunction!(stream_external_file_revision_to_path, m)?)?;
|
|
132
|
+
m.add_function(wrap_pyfunction!(stream_checkpoint_data_to_path, m)?)?;
|
|
133
|
+
m.add_function(wrap_pyfunction!(stream_delta_changeset_to_path, m)?)?;
|
|
108
134
|
m.add_function(wrap_pyfunction!(get_schema_version, m)?)?;
|
|
109
135
|
m.add_function(wrap_pyfunction!(get_schema_version_int, m)?)?;
|
|
110
136
|
m.add_function(wrap_pyfunction!(get_duc_schema_sql, m)?)?;
|
|
@@ -28,11 +28,11 @@ serde_json = "1.0"
|
|
|
28
28
|
serde_repr = "0.1"
|
|
29
29
|
serde_bytes = "0.11"
|
|
30
30
|
lazy_static = "1.4"
|
|
31
|
-
rusqlite = { version = "0.38", features = ["bundled"] }
|
|
31
|
+
rusqlite = { version = "0.38", features = ["bundled", "serialize"] }
|
|
32
32
|
thiserror = "2"
|
|
33
33
|
|
|
34
34
|
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
|
|
35
|
-
sqlite-wasm-vfs = { version = "0.2", optional = true }
|
|
35
|
+
sqlite-wasm-vfs = { path = "../../patches/sqlite-wasm-vfs", version = "0.2", optional = true }
|
|
36
36
|
wasm-bindgen = "0.2"
|
|
37
37
|
|
|
38
38
|
[dev-dependencies]
|
|
@@ -7,7 +7,12 @@ fn schema_user_version_from_sql(sql: &str) -> u32 {
|
|
|
7
7
|
for line in sql.lines() {
|
|
8
8
|
let trimmed = line.trim();
|
|
9
9
|
if let Some(rest) = trimmed.strip_prefix("PRAGMA user_version") {
|
|
10
|
-
let rest = rest
|
|
10
|
+
let rest = rest
|
|
11
|
+
.trim()
|
|
12
|
+
.trim_start_matches('=')
|
|
13
|
+
.trim()
|
|
14
|
+
.trim_end_matches(';')
|
|
15
|
+
.trim();
|
|
11
16
|
if let Ok(v) = rest.parse::<u32>() {
|
|
12
17
|
return v;
|
|
13
18
|
}
|
|
@@ -133,7 +138,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
133
138
|
(uv, decode_user_version_to_semver(uv))
|
|
134
139
|
}
|
|
135
140
|
Err(e) => {
|
|
136
|
-
eprintln!(
|
|
141
|
+
eprintln!(
|
|
142
|
+
"cargo:warning=Could not read {:?}: {e}. Defaulting to 0.0.0.",
|
|
143
|
+
sql_path
|
|
144
|
+
);
|
|
137
145
|
(0, "0.0.0".to_string())
|
|
138
146
|
}
|
|
139
147
|
};
|
|
@@ -153,4 +161,4 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
153
161
|
println!("cargo:rerun-if-changed=build.rs");
|
|
154
162
|
|
|
155
163
|
Ok(())
|
|
156
|
-
}
|
|
164
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
-- "DUC_" in ASCII
|
|
2
2
|
-- Apply in order: duc.sql → version_control.sql → search.sql
|
|
3
3
|
PRAGMA application_id = 1146569567;
|
|
4
|
-
PRAGMA user_version =
|
|
4
|
+
PRAGMA user_version = 4000000;
|
|
5
5
|
PRAGMA journal_mode = WAL;
|
|
6
6
|
PRAGMA foreign_keys = ON;
|
|
7
7
|
PRAGMA synchronous = NORMAL;
|
|
@@ -996,8 +996,15 @@ CREATE TABLE external_file_revisions (
|
|
|
996
996
|
CREATE INDEX idx_external_file_revisions_file ON external_file_revisions(file_id);
|
|
997
997
|
|
|
998
998
|
-- Actual binary content for each revision, separated from metadata
|
|
999
|
-
-- so
|
|
1000
|
-
CREATE TABLE
|
|
1001
|
-
revision_id
|
|
1002
|
-
|
|
1003
|
-
|
|
999
|
+
-- and chunked so large files can be streamed without loading one BLOB.
|
|
1000
|
+
CREATE TABLE external_file_revision_chunks (
|
|
1001
|
+
revision_id TEXT NOT NULL REFERENCES external_file_revisions(id) ON DELETE CASCADE,
|
|
1002
|
+
chunk_index INTEGER NOT NULL CHECK (chunk_index >= 0),
|
|
1003
|
+
offset_bytes INTEGER NOT NULL CHECK (offset_bytes >= 0),
|
|
1004
|
+
size_bytes INTEGER NOT NULL CHECK (size_bytes >= 0),
|
|
1005
|
+
data BLOB NOT NULL,
|
|
1006
|
+
PRIMARY KEY (revision_id, chunk_index)
|
|
1007
|
+
) WITHOUT ROWID;
|
|
1008
|
+
|
|
1009
|
+
CREATE INDEX idx_external_file_revision_chunks_revision_offset
|
|
1010
|
+
ON external_file_revision_chunks(revision_id, offset_bytes);
|
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
-- `external_file_revision_data` table so metadata can be read without
|
|
4
4
|
-- loading heavy blobs.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
PRAGMA foreign_keys = OFF;
|
|
7
|
+
|
|
8
|
+
-- 1. Stage existing data blobs before the parent table is renamed/recreated.
|
|
9
|
+
CREATE TABLE _external_file_revision_data_v3000001 (
|
|
10
|
+
revision_id TEXT PRIMARY KEY,
|
|
9
11
|
data BLOB NOT NULL
|
|
10
12
|
) WITHOUT ROWID;
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
INSERT INTO external_file_revision_data (revision_id, data)
|
|
14
|
+
INSERT INTO _external_file_revision_data_v3000001 (revision_id, data)
|
|
14
15
|
SELECT id, data FROM external_file_revisions;
|
|
15
16
|
|
|
16
|
-
--
|
|
17
|
+
-- 2. Recreate external_file_revisions without the data column.
|
|
17
18
|
ALTER TABLE external_file_revisions RENAME TO _ext_revisions_old_v3000001;
|
|
18
19
|
|
|
19
20
|
CREATE TABLE external_file_revisions (
|
|
@@ -35,6 +36,17 @@ INSERT INTO external_file_revisions (id, file_id, size_bytes, checksum, source_n
|
|
|
35
36
|
SELECT id, file_id, size_bytes, checksum, source_name, mime_type, message, created, last_retrieved
|
|
36
37
|
FROM _ext_revisions_old_v3000001;
|
|
37
38
|
|
|
39
|
+
-- 3. Create the final data table after the new parent table exists.
|
|
40
|
+
CREATE TABLE external_file_revision_data (
|
|
41
|
+
revision_id TEXT PRIMARY KEY REFERENCES external_file_revisions(id) ON DELETE CASCADE,
|
|
42
|
+
data BLOB NOT NULL
|
|
43
|
+
) WITHOUT ROWID;
|
|
44
|
+
|
|
45
|
+
INSERT INTO external_file_revision_data (revision_id, data)
|
|
46
|
+
SELECT revision_id, data FROM _external_file_revision_data_v3000001;
|
|
47
|
+
|
|
38
48
|
DROP TABLE _ext_revisions_old_v3000001;
|
|
49
|
+
DROP TABLE _external_file_revision_data_v3000001;
|
|
39
50
|
|
|
51
|
+
PRAGMA foreign_keys = ON;
|
|
40
52
|
PRAGMA user_version = 3000002;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
-- Migration: 3000007 -> 3000008
|
|
2
|
+
-- Store external file revision bytes in ordered chunks instead of one BLOB row.
|
|
3
|
+
|
|
4
|
+
BEGIN IMMEDIATE;
|
|
5
|
+
PRAGMA defer_foreign_keys = ON;
|
|
6
|
+
|
|
7
|
+
CREATE TABLE external_file_revision_chunks (
|
|
8
|
+
revision_id TEXT NOT NULL REFERENCES external_file_revisions(id) ON DELETE CASCADE,
|
|
9
|
+
chunk_index INTEGER NOT NULL CHECK (chunk_index >= 0),
|
|
10
|
+
offset_bytes INTEGER NOT NULL CHECK (offset_bytes >= 0),
|
|
11
|
+
size_bytes INTEGER NOT NULL CHECK (size_bytes >= 0),
|
|
12
|
+
data BLOB NOT NULL,
|
|
13
|
+
PRIMARY KEY (revision_id, chunk_index)
|
|
14
|
+
) WITHOUT ROWID;
|
|
15
|
+
|
|
16
|
+
CREATE INDEX idx_external_file_revision_chunks_revision_offset
|
|
17
|
+
ON external_file_revision_chunks(revision_id, offset_bytes);
|
|
18
|
+
|
|
19
|
+
WITH RECURSIVE chunk_offsets(offset_bytes) AS (
|
|
20
|
+
VALUES(0)
|
|
21
|
+
UNION ALL
|
|
22
|
+
SELECT offset_bytes + 8388608
|
|
23
|
+
FROM chunk_offsets
|
|
24
|
+
WHERE EXISTS (
|
|
25
|
+
SELECT 1
|
|
26
|
+
FROM external_file_revision_data
|
|
27
|
+
WHERE length(data) > offset_bytes + 8388608
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
INSERT INTO external_file_revision_chunks (
|
|
31
|
+
revision_id, chunk_index, offset_bytes, size_bytes, data
|
|
32
|
+
)
|
|
33
|
+
SELECT
|
|
34
|
+
source.revision_id,
|
|
35
|
+
chunk_offsets.offset_bytes / 8388608,
|
|
36
|
+
chunk_offsets.offset_bytes,
|
|
37
|
+
length(substr(source.data, chunk_offsets.offset_bytes + 1, 8388608)),
|
|
38
|
+
substr(source.data, chunk_offsets.offset_bytes + 1, 8388608)
|
|
39
|
+
FROM external_file_revision_data AS source
|
|
40
|
+
JOIN chunk_offsets
|
|
41
|
+
ON chunk_offsets.offset_bytes = 0
|
|
42
|
+
OR chunk_offsets.offset_bytes < length(source.data);
|
|
43
|
+
|
|
44
|
+
UPDATE external_file_revisions
|
|
45
|
+
SET size_bytes = (
|
|
46
|
+
SELECT length(source.data)
|
|
47
|
+
FROM external_file_revision_data AS source
|
|
48
|
+
WHERE source.revision_id = external_file_revisions.id
|
|
49
|
+
)
|
|
50
|
+
WHERE EXISTS (
|
|
51
|
+
SELECT 1
|
|
52
|
+
FROM external_file_revision_data AS source
|
|
53
|
+
WHERE source.revision_id = external_file_revisions.id
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
DROP TABLE external_file_revision_data;
|
|
57
|
+
|
|
58
|
+
PRAGMA user_version = 3000008;
|
|
59
|
+
COMMIT;
|