dirsql 0.0.24__tar.gz → 0.0.25__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.
Files changed (43) hide show
  1. {dirsql-0.0.24 → dirsql-0.0.25}/Cargo.lock +8 -20
  2. dirsql-0.0.25/Cargo.toml +18 -0
  3. {dirsql-0.0.24 → dirsql-0.0.25}/PKG-INFO +1 -1
  4. {dirsql-0.0.24 → dirsql-0.0.25}/packages/python/Cargo.toml +6 -5
  5. dirsql-0.0.25/packages/python/src/lib.rs +315 -0
  6. {dirsql-0.0.24 → dirsql-0.0.25}/packages/python/tests/integration/test_async_dirsql.py +13 -7
  7. {dirsql-0.0.24 → dirsql-0.0.25}/packages/python/tests/integration/test_docs_gaps.py +1 -1
  8. {dirsql-0.0.24/packages/core → dirsql-0.0.25/packages/rust}/Cargo.toml +14 -9
  9. dirsql-0.0.25/packages/rust/README.md +22 -0
  10. {dirsql-0.0.24/packages/core → dirsql-0.0.25/packages/rust}/benches/db_bench.rs +1 -1
  11. {dirsql-0.0.24/packages/core → dirsql-0.0.25/packages/rust}/benches/differ_bench.rs +2 -2
  12. {dirsql-0.0.24/packages/core → dirsql-0.0.25/packages/rust}/benches/matcher_bench.rs +1 -1
  13. {dirsql-0.0.24/packages/core → dirsql-0.0.25/packages/rust}/benches/scanner_bench.rs +2 -2
  14. dirsql-0.0.25/packages/rust/src/lib.rs +713 -0
  15. dirsql-0.0.25/packages/rust/tests/async_sdk.rs +174 -0
  16. dirsql-0.0.25/packages/rust/tests/docs_examples.rs +812 -0
  17. dirsql-0.0.25/packages/rust/tests/docs_gaps.rs +327 -0
  18. dirsql-0.0.25/packages/rust/tests/from_config.rs +256 -0
  19. dirsql-0.0.25/packages/rust/tests/sdk.rs +390 -0
  20. {dirsql-0.0.24 → dirsql-0.0.25}/pyproject.toml +1 -1
  21. dirsql-0.0.24/Cargo.toml +0 -3
  22. dirsql-0.0.24/packages/core/README.md +0 -29
  23. dirsql-0.0.24/packages/core/src/lib.rs +0 -7
  24. dirsql-0.0.24/packages/python/src/lib.rs +0 -857
  25. {dirsql-0.0.24 → dirsql-0.0.25}/README.md +0 -0
  26. {dirsql-0.0.24 → dirsql-0.0.25}/packages/python/README.md +0 -0
  27. {dirsql-0.0.24 → dirsql-0.0.25}/packages/python/tests/__init__.py +0 -0
  28. {dirsql-0.0.24 → dirsql-0.0.25}/packages/python/tests/conftest.py +0 -0
  29. {dirsql-0.0.24 → dirsql-0.0.25}/packages/python/tests/integration/__init__.py +0 -0
  30. {dirsql-0.0.24 → dirsql-0.0.25}/packages/python/tests/integration/test_binding.py +0 -0
  31. {dirsql-0.0.24 → dirsql-0.0.25}/packages/python/tests/integration/test_dirsql.py +0 -0
  32. {dirsql-0.0.24 → dirsql-0.0.25}/packages/python/tests/integration/test_docs_examples.py +0 -0
  33. {dirsql-0.0.24 → dirsql-0.0.25}/packages/python/tests/integration/test_from_config.py +0 -0
  34. {dirsql-0.0.24/packages/core → dirsql-0.0.25/packages/rust}/src/config.rs +0 -0
  35. {dirsql-0.0.24/packages/core → dirsql-0.0.25/packages/rust}/src/db.rs +0 -0
  36. {dirsql-0.0.24/packages/core → dirsql-0.0.25/packages/rust}/src/differ.rs +0 -0
  37. {dirsql-0.0.24/packages/core → dirsql-0.0.25/packages/rust}/src/matcher.rs +0 -0
  38. {dirsql-0.0.24/packages/core → dirsql-0.0.25/packages/rust}/src/parser.rs +0 -0
  39. {dirsql-0.0.24/packages/core → dirsql-0.0.25/packages/rust}/src/scanner.rs +0 -0
  40. {dirsql-0.0.24/packages/core → dirsql-0.0.25/packages/rust}/src/watcher.rs +0 -0
  41. {dirsql-0.0.24 → dirsql-0.0.25}/python/dirsql/__init__.py +0 -0
  42. {dirsql-0.0.24 → dirsql-0.0.25}/python/dirsql/_async.py +0 -0
  43. {dirsql-0.0.24 → dirsql-0.0.25}/python/dirsql/test_async.py +0 -0
