use nodedb_types::{DatabaseId, Lsn, Surrogate, TenantId};
use crate::bridge::envelope::{Payload, PhysicalPlan, Response, Status};
use crate::control::insert_select::copy_rows::{assign_page_rows, resolve_copy_spec};
use crate::control::maintenance::clone_materializer::{dispatch_local, scan_source_page};
use crate::control::state::SharedState;
use nodedb_physical::physical_plan::DocumentOp;
pub async fn run_insert_select(
state: &SharedState,
tenant_id: TenantId,
database_id: DatabaseId,
target_collection: &str,
source_collection: &str,
source_filters: &[u8],
source_limit: usize,
) -> crate::Result<Response> {
let spec = resolve_copy_spec(
state,
tenant_id,
database_id,
target_collection,
source_collection,
source_filters,
)?;
let mut cursor: Vec<u8> = Vec::new();
let mut remaining = source_limit;
let mut total_inserted: usize = 0;
let mut max_lsn = Lsn::ZERO;
while remaining > 0 {
let (entries, next_cursor) = scan_source_page(
state,
tenant_id,
database_id,
source_collection,
&cursor,
None,
None,
)
.await?;
let rows = assign_page_rows(
state,
tenant_id,
database_id,
target_collection,
&spec,
entries,
&mut remaining,
)?;
if !rows.is_empty() {
let page_len = rows.len();
let mut documents: Vec<(String, Vec<u8>)> = Vec::with_capacity(page_len);
let mut surrogates: Vec<Surrogate> = Vec::with_capacity(page_len);
for (document_id, value, surrogate) in rows {
documents.push((document_id, value));
surrogates.push(surrogate);
}
let plan = PhysicalPlan::Document(DocumentOp::BatchInsert {
collection: target_collection.to_string(),
documents,
surrogates,
});
let resp = dispatch_local(state, tenant_id, database_id, target_collection, plan, None)
.await?;
if resp.status != Status::Ok {
return Ok(resp);
}
crate::control::server::wal_dispatch::mint_dispatch_local_redo(
&state.wal,
tenant_id,
database_id,
target_collection,
&resp,
)?;
total_inserted += decode_inserted(&resp.payload).unwrap_or(page_len);
if resp.watermark_lsn > max_lsn {
max_lsn = resp.watermark_lsn;
}
}
if next_cursor.is_empty() {
break;
}
cursor = next_cursor;
}
let payload = nodedb_types::json_to_msgpack(&serde_json::json!({ "inserted": total_inserted }))
.map_err(|e| crate::Error::Serialization {
format: "msgpack".into(),
detail: format!("insert-select response: {e}"),
})?;
Ok(Response {
request_id: crate::types::RequestId::new(0),
status: Status::Ok,
attempt: 1,
partial: false,
payload: Payload::from_vec(payload),
watermark_lsn: max_lsn,
error_code: None,
read_set_valid: None,
read_version_lsn: crate::types::Lsn::ZERO,
write_set: Vec::new(),
})
}
fn decode_inserted(payload: &[u8]) -> Option<usize> {
if payload.is_empty() {
return None;
}
let json: serde_json::Value = nodedb_types::json_from_msgpack(payload)
.ok()
.or_else(|| sonic_rs::from_slice(payload).ok())?;
json.get("inserted")
.and_then(|v| v.as_u64())
.map(|v| v as usize)
}