Skip to content

Commit 563fada

Browse files
committed
refactor(query): split oversized engine.rs and graph.rs by concern
Both files exceeded the 500-line non-test limit; split behavior-preserving: - query/engine.rs (583 → 442): the INSERT/UPDATE/DELETE/TRUNCATE execution methods move into a second inherent `impl LiteQueryEngine` block in query/engine_dml.rs. `sql_value_to_string`/`sql_value_to_loro` widened to pub(super) so the new module can call them. Call sites (self.execute_*) are unchanged. - physical_visitor/adapter/graph.rs (507 → ~486): the collection-resolution helper moves to a sibling graph_resolve.rs (it is CSR-traversal logic, not dispatch), imported back into the dispatcher. Verified: compiles, clippy clean, 669/669 lite lib unit tests pass.
1 parent 81457b7 commit 563fada

6 files changed

Lines changed: 193 additions & 170 deletions

File tree

nodedb-lite/src/query/engine.rs

Lines changed: 2 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -334,150 +334,9 @@ impl<S: StorageEngine> LiteQueryEngine<S> {
334334
}
335335
}
336336

337-
pub(super) async fn execute_insert(
338-
&self,
339-
collection: &str,
340-
engine: &EngineType,
341-
rows: &[Vec<(String, SqlValue)>],
342-
if_absent: bool,
343-
primary_key: Option<&str>,
344-
) -> Result<QueryResult, LiteError> {
345-
if *engine == EngineType::DocumentStrict {
346-
return super::strict_dml::insert_strict(&self.strict, collection, rows, if_absent)
347-
.await;
348-
}
349-
if *engine == EngineType::Columnar {
350-
// `written` feeds outbound sync, which is compiled out on wasm32.
351-
#[cfg_attr(target_arch = "wasm32", allow(unused_variables))]
352-
let (result, written) =
353-
super::columnar_dml::insert_columnar(&self.columnar, collection, rows)?;
354-
// Durable outbound enqueue must run here (async) — the sync insert
355-
// path cannot await. Covers the SQL-INSERT route to Origin sync.
356-
#[cfg(not(target_arch = "wasm32"))]
357-
crate::sync::reconcile_outbound_enqueue(
358-
self.columnar.enqueue_outbound(collection, &written).await,
359-
"columnar insert (sql)",
360-
collection,
361-
"",
362-
)?;
363-
return Ok(result);
364-
}
365-
// CRDT / schemaless path.
366-
let mut crdt = self.crdt.lock().map_err(|_| LiteError::LockPoisoned)?;
367-
let mut affected = 0;
368-
for row in rows {
369-
let id = row
370-
.iter()
371-
.find(|(k, _)| match primary_key {
372-
Some(pk) => k == pk,
373-
None => k == "id",
374-
})
375-
.map(|(_, v)| sql_value_to_string(v))
376-
.unwrap_or_default();
377-
if crdt.exists(collection, &id) {
378-
if if_absent {
379-
continue;
380-
}
381-
return Err(LiteError::Query(format!(
382-
"duplicate key value violates unique constraint on '{collection}' (id = '{id}')"
383-
)));
384-
}
385-
let fields: Vec<(&str, loro::LoroValue)> = row
386-
.iter()
387-
.map(|(k, v)| (k.as_str(), sql_value_to_loro(v)))
388-
.collect();
389-
crdt.upsert(collection, &id, &fields)
390-
.map_err(|e| LiteError::Query(format!("insert: {e}")))?;
391-
affected += 1;
392-
}
393-
Ok(QueryResult {
394-
columns: Vec::new(),
395-
rows: Vec::new(),
396-
rows_affected: affected,
397-
})
398-
}
399-
400-
pub(super) async fn execute_update(
401-
&self,
402-
collection: &str,
403-
engine: &EngineType,
404-
assignments: &[(String, nodedb_sql::types::SqlExpr)],
405-
target_keys: &[SqlValue],
406-
) -> Result<QueryResult, LiteError> {
407-
if *engine == EngineType::DocumentStrict {
408-
return super::strict_dml::update_strict(
409-
&self.strict,
410-
collection,
411-
assignments,
412-
target_keys,
413-
)
414-
.await;
415-
}
416-
// CRDT / schemaless path.
417-
let mut crdt = self.crdt.lock().map_err(|_| LiteError::LockPoisoned)?;
418-
let mut affected = 0;
419-
for key in target_keys {
420-
let key_str = sql_value_to_string(key);
421-
let fields: Vec<(&str, loro::LoroValue)> = assignments
422-
.iter()
423-
.filter_map(|(field, expr)| {
424-
if let nodedb_sql::types::SqlExpr::Literal(val) = expr {
425-
Some((field.as_str(), sql_value_to_loro(val)))
426-
} else {
427-
None
428-
}
429-
})
430-
.collect();
431-
crdt.upsert(collection, &key_str, &fields)
432-
.map_err(|e| LiteError::Query(format!("update: {e}")))?;
433-
affected += 1;
434-
}
435-
Ok(QueryResult {
436-
columns: Vec::new(),
437-
rows: Vec::new(),
438-
rows_affected: affected,
439-
})
440-
}
441-
442-
pub(super) async fn execute_delete(
443-
&self,
444-
collection: &str,
445-
engine: &EngineType,
446-
target_keys: &[SqlValue],
447-
) -> Result<QueryResult, LiteError> {
448-
if *engine == EngineType::DocumentStrict {
449-
return super::strict_dml::delete_strict(&self.strict, collection, target_keys).await;
450-
}
451-
// CRDT / schemaless path.
452-
let mut crdt = self.crdt.lock().map_err(|_| LiteError::LockPoisoned)?;
453-
let mut affected = 0;
454-
for key in target_keys {
455-
let key_str = sql_value_to_string(key);
456-
crdt.delete(collection, &key_str)
457-
.map_err(|e| LiteError::Query(format!("delete: {e}")))?;
458-
affected += 1;
459-
}
460-
Ok(QueryResult {
461-
columns: Vec::new(),
462-
rows: Vec::new(),
463-
rows_affected: affected,
464-
})
465-
}
466-
467-
pub(super) async fn execute_truncate(
468-
&self,
469-
collection: &str,
470-
) -> Result<QueryResult, LiteError> {
471-
self.crdt
472-
.lock()
473-
.map_err(|_| LiteError::LockPoisoned)?
474-
.clear_collection(collection)
475-
.map_err(|e| LiteError::Query(format!("truncate: {e}")))?;
476-
Ok(QueryResult::empty())
477-
}
478337
}
479338

