use nodedb_types::columnar::StrictSchema;
use nodedb_types::{CollectionType, DatabaseId, Surrogate, TenantId};
use crate::bridge::scan_filter::ScanFilter;
use crate::control::state::SharedState;
use crate::control::target_identity::{
TargetPk, assign_target_surrogate, bare_collection_name, resolve_target_pk,
};
use crate::data::executor::strict_format::binary_tuple_to_msgpack;
use crate::engine::document::store::surrogate_to_doc_id;
pub(crate) struct CopySpec {
pub target_pk: TargetPk,
pub filters: Vec<ScanFilter>,
pub source_strict_schema: Option<StrictSchema>,
}
pub(crate) fn resolve_copy_spec(
state: &SharedState,
tenant_id: TenantId,
database_id: DatabaseId,
target_collection: &str,
source_collection: &str,
source_filters: &[u8],
) -> crate::Result<CopySpec> {
let catalog = state.credentials.catalog();
let target = catalog
.get_collection(
database_id,
tenant_id.as_u64(),
&bare_collection_name(database_id, target_collection),
)?
.ok_or_else(|| crate::Error::CollectionNotFound {
tenant_id,
collection: target_collection.to_string(),
})?;
let target_pk = resolve_target_pk(&target, "INSERT ... SELECT")?;
let source_strict_schema = catalog
.get_collection(
database_id,
tenant_id.as_u64(),
&bare_collection_name(database_id, source_collection),
)?
.and_then(|s| match &s.collection_type {
CollectionType::Document(mode) => mode.schema().cloned(),
CollectionType::Columnar(_) | CollectionType::KeyValue(_) => None,
});
let filters: Vec<ScanFilter> = if source_filters.is_empty() {
Vec::new()
} else {
zerompk::from_msgpack(source_filters).map_err(|e| crate::Error::Serialization {
format: "msgpack".into(),
detail: format!("insert-select source filters: {e}"),
})?
};
Ok(CopySpec {
target_pk,
filters,
source_strict_schema,
})
}
pub(crate) fn assign_page_rows(
state: &SharedState,
tenant_id: TenantId,
database_id: DatabaseId,
target_collection: &str,
spec: &CopySpec,
entries: Vec<(String, u32, Vec<u8>)>,
remaining: &mut usize,
) -> crate::Result<Vec<(String, Vec<u8>, Surrogate)>> {
let mut out = Vec::with_capacity(entries.len());
for (_source_doc_id, _source_surrogate, raw) in entries {
if *remaining == 0 {
break;
}
let value = match spec.source_strict_schema.as_ref() {
Some(schema) => binary_tuple_to_msgpack(&raw, schema).unwrap_or(raw),
None => raw,
};
if !spec.filters.is_empty() && !spec.filters.iter().all(|f| f.matches_binary(&value)) {
continue;
}
let surrogate = assign_target_surrogate(
state,
database_id,
tenant_id,
target_collection,
&spec.target_pk,
&value,
)?;
out.push((surrogate_to_doc_id(surrogate), value, surrogate));
*remaining -= 1;
}
Ok(out)
}