use crate::db::schema::{
AcceptedEnumCatalog, AcceptedFieldKind, AcceptedSchemaSnapshot, AcceptedValueAdmissionContract,
AcceptedValueCatalogHandle, FieldId, FieldStorageDecode, FieldType, LeafCodec,
PersistedFieldSnapshot, PersistedIndexExpressionOp, PersistedIndexFieldPathSnapshot,
PersistedIndexKeyItemSnapshot, PersistedIndexKeySnapshot, PersistedIndexSnapshot,
PersistedNestedLeafSnapshot, PersistedSchemaSnapshot, SchemaFieldSlot,
enum_catalog::AcceptedValueContract, field_type_from_persisted_kind,
};
#[cfg(feature = "sql")]
use crate::db::schema::{SqlCapabilities, sql_capabilities_with_enum_catalog};
#[cfg(feature = "sql")]
use crate::{
db::schema::{
canonicalize_filter_literal_for_persisted_kind,
canonicalize_strict_sql_literal_for_persisted_kind, enum_catalog::ValueAdmissionBudget,
},
value::Value,
};
type SchemaFieldEntry = (String, SchemaFieldInfo);
#[cfg(feature = "sql")]
fn accepted_sql_capabilities(
kind: &AcceptedFieldKind,
value_catalog: &AcceptedValueCatalogHandle,
) -> SqlCapabilities {
sql_capabilities_with_enum_catalog(kind, value_catalog.enum_catalog())
}
#[cfg_attr(
not(any(test, feature = "sql")),
expect(dead_code, reason = "field lookup is query-owned")
)]
fn schema_field_info<'a>(
fields: &'a [SchemaFieldEntry],
name: &str,
) -> Option<&'a SchemaFieldInfo> {
fields
.binary_search_by(|(field_name, _)| field_name.as_str().cmp(name))
.ok()
.map(|index| &fields[index].1)
}
fn accepted_indexed_field_ids(snapshot: &PersistedSchemaSnapshot) -> Vec<FieldId> {
let mut field_ids = Vec::new();
for index in snapshot.indexes() {
for field in snapshot.fields() {
if index.key().references_field(field.id()) && !field_ids.contains(&field.id()) {
field_ids.push(field.id());
}
}
}
field_ids
}
fn accepted_field_name(snapshot: &PersistedSchemaSnapshot, field_id: FieldId) -> Option<&str> {
snapshot
.fields()
.iter()
.find(|field| field.id() == field_id)
.map(PersistedFieldSnapshot::name)
}
fn accepted_slot_index(slot: SchemaFieldSlot) -> usize {
usize::from(slot.get())
}
#[derive(Clone, Debug)]
#[cfg_attr(
not(any(test, feature = "sql")),
expect(
dead_code,
reason = "field metadata is retained by the shared commit schema view"
)
)]
struct SchemaFieldInfo {
slot: usize,
ty: FieldType,
nullable: bool,
leaf_codec: LeafCodec,
#[cfg(feature = "sql")]
sql_capabilities: SqlCapabilities,
#[cfg(feature = "sql")]
persisted_kind: AcceptedFieldKind,
accepted_value_contract: Option<AcceptedValueContract>,
indexed: bool,
nested_leaves: Vec<PersistedNestedLeafSnapshot>,
}
#[derive(Clone, Debug)]
pub(in crate::db) struct SchemaIndexInfo {
ordinal: u16,
physical_generation: u64,
name: String,
store: String,
unique: bool,
#[cfg_attr(
not(any(test, feature = "sql")),
expect(dead_code, reason = "generated provenance is query-owned")
)]
generated: bool,
fields: Vec<SchemaIndexFieldPathInfo>,
predicate_sql: Option<String>,
value_catalog: AcceptedValueCatalogHandle,
}
impl SchemaIndexInfo {
#[must_use]
pub(in crate::db) const fn ordinal(&self) -> u16 {
self.ordinal
}
#[must_use]
pub(in crate::db) const fn physical_generation(&self) -> u64 {
self.physical_generation
}
#[must_use]
pub(in crate::db) const fn name(&self) -> &str {
self.name.as_str()
}
#[must_use]
pub(in crate::db) const fn store(&self) -> &str {
self.store.as_str()
}
#[must_use]
pub(in crate::db) const fn unique(&self) -> bool {
self.unique
}
#[must_use]
#[cfg_attr(
not(any(test, feature = "sql")),
expect(dead_code, reason = "generated provenance is query-owned")
)]
pub(in crate::db) const fn generated(&self) -> bool {
self.generated
}
#[must_use]
pub(in crate::db) const fn fields(&self) -> &[SchemaIndexFieldPathInfo] {
self.fields.as_slice()
}
#[must_use]
pub(in crate::db) const fn predicate_sql(&self) -> Option<&str> {
match &self.predicate_sql {
Some(sql) => Some(sql.as_str()),
None => None,
}
}
#[must_use]
pub(in crate::db) fn accepted_field_contract<'a>(
&'a self,
field: &'a SchemaIndexFieldPathInfo,
) -> Option<AcceptedValueAdmissionContract<'a>> {
if !self
.fields
.iter()
.any(|candidate| std::ptr::eq(candidate, field))
{
return None;
}
field.accepted_value_contract(&self.value_catalog)
}
}
#[derive(Clone, Debug)]
pub(in crate::db) struct SchemaExpressionIndexInfo {
ordinal: u16,
physical_generation: u64,
name: String,
store: String,
unique: bool,
#[cfg_attr(
not(any(test, feature = "sql")),
expect(dead_code, reason = "generated provenance is query-owned")
)]
generated: bool,
key_items: Vec<SchemaExpressionIndexKeyItemInfo>,
predicate_sql: Option<String>,
value_catalog: AcceptedValueCatalogHandle,
}
impl SchemaExpressionIndexInfo {
#[must_use]
pub(in crate::db) const fn ordinal(&self) -> u16 {
self.ordinal
}
#[must_use]
pub(in crate::db) const fn physical_generation(&self) -> u64 {
self.physical_generation
}
#[must_use]
pub(in crate::db) const fn name(&self) -> &str {
self.name.as_str()
}
#[must_use]
pub(in crate::db) const fn store(&self) -> &str {
self.store.as_str()
}
#[must_use]
pub(in crate::db) const fn unique(&self) -> bool {
self.unique
}
#[must_use]
#[cfg_attr(
not(any(test, feature = "sql")),
expect(dead_code, reason = "generated provenance is query-owned")
)]
pub(in crate::db) const fn generated(&self) -> bool {
self.generated
}
#[must_use]
pub(in crate::db) const fn key_items(&self) -> &[SchemaExpressionIndexKeyItemInfo] {
self.key_items.as_slice()
}
#[must_use]
pub(in crate::db) const fn predicate_sql(&self) -> Option<&str> {
match &self.predicate_sql {
Some(sql) => Some(sql.as_str()),
None => None,
}
}
#[must_use]
pub(in crate::db) fn accepted_field_contract<'a>(
&'a self,
field: &'a SchemaIndexFieldPathInfo,
) -> Option<AcceptedValueAdmissionContract<'a>> {
if !self.key_items.iter().any(|item| match item {
SchemaExpressionIndexKeyItemInfo::FieldPath(candidate) => {
std::ptr::eq(candidate, field)
}
SchemaExpressionIndexKeyItemInfo::Expression(expression) => {
std::ptr::eq(expression.source(), field)
}
}) {
return None;
}
field.accepted_value_contract(&self.value_catalog)
}
}
#[derive(Clone, Debug)]
pub(in crate::db) enum SchemaExpressionIndexKeyItemInfo {
FieldPath(SchemaIndexFieldPathInfo),
Expression(Box<SchemaIndexExpressionInfo>),
}
#[derive(Clone, Debug)]
pub(in crate::db) struct SchemaIndexExpressionInfo {
op: PersistedIndexExpressionOp,
source: SchemaIndexFieldPathInfo,
canonical_text: String,
}
impl SchemaIndexExpressionInfo {
#[must_use]
pub(in crate::db) const fn op(&self) -> PersistedIndexExpressionOp {
self.op
}
#[must_use]
pub(in crate::db) const fn source(&self) -> &SchemaIndexFieldPathInfo {
&self.source
}
#[must_use]
pub(in crate::db) const fn canonical_text(&self) -> &str {
self.canonical_text.as_str()
}
}
#[derive(Clone, Debug)]
pub(in crate::db) struct SchemaIndexFieldPathInfo {
field_name: String,
slot: usize,
path: Vec<String>,
persisted_kind: AcceptedFieldKind,
accepted_value_contract: Option<Box<AcceptedValueContract>>,
nullable: bool,
}
impl SchemaIndexFieldPathInfo {
#[must_use]
pub(in crate::db) const fn field_name(&self) -> &str {
self.field_name.as_str()
}
#[must_use]
pub(in crate::db) const fn slot(&self) -> usize {
self.slot
}
#[must_use]
pub(in crate::db) const fn path(&self) -> &[String] {
self.path.as_slice()
}
#[must_use]
pub(in crate::db) fn persisted_kind(&self) -> Option<&AcceptedFieldKind> {
self.accepted_value_contract
.as_deref()
.map(AcceptedValueContract::kind)
.or(Some(&self.persisted_kind))
}
fn accepted_value_contract<'a>(
&'a self,
value_catalog: &'a AcceptedValueCatalogHandle,
) -> Option<AcceptedValueAdmissionContract<'a>> {
Some(AcceptedValueAdmissionContract::borrowed(
value_catalog,
self.accepted_value_contract.as_deref()?,
self.nullable,
))
}
}
#[derive(Clone, Debug)]
#[cfg_attr(
not(any(test, feature = "sql")),
expect(
dead_code,
reason = "field and identity metadata share the commit schema view with query planning"
)
)]
pub(crate) struct SchemaInfo {
fields: Vec<SchemaFieldEntry>,
indexes: Vec<SchemaIndexInfo>,
expression_indexes: Vec<SchemaExpressionIndexInfo>,
value_catalog: AcceptedValueCatalogHandle,
entity_name: Option<String>,
primary_key_names: Vec<String>,
}
#[cfg_attr(
not(any(test, feature = "sql")),
expect(
dead_code,
reason = "query-only accessors share the commit schema view"
)
)]
impl SchemaInfo {
#[must_use]
pub(crate) fn field(&self, name: &str) -> Option<&FieldType> {
schema_field_info(self.fields.as_slice(), name).map(|field| &field.ty)
}
#[must_use]
pub(in crate::db) fn accepted_field_contract(
&self,
name: &str,
) -> Option<AcceptedValueAdmissionContract<'_>> {
let field = schema_field_info(self.fields.as_slice(), name)?;
Some(AcceptedValueAdmissionContract::borrowed(
&self.value_catalog,
field.accepted_value_contract.as_ref()?,
field.nullable,
))
}
#[must_use]
pub(in crate::db) fn field_slot_index(&self, name: &str) -> Option<usize> {
schema_field_info(self.fields.as_slice(), name).map(|field| field.slot)
}
#[must_use]
pub(in crate::db) fn field_names_in_slot_order(&self) -> Vec<&str> {
let mut fields = self
.fields
.iter()
.map(|(name, field)| (field.slot, name.as_str()))
.collect::<Vec<_>>();
fields.sort_unstable_by_key(|(slot, _)| *slot);
fields.into_iter().map(|(_, name)| name).collect()
}
#[must_use]
pub(in crate::db) fn field_slot_has_scalar_leaf(&self, slot: usize) -> bool {
self.fields
.iter()
.find(|(_, field)| field.slot == slot)
.is_some_and(|(_, field)| matches!(field.leaf_codec, LeafCodec::Scalar(_)))
}
#[must_use]
#[cfg(any(test, feature = "sql"))]
pub(in crate::db) fn entity_name(&self) -> Option<&str> {
self.entity_name.as_deref()
}
#[must_use]
pub(in crate::db) fn scalar_primary_key_name(&self) -> Option<&str> {
(self.primary_key_names.len() == 1).then(|| self.primary_key_names[0].as_str())
}
#[must_use]
pub(in crate::db) const fn primary_key_names(&self) -> &[String] {
self.primary_key_names.as_slice()
}
#[must_use]
pub(in crate::db) fn field_is_indexed(&self, name: &str) -> bool {
schema_field_info(self.fields.as_slice(), name).is_some_and(|field| field.indexed)
}
#[must_use]
pub(in crate::db) fn enum_catalog(&self) -> &AcceptedEnumCatalog {
self.value_catalog.enum_catalog()
}
#[must_use]
#[cfg_attr(
target_arch = "wasm32",
allow(
dead_code,
reason = "schema DDL binding is host-owned even when SQL query support is built for wasm"
)
)]
pub(in crate::db) const fn value_catalog_handle(&self) -> &AcceptedValueCatalogHandle {
&self.value_catalog
}
#[must_use]
pub(in crate::db) const fn field_path_indexes(&self) -> &[SchemaIndexInfo] {
self.indexes.as_slice()
}
#[must_use]
pub(in crate::db) const fn expression_indexes(&self) -> &[SchemaExpressionIndexInfo] {
self.expression_indexes.as_slice()
}
#[must_use]
#[cfg(feature = "sql")]
pub(in crate::db) fn sql_capabilities(&self, name: &str) -> Option<SqlCapabilities> {
schema_field_info(self.fields.as_slice(), name).map(|field| field.sql_capabilities)
}
#[must_use]
#[cfg(feature = "sql")]
pub(in crate::db) fn nested_sql_capabilities(
&self,
name: &str,
segments: &[String],
) -> Option<SqlCapabilities> {
let field = schema_field_info(self.fields.as_slice(), name)?;
field
.nested_leaves
.iter()
.find(|leaf| leaf.path() == segments)
.map(|leaf| accepted_sql_capabilities(leaf.kind(), &self.value_catalog))
}
#[must_use]
pub(crate) fn nested_field_type(&self, name: &str, segments: &[String]) -> Option<FieldType> {
let field = schema_field_info(self.fields.as_slice(), name)?;
field
.nested_leaves
.iter()
.find(|leaf| leaf.path() == segments)
.map(|leaf| field_type_from_persisted_kind(leaf.kind()))
}
#[must_use]
pub(crate) fn field_has_nested_paths(&self, name: &str) -> bool {
schema_field_info(self.fields.as_slice(), name)
.is_some_and(|field| !field.nested_leaves.is_empty())
}
#[cfg(feature = "sql")]
#[must_use]
pub(in crate::db) fn canonicalize_strict_sql_literal(
&self,
field_name: &str,
value: &Value,
) -> Option<Value> {
let field = schema_field_info(self.fields.as_slice(), field_name)?;
let kind = &field.persisted_kind;
if matches!(kind, AcceptedFieldKind::Enum { .. }) {
let Value::Text(variant) = value else {
return None;
};
let contract = self.accepted_field_contract(field_name)?;
let input = crate::value::InputValue::Enum(crate::value::InputValueEnum::loose(
variant.clone(),
));
return contract
.normalize_input_to_runtime(input, &mut ValueAdmissionBudget::standard())
.ok();
}
canonicalize_strict_sql_literal_for_persisted_kind(kind, value)
}
#[must_use]
#[cfg(feature = "sql")]
pub(in crate::db) fn canonicalize_filter_literal(
&self,
field_name: &str,
value: &Value,
) -> Option<Value> {
let field = schema_field_info(self.fields.as_slice(), field_name)?;
let kind = &field.persisted_kind;
if matches!(kind, AcceptedFieldKind::Enum { .. }) {
let Value::Text(variant) = value else {
return None;
};
let contract = self.accepted_field_contract(field_name)?;
let input = crate::value::InputValue::Enum(crate::value::InputValueEnum::loose(
variant.clone(),
));
return contract
.normalize_input_to_runtime(input, &mut ValueAdmissionBudget::standard())
.ok();
}
canonicalize_filter_literal_for_persisted_kind(kind, value)
}
#[must_use]
pub(in crate::db) fn from_accepted_snapshot_and_catalog(
schema: &AcceptedSchemaSnapshot,
value_catalog: AcceptedValueCatalogHandle,
include_expression_indexes: bool,
) -> Self {
Self::from_snapshot(schema, value_catalog, include_expression_indexes)
}
fn from_snapshot(
schema: &AcceptedSchemaSnapshot,
value_catalog: AcceptedValueCatalogHandle,
include_expression_indexes: bool,
) -> Self {
let snapshot = schema.persisted_snapshot();
let indexed_field_ids = accepted_indexed_field_ids(snapshot);
let mut fields = snapshot
.fields()
.iter()
.map(|field| {
let slot = snapshot
.row_layout()
.slot_for_field(field.id())
.map_or_else(|| usize::from(field.slot().get()), accepted_slot_index);
let accepted_value_contract = AcceptedValueContract::from_accepted_field(
&value_catalog,
field.kind(),
field.storage_decode(),
)
.ok();
debug_assert!(accepted_value_contract.is_some());
(
field.name().to_string(),
SchemaFieldInfo {
slot,
ty: field_type_from_persisted_kind(field.kind()),
nullable: field.nullable(),
leaf_codec: field.leaf_codec(),
#[cfg(feature = "sql")]
sql_capabilities: accepted_sql_capabilities(field.kind(), &value_catalog),
#[cfg(feature = "sql")]
persisted_kind: field.kind().clone(),
accepted_value_contract,
indexed: indexed_field_ids.contains(&field.id()),
nested_leaves: field.nested_leaves().to_vec(),
},
)
})
.collect::<Vec<_>>();
fields.sort_unstable_by(|(left, _), (right, _)| left.cmp(right));
let primary_key_names = snapshot
.primary_key_field_ids()
.iter()
.filter_map(|field_id| {
snapshot
.fields()
.iter()
.find(|field| field.id() == *field_id)
.map(|field| field.name().to_string())
})
.collect();
Self {
fields,
indexes: snapshot
.indexes()
.iter()
.filter_map(|index| {
schema_index_info_from_accepted_index(index, snapshot, &value_catalog)
})
.collect(),
expression_indexes: snapshot
.indexes()
.iter()
.filter_map(|index| {
include_expression_indexes
.then(|| {
schema_expression_index_info_from_accepted_index(
index,
snapshot,
&value_catalog,
)
})
.flatten()
})
.collect(),
value_catalog,
entity_name: Some(schema.entity_name().to_string()),
primary_key_names,
}
}
}
pub(in crate::db) fn schema_index_info_from_accepted_index(
index: &PersistedIndexSnapshot,
snapshot: &PersistedSchemaSnapshot,
value_catalog: &AcceptedValueCatalogHandle,
) -> Option<SchemaIndexInfo> {
if !index.key().is_field_path_only() {
return None;
}
Some(SchemaIndexInfo {
ordinal: index.ordinal(),
physical_generation: index.physical_generation(),
name: index.name().to_string(),
store: index.store().to_string(),
unique: index.unique(),
generated: index.generated(),
fields: index
.key()
.field_paths()
.iter()
.map(|path| schema_index_field_path_info_from_accepted(path, snapshot, value_catalog))
.collect(),
predicate_sql: index.predicate_sql().map(str::to_string),
value_catalog: value_catalog.clone(),
})
}
pub(in crate::db) fn schema_expression_index_info_from_accepted_index(
index: &PersistedIndexSnapshot,
snapshot: &PersistedSchemaSnapshot,
value_catalog: &AcceptedValueCatalogHandle,
) -> Option<SchemaExpressionIndexInfo> {
let PersistedIndexKeySnapshot::Items(items) = index.key() else {
return None;
};
if !items
.iter()
.any(|item| matches!(item, PersistedIndexKeyItemSnapshot::Expression(_)))
{
return None;
}
Some(SchemaExpressionIndexInfo {
ordinal: index.ordinal(),
physical_generation: index.physical_generation(),
name: index.name().to_string(),
store: index.store().to_string(),
unique: index.unique(),
generated: index.generated(),
key_items: items
.iter()
.map(|item| schema_expression_index_key_item_info(item, snapshot, value_catalog))
.collect(),
predicate_sql: index.predicate_sql().map(str::to_string),
value_catalog: value_catalog.clone(),
})
}
fn schema_expression_index_key_item_info(
item: &PersistedIndexKeyItemSnapshot,
snapshot: &PersistedSchemaSnapshot,
value_catalog: &AcceptedValueCatalogHandle,
) -> SchemaExpressionIndexKeyItemInfo {
match item {
PersistedIndexKeyItemSnapshot::FieldPath(path) => {
SchemaExpressionIndexKeyItemInfo::FieldPath(schema_index_field_path_info_from_accepted(
path,
snapshot,
value_catalog,
))
}
PersistedIndexKeyItemSnapshot::Expression(expression) => {
SchemaExpressionIndexKeyItemInfo::Expression(Box::new(SchemaIndexExpressionInfo {
op: expression.op(),
source: schema_index_field_path_info_from_accepted(
expression.source(),
snapshot,
value_catalog,
),
canonical_text: expression.canonical_text().to_string(),
}))
}
}
}
fn schema_index_field_path_info_from_accepted(
path: &PersistedIndexFieldPathSnapshot,
snapshot: &PersistedSchemaSnapshot,
value_catalog: &AcceptedValueCatalogHandle,
) -> SchemaIndexFieldPathInfo {
let field_name = accepted_field_name(snapshot, path.field_id())
.or_else(|| path.path().first().map(String::as_str))
.unwrap_or_default()
.to_string();
let accepted_value_contract = AcceptedValueContract::from_accepted_field(
value_catalog,
path.kind(),
FieldStorageDecode::ByKind,
)
.ok()
.map(Box::new);
debug_assert!(accepted_value_contract.is_some());
SchemaIndexFieldPathInfo {
field_name,
slot: accepted_slot_index(path.slot()),
path: path.path().to_vec(),
persisted_kind: path.kind().clone(),
accepted_value_contract,
nullable: path.nullable(),
}
}