mod answer;
mod entry;
mod failure;
mod matching;
mod plan;
mod rerank;
mod rerank_pipeline;
mod rerank_vectors;
#[cfg(all(test, unix))]
mod similar_after_tests;
#[cfg(all(test, unix))]
mod similar_text_tests;
#[cfg(all(test, unix))]
mod rerank_test_support;
mod rg;
mod shape;
pub(in crate::serve) mod window;
pub use entry::run;
#[cfg(test)]
#[path = "stages_tests.rs"]
mod stages_tests;
use answer::{Head, answer, fitted, named, rows_bytes};
use failure::{refused, start_failure};
use plan::present;
use rerank_pipeline::worked;
use std::collections::BTreeMap;
use std::num::{NonZeroU32, NonZeroU64, NonZeroUsize};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use haematite::db::ReadOnlyDatabase;
use haematite_query::Ready;
use haematite_query::page::{Allowance, BINDING_NAMES, Origin, Resume};
use serde_json::value::RawValue;
use serde_json::{Value, json};
use super::admission::Slots;
use super::parser::Refused;
use super::source::{AuthorizedSource, Place};
use super::{Binding, Continuation, Engine, Rows, Seal, Settings, origins};
use crate::serve::access::Access;
use crate::serve::contracts::Caller;
use crate::serve::protocol::{QueryRequest, Selection};
use crate::serve::service::Service;
use crate::serve::storage::{Captured, Catalogue, Failure};
const ID_BYTES: usize = 512;
pub struct Context<'a> {
pub access: &'a Access,
pub reader: &'a ReadOnlyDatabase,
pub catalogue: &'a Catalogue,
pub slots: &'a Slots,
pub settings: Option<&'a Settings>,
pub max_page: NonZeroU32,
pub seal: &'a Seal,
}
pub(in crate::serve) fn served(
context: &Context<'_>,
request: &QueryRequest,
response_byte_limit: Option<NonZeroUsize>,
service: Option<&Service>,
) -> Result<Box<RawValue>, Failure> {
let token = continuation(request, context.seal)?;
let settings = settings(context, request.page.rows)?;
let budget = budget(request.page.bytes, response_byte_limit)?;
let now = token
.as_ref()
.map_or_else(clock, |token| Ok(token.now.clone()))?;
let parameters = parameters(&request.parameters, &now)?;
let parsed = shape::parse(&request.query)?;
if let Err(refused @ Refused::Window(..)) = &parsed {
return Err(Failure::new(refused.code(), refused.to_string()));
}
entry::ready(service, parsed.as_ref().ok())?;
let at = context
.catalogue
.capture_held(&moment(&request.selection, token.as_ref()))?;
let asked = Asked {
request,
token: token.as_ref(),
now: &now,
at: &at,
budget,
scan: settings.scan_records.get(),
};
if let Ok(plan) = &parsed {
plan::check(context.reader, &at, plan)?;
}
if request.rows == Rows::Origins
&& let Some(executed) = plan::unworked(
&request.query,
parsed.as_ref().ok(),
context,
&at,
&now,
settings.scan_records.get(),
)?
{
return origins_only(context, &asked, &executed);
}
worked(context, &asked, settings, parsed, parameters, service)
}
struct Asked<'a> {
request: &'a QueryRequest,
token: Option<&'a Continuation>,
now: &'a RawValue,
at: &'a Captured,
budget: NonZeroUsize,
scan: u64,
}
fn origins_only(
context: &Context<'_>,
asked: &Asked<'_>,
executed: &plan::Executed,
) -> Result<Box<RawValue>, Failure> {
let scope = &executed.scope;
let limits = executed.limits();
let binding = bind(asked.request, &unworked_engine(), asked.scan)?;
if let Some(token) = asked.token {
token.check_binding(&binding)?;
}
present(context.reader, asked.at, scope)?;
let resume = asked.token.map(|token| &token.resume);
let read_before = resume.map_or(0, |resume| resume.records_read);
let start = resume.map(|resume| Place {
collection: resume.pending.collection.clone(),
id: resume.pending.id.clone(),
after: resume.passed,
});
let mut source = AuthorizedSource::origins(
context.access,
context.reader,
asked.at,
scope,
start.as_ref(),
asked.scan,
)
.map_err(|error| refused(&error))?;
let widest = widest_next(
asked.at,
named(scope),
asked.now,
&binding,
context.seal,
limits.len(),
)?;
let mut head = Head {
reading: executed.reading,
stages: &executed.names,
collection: named(scope),
read: u64::MAX,
skipped: u64::MAX,
search: &Value::Null,
unscored: None,
next: Some(&widest),
};
let envelope = answer(asked.at, &json!([]), &Value::Null, &head);
let allowance = Allowance {
rows: asked.request.page.rows,
bytes: rows_bytes(asked.budget, &envelope)?,
scan_records: read_before.saturating_add(asked.scan),
};
let page =
origins::page(&mut source, &allowance, resume, &limits).map_err(|error| refused(&error))?;
let next = page
.next
.map(|resume| {
let at = asked.at;
let now = asked.now.to_owned();
Continuation::new(at.branch.clone(), at.version, resume, now, binding)?
.encode(context.seal)
})
.transpose()?;
let rows = serde_json::value::to_raw_value(&page.rows).map_err(Failure::storage)?;
head.read = page.records_read;
head.skipped = 0;
head.next = next.as_deref();
fitted(
answer(asked.at, &json!([]), &Value::Null, &head),
&rows,
asked.budget,
)
}
fn settings<'a>(context: &Context<'a>, rows: NonZeroU64) -> Result<&'a Settings, Failure> {
if rows.get() > u64::from(context.max_page.get()) {
return Err(Failure::new(
"page_limit",
format!("maximum page size is {}", context.max_page),
));
}
context.settings.ok_or_else(|| {
Failure::new(
"query_engine_unavailable",
"query settings are not configured",
)
})
}
fn continuation(request: &QueryRequest, seal: &Seal) -> Result<Option<Continuation>, Failure> {
let Some(encoded) = request.continuation.as_deref() else {
return Ok(None);
};
let token = Continuation::decode(encoded, seal)?;
token.check_moment(&request.selection.branch, request.selection.version)?;
Ok(Some(token))
}
fn moment(selection: &Selection, token: Option<&Continuation>) -> Selection {
Selection {
branch: selection.branch.clone(),
version: token.map_or(selection.version, |token| Some(token.version)),
}
}
fn caller() -> Result<Caller, Failure> {
Caller::local("standalone")
}
fn worker_engine(ready: &Ready) -> Engine {
Engine {
protocol: ready.protocol,
worker: ready.worker.clone(),
engine: ready.engine.clone(),
jaq_core: ready.jaq_core.clone(),
jaq_std: ready.jaq_std.clone(),
jaq_json: ready.jaq_json.clone(),
}
}
fn unworked_engine() -> Engine {
Engine {
protocol: haematite_query::PROTOCOL,
worker: "haem".to_owned(),
engine: "origins".to_owned(),
jaq_core: String::new(),
jaq_std: String::new(),
jaq_json: String::new(),
}
}
fn bind(request: &QueryRequest, engine: &Engine, scan: u64) -> Result<Binding, Failure> {
let limits = Allowance {
rows: request.page.rows,
bytes: request.page.bytes,
scan_records: scan,
};
Binding::new(
&request.query,
&request.parameters,
engine,
&caller()?,
&limits,
request.rows,
)
}
fn widest_next(
at: &Captured,
collection: Option<&str>,
now: &RawValue,
binding: &Binding,
seal: &Seal,
limit_counts: usize,
) -> Result<String, Failure> {
let version = serde_json::value::to_raw_value(&at.version).map_err(Failure::storage)?;
let pending = Origin {
collection: collection.map_or_else(|| "\u{1}".repeat(ID_BYTES), str::to_owned),
id: "\u{1}".repeat(ID_BYTES),
version,
score: Some(-f64::MIN_POSITIVE),
};
let resume = Resume {
records_read: u64::MAX,
pending,
discard_outputs: u64::MAX,
passed: false,
passed_counts: vec![u64::MAX; limit_counts],
};
let widest = Continuation::new(
at.branch.clone(),
at.version,
resume,
now.to_owned(),
binding.clone(),
)?;
widest.encode(seal)
}
fn budget(
requested: NonZeroU64,
response_byte_limit: Option<NonZeroUsize>,
) -> Result<NonZeroUsize, Failure> {
let requested = NonZeroUsize::try_from(requested)
.map_err(|_| Failure::new("page_limit", "the requested bytes do not fit this machine"))?;
let bytes = response_byte_limit
.map_or_else(|| requested.get(), |limit| limit.get().min(requested.get()));
NonZeroUsize::new(bytes)
.ok_or_else(|| Failure::new("page_limit", "selected byte limit is zero"))
}
fn frame_bytes(budget: NonZeroUsize) -> Result<NonZeroU32, Failure> {
let bytes = u32::try_from(budget.get())
.unwrap_or(u32::MAX)
.max(haematite_query::MINIMUM_FRAME_BYTES);
NonZeroU32::new(bytes).ok_or_else(|| Failure::new("page_limit", "a frame of no bytes"))
}
fn parameters(
given: &BTreeMap<String, Box<RawValue>>,
now: &RawValue,
) -> Result<BTreeMap<String, Box<RawValue>>, Failure> {
for name in given.keys() {
if name == "now" || BINDING_NAMES.contains(&name.as_str()) {
return Err(Failure::new(
"query_parameter_reserved",
format!("${name} is reserved and cannot be supplied"),
));
}
}
let mut parameters = given.clone();
parameters.insert("now".to_owned(), now.to_owned());
Ok(parameters)
}
fn clock() -> Result<Box<RawValue>, Failure> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|error| Failure::new("query_clock", error.to_string()))?;
clock_json(now)
}
fn clock_json(now: Duration) -> Result<Box<RawValue>, Failure> {
RawValue::from_string(format!("{}.{:09}", now.as_secs(), now.subsec_nanos()))
.map_err(Failure::storage)
}
#[cfg(test)]
mod nonzero_tests;