use linkrs_core::types::{LabelId, TagInfo};
use linkrs_core::{StorageError, StorageResult, Value};
use std::sync::Arc;
use super::super::context::GraphStorageContext;
use super::batch::SerialBatchState;
pub(super) fn apply_tag_constraints_prechecked(
ctx: &GraphStorageContext,
space_id: u64,
tag: &TagInfo,
serial_state: &mut SerialBatchState,
props: Vec<(Arc<str>, Value)>,
) -> StorageResult<Vec<(Arc<str>, Value)>> {
let mut result = props;
for prop_def in &tag.properties {
if let Some((_, value)) = result
.iter()
.find(|(name, _)| name.as_ref() == prop_def.name.as_str())
{
if !prop_def.nullable && value.is_null() {
return Err(StorageError::null_value_not_allowed(&prop_def.name));
}
if prop_def.serial {
serial_state.check_explicit(
ctx,
space_id,
&tag.tag_name,
tag.tag_id,
&prop_def.name,
value,
)?;
}
continue;
}
if prop_def.serial {
let key = super::super::serial::SerialKey::new(space_id, tag.tag_name.clone());
let next = ctx.serial_allocator().next(&key);
result.push((prop_def.name.as_str().into(), Value::BigInt(next as i64)));
continue;
}
if let Some(default) = &prop_def.default {
result.push((prop_def.name.as_str().into(), default.clone()));
} else if !prop_def.nullable {
return Err(StorageError::null_value_not_allowed(&prop_def.name));
}
}
Ok(result)
}
pub(super) fn apply_tag_constraints(
ctx: &GraphStorageContext,
space: &str,
tag_name: &str,
props: Vec<(Arc<str>, Value)>,
) -> StorageResult<Vec<(Arc<str>, Value)>> {
let tag = ctx
.schema_manager()
.get_tag(space, tag_name)?
.ok_or_else(|| StorageError::label_not_found(tag_name.to_string()))?;
let space_id = ctx
.schema_manager()
.get_space(space)?
.map(|space| space.space_id)
.unwrap_or(0);
let mut result = props;
for prop_def in &tag.properties {
if let Some((_, value)) = result
.iter()
.find(|(name, _)| name.as_ref() == prop_def.name.as_str())
{
if !prop_def.nullable && value.is_null() {
return Err(StorageError::null_value_not_allowed(&prop_def.name));
}
if prop_def.serial {
validate_explicit_serial_value(
ctx,
space_id,
tag_name,
tag.tag_id,
&prop_def.name,
value,
super::super::serial::scan_vertex_serial_column,
)?;
}
continue;
}
if prop_def.serial {
let key = super::super::serial::SerialKey::new(space_id, tag_name.to_string());
let next = ctx.serial_allocator().next(&key);
result.push((prop_def.name.as_str().into(), Value::BigInt(next as i64)));
continue;
}
if let Some(default) = &prop_def.default {
result.push((prop_def.name.as_str().into(), default.clone()));
} else if !prop_def.nullable {
return Err(StorageError::null_value_not_allowed(&prop_def.name));
}
}
Ok(result)
}
pub(super) fn apply_edge_type_constraints(
ctx: &GraphStorageContext,
space: &str,
edge_type: &str,
props: Vec<(std::sync::Arc<str>, Value)>,
) -> StorageResult<Vec<(std::sync::Arc<str>, Value)>> {
let et = ctx
.schema_manager()
.get_edge_type(space, edge_type)?
.ok_or_else(|| StorageError::label_not_found(edge_type.to_string()))?;
let space_id = ctx
.schema_manager()
.get_space(space)?
.map(|space| space.space_id)
.unwrap_or(0);
let mut result = props;
for prop_def in &et.properties {
if let Some((_, value)) = result
.iter()
.find(|(name, _)| name.as_ref() == prop_def.name.as_str())
{
if !prop_def.nullable && value.is_null() {
return Err(StorageError::null_value_not_allowed(&prop_def.name));
}
if prop_def.serial {
validate_explicit_serial_value(
ctx,
space_id,
edge_type,
et.edge_type_id,
&prop_def.name,
value,
|ctx, label, prop_name| {
Ok(super::super::serial::scan_edge_serial_column(
ctx, label, prop_name,
))
},
)?;
}
continue;
}
if prop_def.serial {
let key = super::super::serial::SerialKey::new(space_id, edge_type.to_string());
let next = ctx.serial_allocator().next(&key);
result.push((prop_def.name.as_str().into(), Value::BigInt(next as i64)));
continue;
}
if let Some(default) = &prop_def.default {
result.push((prop_def.name.as_str().into(), default.clone()));
} else if !prop_def.nullable {
return Err(StorageError::null_value_not_allowed(&prop_def.name));
}
}
Ok(result)
}
fn validate_explicit_serial_value(
ctx: &GraphStorageContext,
space_id: u64,
table_name: &str,
label: LabelId,
prop_name: &str,
value: &Value,
scan_column: impl FnOnce(
&GraphStorageContext,
LabelId,
&str,
) -> StorageResult<Option<super::super::serial::SerialColumnScan>>,
) -> StorageResult<()> {
let Some(integer) = serial_value_as_i64(value) else {
return Ok(());
};
if integer >= 0 {
if let Some(scan) = scan_column(ctx, label, prop_name)? {
if scan.contains(integer) {
return Err(StorageError::invalid_operation(format!(
"Duplicate value {} for SERIAL column '{}': the value is already allocated",
integer, prop_name
)));
}
}
ctx.serial_allocator().advance_to(
&super::super::serial::SerialKey::new(space_id, table_name),
integer as u64,
);
}
Ok(())
}
pub(super) fn serial_value_as_i64(value: &Value) -> Option<i64> {
match value {
Value::BigInt(v) => Some(*v),
Value::Int(v) => Some(*v as i64),
Value::SmallInt(v) => Some(*v as i64),
_ => None,
}
}