@@ -253,18 +253,12 @@ checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1"
253
253
  [[package]]
254
254
  name = "dirsql"
255
255
  version = "0.1.0"
256
- dependencies = [
257
- "dirsql-core",
258
- "pyo3",
259
- "regex",
260
- ]
261
-
262
- [[package]]
263
- name = "dirsql-core"
264
- version = "0.1.0"
265
256
  dependencies = [
266
257
  "criterion",
267
258
  "csv",
259
+ "futures-channel",
260
+ "futures-executor",
261
+ "futures-util",
268
262
  "globset",
269
263
  "notify",
270
264
  "regex",
@@ -274,6 +268,7 @@ dependencies = [
274
268
  "serde_yaml",
275
269
  "tempfile",
276
270
  "thiserror",
271
+ "tokio",
277
272
  "toml",
278
273
  "walkdir",
279
274
  ]
@@ -282,7 +277,7 @@ dependencies = [
282
277
  name = "dirsql-napi"
283
278
  version = "0.1.0"
284
279
  dependencies = [
285
- "dirsql-core",
280
+ "dirsql",
286
281
  "napi",
287
282
  "napi-build",
288
283
  "napi-derive",
@@ -290,18 +285,11 @@ dependencies = [
290
285
  ]
291
286
 
292
287
  [[package]]
293
- name = "dirsql-sdk"
288
+ name = "dirsql-py-ext"
294
289
  version = "0.1.0"
295
290
  dependencies = [
296
- "dirsql-core",
297
- "futures-channel",
298
- "futures-core",
299
- "futures-executor",
300
- "futures-util",
301
- "serde_json",
302
- "tempfile",
303
- "thiserror",
304
- "tokio",
291
+ "dirsql",
292
+ "pyo3",
305
293
  ]
306
294
 
307
295
  [[package]]
@@ -0,0 +1,18 @@
1
+ [workspace]
2
+ members = ["packages/python", "packages/rust"]
3
+ resolver = "2"
4
+
5
+ [workspace.package]
6
+ version = "0.1.0"
7
+ edition = "2024"
8
+ license = "MIT"
9
+ repository = "https://github.com/thekevinscott/dirsql"
10
+
11
+ [workspace.dependencies]
12
+ dirsql = { path = "packages/rust" }
13
+ serde = { version = "1", features = ["derive"] }
14
+ serde_json = "1"
15
+ regex = "1"
16
+ thiserror = "2"
17
+ tokio = { version = "1", features = ["rt", "sync"] }
18
+ futures-channel = "0.3"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.0.24
3
+ Version: 0.0.25
4
4
  Requires-Dist: pytest>=8 ; extra == 'dev'
5
5
  Requires-Dist: pytest-describe>=2 ; extra == 'dev'
6
6
  Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
@@ -1,16 +1,17 @@
1
1
  [package]
2
- name = "dirsql"
3
- version = "0.1.0"
4
- edition = "2024"
2
+ name = "dirsql-py-ext"
3
+ version.workspace = true
4
+ edition.workspace = true
5
+ publish = false
5
6
  readme = "README.md"
6
7
 
7
8
  [lib]
9
+ name = "dirsql_py_ext"
8
10
  crate-type = ["cdylib", "rlib"]
9
11
 
10
12
  [dependencies]
11
- dirsql-core = { path = "../core" }
13
+ dirsql.workspace = true
12
14
  pyo3 = { version = "0.28", features = ["extension-module"], optional = true }
13
- regex = "1"
14
15
 
15
16
  [features]
16
17
  default = []
@@ -0,0 +1,315 @@
1
+ //! PyO3 binding for `dirsql`. Intentionally thin: all orchestration lives in
2
+ //! `dirsql::DirSQL`. This layer only:
3
+ //!
4
+ //! - wraps a Python `extract` callable in a Rust closure (acquiring the GIL as
5
+ //! needed) so it can be handed to [`dirsql::Table`]
6
+ //! - converts row dicts, values, and events between Python and Rust
7
+ //! - forwards `new` / `from_config` / `query` / `_start_watcher` /
8
+ //! `_poll_events` to the corresponding `DirSQL` methods
9
+ //!
10
+ //! The Python-side async wrapper (`dirsql._async.DirSQL`) drives this binding
11
+ //! via `asyncio.to_thread`.
12
+
13
+ #[cfg(feature = "extension-module")]
14
+ mod python {
15
+ use ::dirsql::{DirSQL, Row, RowEvent, Table, Value};
16
+ use pyo3::exceptions::PyRuntimeError;
17
+ use pyo3::prelude::*;
18
+ use pyo3::types::{PyDict, PyList};
19
+ use std::collections::HashMap;
20
+ use std::time::Duration;
21
+
22
+ // -- Public PyO3 classes ------------------------------------------------
23
+
24
+ /// A table definition. Mirrors `dirsql::Table` but holds a Python
25
+ /// callable for `extract`.
26
+ #[pyclass(name = "Table", frozen)]
27
+ struct PyTable {
28
+ #[pyo3(get)]
29
+ ddl: String,
30
+ #[pyo3(get)]
31
+ glob: String,
32
+ extract: Py<PyAny>,
33
+ #[pyo3(get)]
34
+ strict: bool,
35
+ }
36
+
37
+ #[pymethods]
38
+ impl PyTable {
39
+ #[new]
40
+ #[pyo3(signature = (*, ddl, glob, extract, strict=false))]
41
+ fn new(ddl: String, glob: String, extract: Py<PyAny>, strict: bool) -> Self {
42
+ PyTable {
43
+ ddl,
44
+ glob,
45
+ extract,
46
+ strict,
47
+ }
48
+ }
49
+ }
50
+
51
+ /// A row event produced by the watch loop.
52
+ #[pyclass(name = "RowEvent", frozen)]
53
+ struct PyRowEvent {
54
+ #[pyo3(get)]
55
+ table: String,
56
+ #[pyo3(get)]
57
+ action: String,
58
+ #[pyo3(get)]
59
+ row: Option<Py<PyDict>>,
60
+ #[pyo3(get)]
61
+ old_row: Option<Py<PyDict>>,
62
+ #[pyo3(get)]
63
+ error: Option<String>,
64
+ #[pyo3(get)]
65
+ file_path: Option<String>,
66
+ }
67
+
68
+ #[pymethods]
69
+ impl PyRowEvent {
70
+ fn __repr__(&self) -> String {
71
+ format!("RowEvent(table={:?}, action={:?})", self.table, self.action)
72
+ }
73
+ }
74
+
75
+ /// Synchronous binding class. `dirsql._async.DirSQL` wraps it with
76
+ /// `asyncio.to_thread` to produce the async public API.
77
+ #[pyclass(name = "DirSQL")]
78
+ struct PyDirSQL {
79
+ inner: DirSQL,
80
+ }
81
+
82
+ #[pymethods]
83
+ impl PyDirSQL {
84
+ #[new]
85
+ #[pyo3(signature = (root, *, tables, ignore=None))]
86
+ fn new(
87
+ py: Python<'_>,
88
+ root: String,
89
+ tables: Vec<PyRef<'_, PyTable>>,
90
+ ignore: Option<Vec<String>>,
91
+ ) -> PyResult<Self> {
92
+ let rust_tables: Vec<Table> = tables.iter().map(|t| build_table(py, t)).collect();
93
+
94
+ let inner = py
95
+ .detach(move || match ignore {
96
+ Some(ig) => DirSQL::with_ignore(root, rust_tables, ig),
97
+ None => DirSQL::new(root, rust_tables),
98
+ })
99
+ .map_err(to_py_err)?;
100
+
101
+ Ok(Self { inner })
102
+ }
103
+
104
+ #[classmethod]
105
+ fn from_config(
106
+ _cls: &Bound<'_, pyo3::types::PyType>,
107
+ py: Python<'_>,
108
+ path: String,
109
+ ) -> PyResult<Self> {
110
+ let inner = py
111
+ .detach(move || DirSQL::from_config_path(&path))
112
+ .map_err(to_py_err)?;
113
+ Ok(Self { inner })
114
+ }
115
+
116
+ fn query(&self, py: Python<'_>, sql: String) -> PyResult<Py<PyList>> {
117
+ let db = self.inner.clone();
118
+ let rows = py.detach(move || db.query(&sql)).map_err(to_py_err)?;
119
+
120
+ let list = PyList::empty(py);
121
+ for row in rows {
122
+ list.append(value_row_to_py_dict(py, &row)?)?;
123
+ }
124
+ Ok(list.unbind())
125
+ }
126
+
127
+ fn _start_watcher(&self, py: Python<'_>) -> PyResult<()> {
128
+ let db = self.inner.clone();
129
+ py.detach(move || db.start_watching()).map_err(to_py_err)
130
+ }
131
+
132
+ fn _poll_events(&self, py: Python<'_>, timeout_ms: u64) -> PyResult<Vec<PyRowEvent>> {
133
+ let db = self.inner.clone();
134
+ let events = py
135
+ .detach(move || db.poll_events(Duration::from_millis(timeout_ms)))
136
+ .map_err(to_py_err)?;
137
+
138
+ events.iter().map(|e| row_event_to_py(py, e)).collect()
139
+ }
140
+ }
141
+
142
+ // -- Helpers ------------------------------------------------------------
143
+
144
+ fn build_table(py: Python<'_>, t: &PyTable) -> Table {
145
+ let extract_ref = t.extract.clone_ref(py);
146
+ let mut table = Table::try_new(
147
+ t.ddl.clone(),
148
+ t.glob.clone(),
149
+ make_extract_closure(extract_ref),
150
+ );
151
+ table.strict = t.strict;
152
+ table
153
+ }
154
+
155
+ type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
156
+
157
+ fn make_extract_closure(
158
+ extract: Py<PyAny>,
159
+ ) -> impl Fn(&str, &str) -> std::result::Result<Vec<Row>, BoxError> + Send + Sync + 'static
160
+ {
161
+ move |path: &str, content: &str| {
162
+ Python::attach(|py| -> std::result::Result<Vec<Row>, BoxError> {
163
+ let result = extract
164
+ .call1(py, (path, content))
165
+ .map_err(|e| -> BoxError { Box::new(ExtractError(e.to_string())) })?;
166
+ let raw: Vec<HashMap<String, Py<PyAny>>> = result
167
+ .extract(py)
168
+ .map_err(|e: PyErr| -> BoxError { Box::new(ExtractError(e.to_string())) })?;
169
+
170
+ let mut rows = Vec::with_capacity(raw.len());
171
+ for r in &raw {
172
+ rows.push(
173
+ convert_py_row(py, r)
174
+ .map_err(|e| -> BoxError { Box::new(ExtractError(e.to_string())) })?,
175
+ );
176
+ }
177
+ Ok(rows)
178
+ })
179
+ }
180
+ }
181
+
182
+ #[derive(Debug)]
183
+ struct ExtractError(String);
184
+ impl std::fmt::Display for ExtractError {
185
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186
+ f.write_str(&self.0)
187
+ }
188
+ }
189
+ impl std::error::Error for ExtractError {}
190
+
191
+ fn to_py_err<E: std::fmt::Display>(e: E) -> PyErr {
192
+ PyRuntimeError::new_err(e.to_string())
193
+ }
194
+
195
+ fn row_event_to_py(py: Python<'_>, event: &RowEvent) -> PyResult<PyRowEvent> {
196
+ Ok(match event {
197
+ RowEvent::Insert {
198
+ table,
199
+ row,
200
+ file_path,
201
+ } => PyRowEvent {
202
+ table: table.clone(),
203
+ action: "insert".to_string(),
204
+ row: Some(value_row_to_py_dict(py, row)?),
205
+ old_row: None,
206
+ error: None,
207
+ file_path: Some(file_path.clone()),
208
+ },
209
+ RowEvent::Update {
210
+ table,
211
+ old_row,
212
+ new_row,
213
+ file_path,
214
+ } => PyRowEvent {
215
+ table: table.clone(),
216
+ action: "update".to_string(),
217
+ row: Some(value_row_to_py_dict(py, new_row)?),
218
+ old_row: Some(value_row_to_py_dict(py, old_row)?),
219
+ error: None,
220
+ file_path: Some(file_path.clone()),
221
+ },
222
+ RowEvent::Delete {
223
+ table,
224
+ row,
225
+ file_path,
226
+ } => PyRowEvent {
227
+ table: table.clone(),
228
+ action: "delete".to_string(),
229
+ row: Some(value_row_to_py_dict(py, row)?),
230
+ old_row: None,
231
+ error: None,
232
+ file_path: Some(file_path.clone()),
233
+ },
234
+ RowEvent::Error { file_path, error } => PyRowEvent {
235
+ table: String::new(),
236
+ action: "error".to_string(),
237
+ row: None,
238
+ old_row: None,
239
+ error: Some(error.clone()),
240
+ file_path: Some(file_path.to_string_lossy().to_string()),
241
+ },
242
+ })
243
+ }
244
+
245
+ fn value_row_to_py_dict(py: Python<'_>, row: &Row) -> PyResult<Py<PyDict>> {
246
+ let dict = PyDict::new(py);
247
+ for (key, value) in row {
248
+ dict.set_item(key, value_to_py(py, value))?;
249
+ }
250
+ Ok(dict.unbind())
251
+ }
252
+
253
+ fn convert_py_row(
254
+ py: Python<'_>,
255
+ py_row: &HashMap<String, Py<PyAny>>,
256
+ ) -> PyResult<HashMap<String, Value>> {
257
+ let mut row = HashMap::new();
258
+ for (key, val) in py_row {
259
+ row.insert(key.clone(), py_to_value(py, val)?);
260
+ }
261
+ Ok(row)
262
+ }
263
+
264
+ fn py_to_value(py: Python<'_>, obj: &Py<PyAny>) -> PyResult<Value> {
265
+ let bound = obj.bind(py);
266
+
267
+ if bound.is_none() {
268
+ return Ok(Value::Null);
269
+ }
270
+
271
+ // bool must precede int (bool is a subclass of int in Python).
272
+ if bound.is_instance_of::<pyo3::types::PyBool>() {
273
+ let b: bool = bound.extract()?;
274
+ return Ok(Value::Integer(if b { 1 } else { 0 }));
275
+ }
276
+
277
+ if let Ok(i) = bound.extract::<i64>() {
278
+ return Ok(Value::Integer(i));
279
+ }
280
+ if let Ok(f) = bound.extract::<f64>() {
281
+ return Ok(Value::Real(f));
282
+ }
283
+ if let Ok(s) = bound.extract::<String>() {
284
+ return Ok(Value::Text(s));
285
+ }
286
+ if let Ok(b) = bound.extract::<Vec<u8>>() {
287
+ return Ok(Value::Blob(b));
288
+ }
289
+
290
+ // Fall back to the Python repr.
291
+ Ok(Value::Text(bound.str()?.to_string()))
292
+ }
293
+
294
+ fn value_to_py(py: Python<'_>, value: &Value) -> Py<PyAny> {
295
+ match value {
296
+ Value::Null => py.None(),
297
+ Value::Integer(i) => i.into_pyobject(py).unwrap().into_any().unbind(),
298
+ Value::Real(f) => f.into_pyobject(py).unwrap().into_any().unbind(),
299
+ Value::Text(s) => s.into_pyobject(py).unwrap().into_any().unbind(),
300
+ Value::Blob(b) => b.into_pyobject(py).unwrap().unbind(),
301
+ }
302
+ }
303
+
304
+ // -- Module registration ------------------------------------------------
305
+
306
+ #[pymodule]
307
+ #[pyo3(name = "_dirsql")]
308
+ fn py_dirsql_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
309
+ m.add("__version__", env!("CARGO_PKG_VERSION"))?;
310
+ m.add_class::<PyTable>()?;
311
+ m.add_class::<PyDirSQL>()?;
312
+ m.add_class::<PyRowEvent>()?;
313
+ Ok(())
314
+ }
315
+ }
@@ -180,7 +180,7 @@ def describe_DirSQL_async():
180
180
  async def collect_events():
181
181
  async for event in db.watch():
182
182
  events.append(event)
183
- if len(events) >= 1:
183
+ if event.action == "insert":
184
184
  break
185
185
 
186
186
  task = asyncio.create_task(collect_events())
@@ -188,9 +188,15 @@ def describe_DirSQL_async():
188
188
  # Give the watcher time to start
189
189
  await asyncio.sleep(0.3)
190
190
 
191
- # Create a new file
192
- with open(os.path.join(tmp_dir, "new_item.json"), "w") as f:
191
+ # Create a new file atomically -- write to a sibling tmp path
192
+ # then rename into place. Without this the watcher can fire on
193
+ # the empty file between open() and write, producing a spurious
194
+ # error event ahead of the insert.
195
+ final = os.path.join(tmp_dir, "new_item.json")
196
+ tmp = final + ".tmp"
197
+ with open(tmp, "w") as f:
193
198
  json.dump({"name": "apple"}, f)
199
+ os.replace(tmp, final)
194
200
 
195
201
  # Wait for events with timeout
196
202
  try:
@@ -198,10 +204,10 @@ def describe_DirSQL_async():
198
204
  except asyncio.TimeoutError:
199
205
  pytest.fail("Timed out waiting for watch events")
200
206
 
201
- assert len(events) >= 1
202
- assert events[0].action == "insert"
203
- assert events[0].table == "items"
204
- assert events[0].row["name"] == "apple"
207
+ insert = next((e for e in events if e.action == "insert"), None)
208
+ assert insert is not None, f"no insert event in {events!r}"
209
+ assert insert.table == "items"
210
+ assert insert.row["name"] == "apple"
205
211
 
206
212
  @pytest.mark.asyncio
207
213
  async def it_emits_delete_events_for_removed_files(tmp_dir):
@@ -305,7 +305,7 @@ def describe_watching_guide_positional_identity_gap():
305
305
  assert delete_events, "expected at least one delete event when file shrinks"
306
306
  # Docs promise positional identity: the third (idx=2) row should be deleted.
307
307
  # The current implementation does a full-replace on shrink instead
308
- # (see packages/core/src/differ.rs::diff_rows). That is a doc/impl
308
+ # (see packages/rust/src/differ.rs::diff_rows). That is a doc/impl
309
309
  # divergence surfaced in TESTS_AUDIT.md, not fixed here.
310
310
  # What we *can* assert without contradicting either side: among the
311
311
  # delete events the dropped row (idx=2, name=row-2) must appear.
@@ -1,28 +1,33 @@
1
1
  [package]
2
- name = "dirsql-core"
3
- version = "0.1.0"
4
- edition = "2024"
2
+ name = "dirsql"
5
3
  description = "Ephemeral SQL index over a local directory"
6
- license = "MIT"
7
- repository = "https://github.com/thekevinscott/dirsql"
4
+ version.workspace = true
5
+ edition.workspace = true
6
+ license.workspace = true
7
+ repository.workspace = true
8
8
  readme = "README.md"
9
9
 
10
10
  [dependencies]
11
11
  csv = "1"
12
+ futures-channel.workspace = true
12
13
  globset = "0.4"
13
- regex = "1"
14
14
  notify = "7"
15
+ regex.workspace = true
15
16
  rusqlite = { version = "0.34", features = ["bundled"] }
16
- serde = { version = "1", features = ["derive"] }
17
- serde_json = "1"
17
+ serde.workspace = true
18
+ serde_json.workspace = true
18
19
  serde_yaml = "0.9"
19
- thiserror = "2"
20
+ thiserror.workspace = true
21
+ tokio.workspace = true
20
22
  toml = "0.8"
21
23
  walkdir = "2"
22
24
 
23
25
  [dev-dependencies]
24
26
  criterion = { version = "0.5", features = ["html_reports"] }
27
+ futures-executor = "0.3"
28
+ futures-util = "0.3"
25
29
  tempfile = "3"
30
+ tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] }
26
31
 
