use anyhow::{Result, bail, ensure};
use surrealdb_datastore::values::graph::{
ADJACENCY_FLAG_PROPS, AdjacencySection, AdjacencyValue, InlineProps,
};
use surrealdb_types::ToSql;
use crate::catalog::{FieldDefinition, LATEST_EDGE_VARIANT};
use crate::ctx::FrozenContext;
use crate::dbs::Options;
use crate::doc::{Document, Error as DocError, Extras};
use crate::expr::Part;
use crate::expr::paths::{IN, OUT};
use crate::key::schema::GraphPointerKey;
use crate::val::Value;
pub(crate) fn inline_fields(fields: &[FieldDefinition]) -> Vec<&FieldDefinition> {
fn root(f: &FieldDefinition) -> Option<&str> {
match f.name.0.as_slice() {
[Part::Field(name)] => Some(name.as_str()),
_ => None,
}
}
let mut inline: Vec<&FieldDefinition> = fields.iter().filter(|f| f.inline).collect();
inline.sort_by(|a, b| match (root(a), root(b)) {
(Some(a), Some(b)) => a.as_bytes().cmp(b.as_bytes()),
_ => a.name.to_raw_string().cmp(&b.name.to_raw_string()),
});
inline
}
pub(crate) fn inline_field_name(f: &FieldDefinition) -> String {
match f.name.0.as_slice() {
[Part::Field(name)] => name.as_str().to_owned(),
_ => f.name.to_raw_string(),
}
}
pub(crate) fn encode_inline_section(
doc: &Value,
fields: &[&FieldDefinition],
generation: u32,
cap: usize,
) -> Result<AdjacencySection> {
ensure!(
fields.len() <= u8::MAX as usize,
"an inline props payload cannot carry more than 255 values"
);
let none = Value::None;
let values: Vec<std::borrow::Cow<'_, Value>> = fields
.iter()
.map(|f| match (f.name.0.as_slice(), doc) {
([Part::Field(name)], Value::Object(obj)) => {
std::borrow::Cow::Borrowed(obj.get(name.as_str()).unwrap_or(&none))
}
_ => std::borrow::Cow::Owned(doc.pick(&f.name)),
})
.collect();
let body = match InlineProps::encode_values_capped(
generation,
values.iter().map(|v| v.as_ref()),
fields.len() as u8,
cap,
)? {
Some(body) => body,
None => InlineProps::spilled(generation).encode()?,
};
Ok(AdjacencySection {
flag_bit: ADJACENCY_FLAG_PROPS.trailing_zeros() as u8,
body,
})
}
impl Document {
pub(super) async fn store_inline_adjacency_data(
&mut self,
ctx: &FrozenContext,
_opt: &Options,
) -> Result<()> {
if matches!(self.extras, Extras::Relate(_, _, _)) {
return Ok(());
}
if !self.initial.doc.is_edge()
|| self.initial.doc.edge_variant() != Some(LATEST_EDGE_VARIANT)
{
return Ok(());
}
let tb = self.doc_ctx.tb()?;
if tb.drop
|| matches!(&tb.table_type, crate::catalog::TableType::Relation(rel) if rel.lightweight)
{
return Ok(());
}
let fields = self.doc_ctx.fd()?;
let inline = inline_fields(fields);
if inline.is_empty() {
return Ok(());
}
let initial = self.initial.doc.as_ref();
let Value::RecordId(ref l) = initial.pick(&IN) else {
fail!("Expected a record id for the `in` field of an edge");
};
let Value::RecordId(ref r) = initial.pick(&OUT) else {
fail!("Expected a record id for the `out` field of an edge");
};
let current = self.current.doc.as_ref();
match current.pick(&IN) {
Value::RecordId(ref id) if id == l => {}
v => bail!(DocError::InOverride {
value: v.to_sql(),
}),
}
match current.pick(&OUT) {
Value::RecordId(ref id) if id == r => {}
v => bail!(DocError::OutOverride {
value: v.to_sql(),
}),
}
let rid = self.id()?;
let ns = self.doc_ctx.ns().namespace_id;
let db = self.doc_ctx.db().database_id;
let cap = ctx.config.idx.graph_inline_props_cap;
let section = encode_inline_section(current, &inline, tb.graph_inline_gen, cap)?;
let value = AdjacencyValue {
tombstone: false,
sections: vec![section],
};
let txn = ctx.tx();
let ltr = GraphPointerKey {
ns,
db,
tb: std::borrow::Cow::Borrowed(&l.table),
id: std::borrow::Cow::Borrowed(&l.key),
dir: crate::expr::Dir::Out,
foreign_table: std::borrow::Cow::Borrowed(&rid.table),
foreign_key: std::borrow::Cow::Borrowed(&rid.key),
target_table: std::borrow::Cow::Borrowed(&r.table),
target_key: std::borrow::Cow::Borrowed(&r.key),
};
let rtl = GraphPointerKey {
ns,
db,
tb: std::borrow::Cow::Borrowed(&r.table),
id: std::borrow::Cow::Borrowed(&r.key),
dir: crate::expr::Dir::In,
foreign_table: std::borrow::Cow::Borrowed(&rid.table),
foreign_key: std::borrow::Cow::Borrowed(&rid.key),
target_table: std::borrow::Cow::Borrowed(&l.table),
target_key: std::borrow::Cow::Borrowed(&l.key),
};
futures::try_join!(txn.set_key(<r, &value), txn.set_key(&rtl, &value))?;
Ok(())
}
}