///|
/// Metadata predicates used by application-facing retrieval APIs.
pub(all) enum MetadataFilter {
MetadataEquals(String, String)
MetadataNotEquals(String, String)
MetadataPrefix(String, String)
MetadataContains(String, String)
MetadataIn(String, Array[String])
}
///|
/// A small composable expression for common metadata policies.
pub(all) enum FilterExpression {
MatchAll
MatchNone
AllOf(Array[MetadataFilter])
AnyOf(Array[MetadataFilter])
Not(MetadataFilter)
}
///|
/// Construct an equality predicate.
pub fn metadata_equals(key : String, value : String) -> MetadataFilter {
MetadataEquals(key, value)
}
///|
/// Construct a negative equality predicate.
pub fn metadata_not_equals(key : String, value : String) -> MetadataFilter {
MetadataNotEquals(key, value)
}
///|
/// Construct a string-prefix predicate.
pub fn metadata_prefix(key : String, prefix : String) -> MetadataFilter {
MetadataPrefix(key, prefix)
}
///|
/// Construct a substring predicate.
pub fn metadata_contains(key : String, fragment : String) -> MetadataFilter {
MetadataContains(key, fragment)
}
///|
/// Construct a membership predicate.
pub fn metadata_in(key : String, values : Array[String]) -> MetadataFilter {
MetadataIn(key, values)
}
///|
/// Return whether one metadata predicate matches a metadata array.
pub fn matches_metadata_filter(
metadata : Array[(String, String)],
filter : MetadataFilter,
) -> Bool {
match filter {
MetadataEquals(key, value) => has_metadata_pair(metadata, key, value)
MetadataNotEquals(key, value) => !has_metadata_pair(metadata, key, value)
MetadataPrefix(key, prefix) => {
for pair in metadata {
if pair.0 == key && pair.1.has_prefix(prefix) {
return true
}
}
false
}
MetadataContains(key, fragment) => {
for pair in metadata {
if pair.0 == key && pair.1.contains(fragment) {
return true
}
}
false
}
MetadataIn(key, values) => {
for pair in metadata {
if pair.0 == key && string_array_contains(values, pair.1) {
return true
}
}
false
}
}
}
///|
/// Evaluate a composed metadata expression.
pub fn matches_filter_expression(
metadata : Array[(String, String)],
expression : FilterExpression,
) -> Bool {
match expression {
MatchAll => true
MatchNone => false
AllOf(filters) => {
for filter in filters {
if !matches_metadata_filter(metadata, filter) {
return false
}
}
true
}
AnyOf(filters) => {
for filter in filters {
if matches_metadata_filter(metadata, filter) {
return true
}
}
false
}
Not(filter) => !matches_metadata_filter(metadata, filter)
}
}
///|
/// Filter documents without changing the input order or records.
pub fn filter_documents_expression(
docs : Array[Document],
expression : FilterExpression,
) -> Array[Document] {
let result = []
for doc in docs {
if matches_filter_expression(doc.metadata, expression) {
result.push(doc)
}
}
result
}
///|
/// Filter ranked results after retrieval while preserving ranking order.
pub fn filter_results_expression(
results : Array[SearchResult],
expression : FilterExpression,
) -> Array[SearchResult] {
let result = []
for item in results {
if matches_filter_expression(item.metadata, expression) {
result.push(item)
}
}
result
}
///|
/// Count how many records satisfy an expression.
pub fn count_matching_documents(
docs : Array[Document],
expression : FilterExpression,
) -> Int {
filter_documents_expression(docs, expression).length()
}
///|
/// Produce a stable human-readable description for audit logs.
pub fn describe_filter(filter : MetadataFilter) -> String {
match filter {
MetadataEquals(key, value) => key + " == " + value
MetadataNotEquals(key, value) => key + " != " + value
MetadataPrefix(key, value) => key + " starts_with " + value
MetadataContains(key, value) => key + " contains " + value
MetadataIn(key, values) => key + " in [" + values.join(", ") + "]"
}
}
///|
/// Produce a stable human-readable description for an expression.
pub fn describe_filter_expression(expression : FilterExpression) -> String {
match expression {
MatchAll => "match_all"
MatchNone => "match_none"
AllOf(filters) => describe_filter_array(filters, " AND ")
AnyOf(filters) => describe_filter_array(filters, " OR ")
Not(filter) => "NOT (" + describe_filter(filter) + ")"
}
}
///|
fn describe_filter_array(
filters : Array[MetadataFilter],
separator : String,
) -> String {
let descriptions = []
for filter in filters {
descriptions.push(describe_filter(filter))
}
"(" + descriptions.join(separator) + ")"
}
///|
fn has_metadata_pair(
metadata : Array[(String, String)],
key : String,
value : String,
) -> Bool {
for pair in metadata {
if pair.0 == key && pair.1 == value {
return true
}
}
false
}
///|
fn string_array_contains(values : Array[String], value : String) -> Bool {
for candidate in values {
if candidate == value {
return true
}
}
false
}