27
32
  [[bench]]
28
33
  name = "db_bench"
@@ -0,0 +1,22 @@
1
+ # dirsql
2
+
3
+ Rust SDK for `dirsql`. Ephemeral SQL index over a local directory.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ cargo add dirsql
9
+ ```
10
+
11
+ ## API
12
+
13
+ - `Table::new(...)` for infallible extractors
14
+ - `Table::try_new(...)` for fallible extractors
15
+ - `DirSQL::new(...)` for synchronous indexing
16
+ - `DirSQL::with_ignore(...)` for indexing with ignore patterns
17
+ - `DirSQL::query(...)` for SQL queries
18
+ - `DirSQL::watch(...)` for an async row-event stream
19
+
20
+ ## License
21
+
22
+ MIT
@@ -1,5 +1,5 @@
1
1
  use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
2
- use dirsql_core::db::{Db, Value};
2
+ use dirsql::db::{Db, Value};
3
3
  use std::collections::HashMap;
4
4
 
5
5
  fn make_row(i: usize) -> HashMap<String, Value> {
@@ -1,6 +1,6 @@
1
1
  use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
2
- use dirsql_core::db::Value;
3
- use dirsql_core::differ::diff;
2
+ use dirsql::db::Value;
3
+ use dirsql::differ::diff;
4
4
  use std::collections::HashMap;
5
5
 
6
6
  fn make_rows(count: usize) -> Vec<HashMap<String, Value>> {
@@ -1,5 +1,5 @@
1
1
  use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
2
- use dirsql_core::matcher::TableMatcher;
2
+ use dirsql::matcher::TableMatcher;
3
3
  use std::path::Path;
4
4
 
5
5
  fn make_matcher(pattern_count: usize) -> TableMatcher {
@@ -1,6 +1,6 @@
1
1
  use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
2
- use dirsql_core::matcher::TableMatcher;
3
- use dirsql_core::scanner::scan_directory;
2
+ use dirsql::matcher::TableMatcher;
3
+ use dirsql::scanner::scan_directory;
4
4
  use std::fs;
5
5
  use tempfile::TempDir;
6
6