use bytes::{BufMut, Bytes, BytesMut};
use crate::codec::encode_field_key;
use crate::error::RelError;
use crate::schema::{FieldValue, Schema};
#[derive(Debug, Clone)]
pub struct IndexSpec {
pub schema_name: String,
pub field_name: String,
pub domain_id: u32,
pub field_idx: usize,
}
impl IndexSpec {
pub fn new(schema: &Schema, field_name: &str) -> Result<Self, RelError> {
let (field_idx, _field) = schema
.field(field_name)
.ok_or_else(|| RelError::UnknownField(field_name.to_string()))?;
let domain_id = derive_index_domain(&schema.name, field_name);
Ok(Self {
schema_name: schema.name.clone(),
field_name: field_name.to_string(),
domain_id,
field_idx,
})
}
}
pub fn derive_index_domain(schema_name: &str, field_name: &str) -> u32 {
let mut h = blake3::Hasher::new();
h.update(b"donadb-rel:idx:");
h.update(schema_name.as_bytes());
h.update(b":");
h.update(field_name.as_bytes());
let hash = h.finalize();
u32::from_le_bytes(hash.as_bytes()[..4].try_into().unwrap())
}
pub fn build_index_key(
field_value: &FieldValue,
field_type: &crate::schema::FieldType,
primary_key: &Bytes,
) -> Result<Bytes, RelError> {
let mut buf = BytesMut::new();
encode_field_key(&mut buf, field_type, field_value)?;
buf.put_slice(primary_key);
Ok(buf.freeze())
}
pub fn extract_primary_key_from_index(index_key: &[u8], field_encoded_len: usize) -> Bytes {
Bytes::copy_from_slice(&index_key[field_encoded_len..])
}
pub fn index_range_for_eq(
field_value: &FieldValue,
field_type: &crate::schema::FieldType,
) -> Result<(Bytes, Bytes), RelError> {
let mut start_buf = BytesMut::new();
encode_field_key(&mut start_buf, field_type, field_value)?;
let start = start_buf.freeze();
let mut end_bytes = start.to_vec();
end_bytes.extend_from_slice(&[0xFF; 4]);
Ok((start, Bytes::from(end_bytes)))
}
pub struct IndexWriter {
pub specs: Vec<IndexSpec>,
}
impl IndexWriter {
pub fn new(specs: Vec<IndexSpec>) -> Self {
Self { specs }
}
pub fn write_indexes(
&self,
batch: &mut donadb::WriteBatch,
record: &crate::schema::Record,
schema: &Schema,
primary_key: &Bytes,
) -> Result<(), RelError> {
for spec in &self.specs {
let field = &schema.fields[spec.field_idx];
let value = record
.values
.get(spec.field_idx)
.ok_or_else(|| RelError::MissingField(field.name.clone()))?;
if value == &FieldValue::Null {
continue;
}
let index_key = build_index_key(value, &field.field_type, primary_key)?;
batch.put(spec.domain_id, index_key, primary_key.clone());
}
Ok(())
}
pub fn remove_indexes(
&self,
batch: &mut donadb::WriteBatch,
old_record: &crate::schema::Record,
schema: &Schema,
primary_key: &Bytes,
) -> Result<(), RelError> {
for spec in &self.specs {
let field = &schema.fields[spec.field_idx];
let value = old_record
.values
.get(spec.field_idx)
.ok_or_else(|| RelError::MissingField(field.name.clone()))?;
if value == &FieldValue::Null {
continue;
}
let index_key = build_index_key(value, &field.field_type, primary_key)?;
batch.put(spec.domain_id, index_key, Bytes::new());
}
Ok(())
}
}