use std::fmt;
use crate::content::node::{NodeState, PropertyState, PropertyValues};
use crate::content::property::{PropertyType, PropertyValue};
pub mod counter;
pub mod definition;
pub mod definitions_json;
pub mod definitions_json_reader;
pub mod inventory;
pub mod lanes;
pub mod lucene;
pub mod path_filter;
pub mod property;
pub mod status;
pub mod value_pattern;
pub use definition::{IndexDefinition, IndexType, IndexingMode, ReindexState, index_paths};
pub use definitions_json::{ChildFilter, RenderOptions, render as render_definitions_json};
pub use inventory::{IndexInfo, IndexInventory};
pub use lanes::{AsyncLane, AsyncLanes};
pub use path_filter::{PathFilter, PathVerdict};
pub use status::{StatusNode, StoredDefinition, definition_drift};
pub use value_pattern::ValuePattern;
pub const INDEX_DEFINITIONS_NAME: &str = "oak:index";
pub const INDEX_DEFINITIONS_NODE_TYPE: &str = "oak:QueryIndexDefinition";
pub const INDEX_CONTENT_NODE_NAME: &str = ":index";
pub const ASYNC_NODE_NAME: &str = ":async";
pub type IndexResult<Value> = std::result::Result<Value, IndexError>;
#[derive(Debug)]
#[non_exhaustive]
pub enum IndexError {
Record(crate::Error),
NoLaneName {
definition_path: String,
},
SeveralLaneNames {
definition_path: String,
lane_names: Vec<String>,
},
RelativeFilterPath {
definition_path: String,
path_set: FilterPathSet,
value: String,
},
EmptyIncludeSet {
definition_path: String,
},
NonStringPrefixValue {
definition_path: String,
property_name: String,
},
UnsupportedValuePattern {
definition_path: String,
pattern: String,
},
UnsupportedDefinition {
definition_path: String,
feature: String,
},
UnsupportedNamePattern {
definition_path: String,
pattern: String,
reason: String,
},
UnparseableDate {
value: String,
},
NodeTypeIndexUnusable {
found: String,
},
NodeTypeIndexHasNoData {
property_name: String,
},
}
impl From<crate::Error> for IndexError {
fn from(error: crate::Error) -> Self {
IndexError::Record(error)
}
}
impl fmt::Display for IndexError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IndexError::Record(source) => write!(formatter, "{source}"),
IndexError::NoLaneName { definition_path } => write!(
formatter,
"the index definition at {definition_path} has an \"async\" property that names \
no lane: every value is \"sync\" or \"nrt\""
),
IndexError::SeveralLaneNames {
definition_path,
lane_names,
} => write!(
formatter,
"the index definition at {definition_path} names several asynchronous lanes \
({}); Oak accepts exactly one",
lane_names.join(", ")
),
IndexError::RelativeFilterPath {
definition_path,
path_set,
value,
} => write!(
formatter,
"the index definition at {definition_path} has a relative path {value:?} in its \
{path_set} list; Oak requires absolute paths"
),
IndexError::EmptyIncludeSet { definition_path } => write!(
formatter,
"the index definition at {definition_path} excludes every one of its included \
paths, so Oak cannot construct its path filter"
),
IndexError::NonStringPrefixValue {
definition_path,
property_name,
} => write!(
formatter,
"the index definition at {definition_path} stores a single-valued \
{property_name} that is not a STRING; Oak reads it as null and fails on the \
first value it tests"
),
IndexError::UnsupportedValuePattern {
definition_path,
pattern,
} => write!(
formatter,
"the index definition at {definition_path} restricts values with the regular \
expression {pattern:?}, which froe does not evaluate"
),
IndexError::UnsupportedDefinition {
definition_path,
feature,
} => write!(
formatter,
"the index definition at {definition_path} cannot be rebuilt natively: {feature}"
),
IndexError::UnsupportedNamePattern {
definition_path,
pattern,
reason,
} => write!(
formatter,
"the index definition at {definition_path} names properties with the regular \
expression {pattern:?}, which froe does not evaluate: it holds {reason}"
),
IndexError::UnparseableDate { value } => write!(
formatter,
"the date {value:?} is not one Jackrabbit's own ISO8601 parses, so Oak's \
indexing commit fails on it rather than skipping the document"
),
IndexError::NodeTypeIndexUnusable { found } => write!(
formatter,
"the node-type index at /oak:index/nodetype is unusable ({found}), so the index \
paths cannot be enumerated"
),
IndexError::NodeTypeIndexHasNoData { property_name } => write!(
formatter,
"no property index with an :index child covers {property_name}, so the node-type \
index cannot be queried for index definitions"
),
}
}
}
impl std::error::Error for IndexError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
IndexError::Record(source) => Some(source),
_ => None,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FilterPathSet {
Included,
Excluded,
}
impl fmt::Display for FilterPathSet {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FilterPathSet::Included => formatter.write_str("included"),
FilterPathSet::Excluded => formatter.write_str("excluded"),
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum IndexWarning {
IgnoredByOak {
condition: String,
},
IndexedButNeverSelected {
property_name: String,
stored_type: String,
},
ExcludedPrefixesIgnoredByQueries {
stored_type: String,
},
DeclaringNodeTypesMatchNothing {
stored_type: String,
},
UniqueIsNotBoolean {
stored_type: String,
},
ValuePatternNotEvaluated {
pattern: String,
},
NonRootDefinition,
CompositeMountPresent {
child_name: String,
},
NonRootDefinitionsNotEnumerated {
condition: String,
},
}
impl fmt::Display for IndexWarning {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IndexWarning::IgnoredByOak { condition } => write!(
formatter,
"ignored by Oak's indexer ({condition}), so this index is not maintained"
),
IndexWarning::IndexedButNeverSelected {
property_name,
stored_type,
} => write!(
formatter,
"{property_name} is stored as {stored_type}; the editor converts it and indexes \
it, but the query planner reads it strictly and never selects this index"
),
IndexWarning::ExcludedPrefixesIgnoredByQueries { stored_type } => write!(
formatter,
"valueExcludedPrefixes is stored as {stored_type}; queries ignore the exclusion \
and answer from an index the editor never populated with those values"
),
IndexWarning::DeclaringNodeTypesMatchNothing { stored_type } => write!(
formatter,
"declaringNodeTypes is stored as {stored_type} rather than Name[], so Oak's type \
predicate matches nothing and this index indexes nothing"
),
IndexWarning::UniqueIsNotBoolean { stored_type } => write!(
formatter,
"unique is stored as {stored_type} rather than Boolean, so Oak treats this as a \
mirror index whatever the value says"
),
IndexWarning::ValuePatternNotEvaluated { pattern } => write!(
formatter,
"valuePattern {pattern:?} is a regular expression froe does not evaluate"
),
IndexWarning::NonRootDefinition => {
formatter.write_str("the definition is not a child of /oak:index")
}
IndexWarning::CompositeMountPresent { child_name } => write!(
formatter,
"the hidden child {child_name} belongs to a composite-store mount, which froe \
reports but does not model"
),
IndexWarning::NonRootDefinitionsNotEnumerated { condition } => write!(
formatter,
"non-root index definitions were not enumerated ({condition})"
),
}
}
}
pub(crate) fn stored_type_name(property: &PropertyState) -> String {
let base = property.property_type.jcr_name();
match property.values {
PropertyValues::Single(_) => base.to_owned(),
PropertyValues::Multiple(_) => format!("{base}[]"),
}
}
pub(crate) fn children_in_tree_order<'provider>(
node: &NodeState<'provider>,
) -> crate::Result<Vec<(String, NodeState<'provider>)>> {
let entries = node.child_node_entries()?;
let Some(order) = node.property(":childOrder")? else {
return Ok(entries);
};
let Some(named) = strict_names(Some(&order)) else {
return Ok(entries);
};
Ok(named
.into_iter()
.filter_map(|name| {
entries
.iter()
.find(|(entry, _)| *entry == name)
.map(|(_, child)| (name, *child))
})
.collect())
}
pub(crate) fn values_of(property: &PropertyState) -> &[PropertyValue] {
match &property.values {
PropertyValues::Single(value) => std::slice::from_ref(value),
PropertyValues::Multiple(values) => values.as_slice(),
}
}
pub(crate) fn strict_name(property: Option<&PropertyState>) -> Option<&str> {
let property = property?;
match (&property.values, property.property_type) {
(PropertyValues::Single(PropertyValue::Name(text)), PropertyType::Name) => Some(text),
_ => None,
}
}
pub(crate) fn strict_names(property: Option<&PropertyState>) -> Option<Vec<String>> {
let property = property?;
match (&property.values, property.property_type) {
(PropertyValues::Multiple(values), PropertyType::Name) => Some(
values
.iter()
.filter_map(PropertyValue::as_text)
.collect::<Vec<_>>(),
),
_ => None,
}
}
pub(crate) fn strict_string(property: Option<&PropertyState>) -> Option<&str> {
let property = property?;
match (&property.values, property.property_type) {
(PropertyValues::Single(PropertyValue::String(text)), PropertyType::String) => Some(text),
_ => None,
}
}
pub(crate) fn strict_strings(property: Option<&PropertyState>) -> Option<Vec<String>> {
let property = property?;
match (&property.values, property.property_type) {
(PropertyValues::Multiple(values), PropertyType::String) => Some(
values
.iter()
.filter_map(PropertyValue::as_text)
.collect::<Vec<_>>(),
),
_ => None,
}
}
pub(crate) fn strict_boolean(property: Option<&PropertyState>) -> bool {
matches!(
property.map(|property| (&property.values, property.property_type)),
Some((
PropertyValues::Single(PropertyValue::Boolean(true)),
PropertyType::Boolean
))
)
}
pub(crate) fn converting_strings(property: Option<&PropertyState>) -> Vec<String> {
property.map_or_else(Vec::new, |property| {
values_of(property)
.iter()
.filter_map(PropertyValue::as_text)
.collect()
})
}
pub(crate) fn converting_boolean(property: Option<&PropertyState>) -> bool {
property
.and_then(|property| values_of(property).first())
.and_then(PropertyValue::as_text)
.is_some_and(|text| text.eq_ignore_ascii_case("true"))
}
pub(crate) fn converting_long(property: Option<&PropertyState>) -> Option<i64> {
let text = property
.and_then(|property| values_of(property).first())
.and_then(PropertyValue::as_text)?;
crate::java::parse_java_signed_decimal(&text, i64::MIN.into(), i64::MAX.into())
.and_then(|value| i64::try_from(value).ok())
}