480-
fn sql_value_to_string(v: &SqlValue) -> String {
339+
pub(super) fn sql_value_to_string(v: &SqlValue) -> String {
481340
match v {
482341
SqlValue::String(s) => s.clone(),
483342
SqlValue::Int(i) => i.to_string(),
@@ -487,7 +346,7 @@ fn sql_value_to_string(v: &SqlValue) -> String {
487346
}
488347
}
489348

490-
fn sql_value_to_loro(v: &SqlValue) -> loro::LoroValue {
349+
pub(super) fn sql_value_to_loro(v: &SqlValue) -> loro::LoroValue {
491350
match v {
492351
SqlValue::Int(i) => loro::LoroValue::I64(*i),
493352
SqlValue::Float(f) => loro::LoroValue::Double(*f),
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
//! DML execution methods for `LiteQueryEngine` (INSERT / UPDATE / DELETE /
2+
//! TRUNCATE). Split out of `engine.rs` as a second inherent `impl` block to
3+
//! keep that file under the size limit; behavior is unchanged.
4+
5+
use nodedb_sql::types::{EngineType, SqlValue};
6+
use nodedb_types::result::QueryResult;
7+
8+
use super::engine::{LiteQueryEngine, sql_value_to_loro, sql_value_to_string};
9+
use crate::error::LiteError;
10+
use crate::storage::engine::StorageEngine;
11+
12+
impl<S: StorageEngine> LiteQueryEngine<S> {
13+
pub(super) async fn execute_insert(
14+
&self,
15+
collection: &str,
16+
engine: &EngineType,
17+
rows: &[Vec<(String, SqlValue)>],
18+
if_absent: bool,
19+
primary_key: Option<&str>,
20+
) -> Result<QueryResult, LiteError> {
21+
if *engine == EngineType::DocumentStrict {
22+
return super::strict_dml::insert_strict(&self.strict, collection, rows, if_absent)
23+
.await;
24+
}
25+
if *engine == EngineType::Columnar {
26+
// `written` feeds outbound sync, which is compiled out on wasm32.
27+
#[cfg_attr(target_arch = "wasm32", allow(unused_variables))]
28+
let (result, written) =
29+
super::columnar_dml::insert_columnar(&self.columnar, collection, rows)?;
30+
// Durable outbound enqueue must run here (async) — the sync insert
31+
// path cannot await. Covers the SQL-INSERT route to Origin sync.
32+
#[cfg(not(target_arch = "wasm32"))]
33+
crate::sync::reconcile_outbound_enqueue(
34+
self.columnar.enqueue_outbound(collection, &written).await,
35+
"columnar insert (sql)",
36+
collection,
37+
"",
38+
)?;
39+
return Ok(result);
40+
}
41+
// CRDT / schemaless path.
42+
let mut crdt = self.crdt.lock().map_err(|_| LiteError::LockPoisoned)?;
43+
let mut affected = 0;
44+
for row in rows {
45+
let id = row
46+
.iter()
47+
.find(|(k, _)| match primary_key {
48+
Some(pk) => k == pk,
49+
None => k == "id",
50+
})
51+
.map(|(_, v)| sql_value_to_string(v))
52+
.unwrap_or_default();
53+
if crdt.exists(collection, &id) {
54+
if if_absent {
55+
continue;
56+
}
57+
return Err(LiteError::Query(format!(
58+
"duplicate key value violates unique constraint on '{collection}' (id = '{id}')"
59+
)));
60+
}
61+
let fields: Vec<(&str, loro::LoroValue)> = row
62+
.iter()
63+
.map(|(k, v)| (k.as_str(), sql_value_to_loro(v)))
64+
.collect();
65+
crdt.upsert(collection, &id, &fields)
66+
.map_err(|e| LiteError::Query(format!("insert: {e}")))?;
67+
affected += 1;
68+
}
69+
Ok(QueryResult {
70+
columns: Vec::new(),
71+
rows: Vec::new(),
72+
rows_affected: affected,
73+
})
74+
}
75+
76+
pub(super) async fn execute_update(
77+
&self,
78+
collection: &str,
79+
engine: &EngineType,
80+
assignments: &[(String, nodedb_sql::types::SqlExpr)],
81+
target_keys: &[SqlValue],
82+
) -> Result<QueryResult, LiteError> {
83+
if *engine == EngineType::DocumentStrict {
84+
return super::strict_dml::update_strict(
85+
&self.strict,
86+
collection,
87+
assignments,
88+
target_keys,
89+
)
90+
.await;
91+
}
92+
// CRDT / schemaless path.
93+
let mut crdt = self.crdt.lock().map_err(|_| LiteError::LockPoisoned)?;
94+
let mut affected = 0;
95+
for key in target_keys {
96+
let key_str = sql_value_to_string(key);
97+
let fields: Vec<(&str, loro::LoroValue)> = assignments
98+
.iter()
99+
.filter_map(|(field, expr)| {
100+
if let nodedb_sql::types::SqlExpr::Literal(val) = expr {
101+
Some((field.as_str(), sql_value_to_loro(val)))
102+
} else {
103+
None
104+
}
105+
})
106+
.collect();
107+
crdt.upsert(collection, &key_str, &fields)
108+
.map_err(|e| LiteError::Query(format!("update: {e}")))?;
109+
affected += 1;
110+
}
111+
Ok(QueryResult {
112+
columns: Vec::new(),
113+
rows: Vec::new(),
114+
rows_affected: affected,
115+
})
116+
}
117+
118+
pub(super) async fn execute_delete(
119+
&self,
120+
collection: &str,
121+
engine: &EngineType,
122+
target_keys: &[SqlValue],
123+
) -> Result<QueryResult, LiteError> {
124+
if *engine == EngineType::DocumentStrict {
125+
return super::strict_dml::delete_strict(&self.strict, collection, target_keys).await;
126+
}
127+
// CRDT / schemaless path.
128+
let mut crdt = self.crdt.lock().map_err(|_| LiteError::LockPoisoned)?;
129+
let mut affected = 0;
130+
for key in target_keys {
131+
let key_str = sql_value_to_string(key);
132+
crdt.delete(collection, &key_str)
133+
.map_err(|e| LiteError::Query(format!("delete: {e}")))?;
134+
affected += 1;
135+
}
136+
Ok(QueryResult {
137+
columns: Vec::new(),
138+
rows: Vec::new(),
139+
rows_affected: affected,
140+
})
141+
}
142+
143+
pub(super) async fn execute_truncate(
144+
&self,
145+
collection: &str,
146+
) -> Result<QueryResult, LiteError> {
147+
self.crdt
148+
.lock()
149+
.map_err(|_| LiteError::LockPoisoned)?
150+
.clear_collection(collection)
151+
.map_err(|e| LiteError::Query(format!("truncate: {e}")))?;
152+
Ok(QueryResult::empty())
153+
}
154+
}

nodedb-lite/src/query/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod crdt_ops;
66
pub mod ddl;
77
pub mod document_ops;
88
pub mod engine;
9+
pub(crate) mod engine_dml;
910
pub(crate) mod expr_convert;
1011
pub(crate) mod filter_convert;
1112
pub(crate) mod graph_ops;

nodedb-lite/src/query/physical_visitor/adapter/graph.rs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
1111
use std::future::Future;
1212
use std::pin::Pin;
13+
use std::sync::Arc;
1314

1415
use nodedb_physical::physical_plan::GraphOp;
1516
use nodedb_types::result::QueryResult;
@@ -21,6 +22,8 @@ use crate::query::graph_ops::{
2122
};
2223
use crate::storage::engine::StorageEngine;
2324

25+
use super::graph_resolve::resolve_collection_for_nodes;
26+
2427
#[cfg(not(target_arch = "wasm32"))]
2528
pub(crate) type GraphFut<'a> =
2629
Pin<Box<dyn Future<Output = Result<QueryResult, LiteError>> + Send + 'a>>;
@@ -478,30 +481,3 @@ pub(crate) fn dispatch<'a, S: StorageEngine + 'a>(
478481

479482
Ok(fut)
480483
}
481-
482-
/// Resolve which collection a set of node IDs belongs to by scanning the CSR map.
483-
///
484-
/// Returns the first collection that contains any of the given nodes, or an
485-
/// empty string when none is found (which will produce an empty result set
486-
/// rather than an error — correct for "no such graph" semantics).
487-
fn resolve_collection_for_nodes(
488-
csr_map: &Arc<
489-
std::sync::Mutex<std::collections::HashMap<String, crate::engine::graph::index::CsrIndex>>,
490-
>,
491-
node_ids: &[String],
492-
) -> String {
493-
let Ok(map) = csr_map.lock() else {
494-
return String::new();
495-
};
496-
for (coll, csr) in map.iter() {
497-
for node in node_ids {
498-
if csr.contains_node(node) {
499-
return coll.clone();
500-
}
501-
}
502-
}
503-
// Fall back to the first collection in the map.
504-
map.keys().next().cloned().unwrap_or_default()
505-
}
506-
507-
use std::sync::Arc;

0 commit comments

Comments
 (0)