mentat/query/src/lib.rs

290 lines
8.5 KiB
Rust
Raw Normal View History

// Copyright 2016 Mozilla
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
///! This module defines some core types that support find expressions: sources,
///! variables, expressions, etc.
///! These are produced as 'fuel' by the query parser, consumed by the query
///! translator and executor.
///!
///! Many of these types are defined as simple structs that are little more than
///! a richer type alias: a variable, for example, is really just a fancy kind
///! of string.
///!
///! At some point in the future, we might consider reducing copying and memory
///! usage by recasting all of these string-holding structs and enums in terms
///! of string references, with those references being slices of some parsed
///! input query string, and valid for the lifetime of that string.
///!
///! For now, for the sake of simplicity, all of these strings are heap-allocated.
///!
///! Furthermore, we might cut out some of the chaff here: each time a 'tagged'
///! type is used within an enum, we have an opportunity to simplify and use the
///! inner type directly in conjunction with matching on the enum. Before diving
///! deeply into this it's worth recognizing that this loss of 'sovereignty' is
///! a tradeoff against well-typed function signatures and other such boundaries.
extern crate edn;
extern crate num;
extern crate ordered_float;
use num::BigInt;
use ordered_float::OrderedFloat;
use edn::{NamespacedKeyword, PlainSymbol};
pub type SrcVarName = String; // Do not include the required syntactic '$'.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Variable(pub PlainSymbol);
pub trait FromValue<T> {
fn from_value(v: &edn::Value) -> Option<T>;
}
/// If the provided EDN value is a PlainSymbol beginning with '?', return
/// it wrapped in a Variable. If not, return None.
impl FromValue<Variable> for Variable {
fn from_value(v: &edn::Value) -> Option<Variable> {
if let edn::Value::PlainSymbol(ref s) = *v {
Variable::from_symbol(s)
} else {
None
}
}
}
impl Variable {
fn from_symbol(sym: &PlainSymbol) -> Option<Variable> {
if sym.is_var_symbol() {
Some(Variable(sym.clone()))
} else {
None
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SrcVar {
DefaultSrc,
NamedSrc(SrcVarName),
}
impl FromValue<SrcVar> for SrcVar {
fn from_value(v: &edn::Value) -> Option<SrcVar> {
if let edn::Value::PlainSymbol(ref s) = *v {
SrcVar::from_symbol(s)
} else {
None
}
}
}
impl SrcVar {
pub fn from_symbol(sym: &PlainSymbol) -> Option<SrcVar> {
if sym.is_src_symbol() {
Some(SrcVar::NamedSrc(sym.plain_name().to_string()))
} else {
None
}
}
}
/// These are the scalar values representable in EDN.
#[derive(Clone,Debug,Eq,PartialEq)]
pub enum NonIntegerConstant {
Boolean(bool),
BigInteger(BigInt),
Float(OrderedFloat<f64>),
Text(String),
}
pub enum FnArg {
Variable(Variable),
SrcVar(SrcVar),
EntidOrInteger(i64),
Ident(NamespacedKeyword),
Constant(NonIntegerConstant),
}
/// e, a, tx can't be values -- no strings, no floats -- and so
/// they can only be variables, entity IDs, ident keywords, or
/// placeholders.
/// This encoding allows us to represent integers that aren't
/// entity IDs. That'll get filtered out in the context of the
/// database.
pub enum PatternNonValuePlace {
Placeholder,
Variable(Variable),
Entid(u64), // Note unsigned. See #190.
Ident(NamespacedKeyword),
}
/// The `v` part of a pattern can be much broader: it can represent
/// integers that aren't entity IDs (particularly negative integers),
/// strings, and all the rest. We group those under `Constant`.
pub enum PatternValuePlace {
Placeholder,
Variable(Variable),
EntidOrInteger(i64),
Ident(NamespacedKeyword),
Constant(NonIntegerConstant),
}
/*
pub enum PullPattern {
Constant(Constant),
Variable(Variable),
}
pub struct Pull {
pub src: SrcVar,
pub var: Variable,
pub pattern: PullPattern, // Constant, variable, or plain variable.
}
*/
/*
pub struct Aggregate {
pub fn_name: String,
pub args: Vec<FnArg>,
}
*/
#[derive(Clone,Debug,Eq,PartialEq)]
pub enum Element {
Variable(Variable),
// Aggregate(Aggregate), // TODO
// Pull(Pull), // TODO
}
/// A definition of the first part of a find query: the
/// `[:find ?foo ?bar…]` bit.
///
/// There are four different kinds of find specs, allowing you to query for
/// a single value, a collection of values from different entities, a single
/// tuple (relation), or a collection of tuples.
///
/// Examples:
///
/// ```rust
/// # extern crate edn;
/// # extern crate mentat_query;
/// # use edn::PlainSymbol;
/// # use mentat_query::{Element, FindSpec, Variable};
///
/// # fn main() {
///
/// let elements = vec![
/// Element::Variable(Variable(PlainSymbol("?foo".to_string()))),
/// Element::Variable(Variable(PlainSymbol("?bar".to_string()))),
/// ];
/// let rel = FindSpec::FindRel(elements);
///
/// if let FindSpec::FindRel(elements) = rel {
/// assert_eq!(2, elements.len());
/// }
///
/// # }
/// ```
///
#[derive(Clone,Debug,Eq,PartialEq)]
pub enum FindSpec {
/// Returns an array of arrays.
FindRel(Vec<Element>),
/// Returns an array of scalars, usually homogeneous.
/// This is equivalent to mapping over the results of a `FindRel`,
/// returning the first value of each.
FindColl(Element),
/// Returns a single tuple: a heterogeneous array of scalars. Equivalent to
/// taking the first result from a `FindRel`.
FindTuple(Vec<Element>),
/// Returns a single scalar value. Equivalent to taking the first result
/// from a `FindColl`.
FindScalar(Element),
}
#[derive(Clone,Debug,Eq,PartialEq)]
#[allow(dead_code)]
pub struct FindQuery {
pub find_spec: FindSpec,
pub default_source: SrcVar,
}
/// Returns true if the provided `FindSpec` returns at most one result.
pub fn is_unit_limited(spec: &FindSpec) -> bool {
match spec {
&FindSpec::FindScalar(..) => true,
&FindSpec::FindTuple(..) => true,
&FindSpec::FindRel(..) => false,
&FindSpec::FindColl(..) => false,
}
}
/// Returns true if the provided `FindSpec` cares about distinct results.
///
/// I use the words "cares about" because find is generally defined in terms of producing distinct
/// results at the Datalog level.
///
/// Two of the find specs (scalar and tuple) produce only a single result. Those don't need to be
/// run with `SELECT DISTINCT`, because we're only consuming a single result. Those queries will be
/// run with `LIMIT 1`.
///
/// Additionally, some projections cannot produce duplicate results: `[:find (max ?x) …]`, for
/// example.
///
/// This function gives us the hook to add that logic when we're ready.
///
/// Beyond this, `DISTINCT` is not always needed. For example, in some kinds of accumulation or
/// sampling projections we might not need to do it at the SQL level because we're consuming into
/// a dupe-eliminating data structure like a Set, or we know that a particular query cannot produce
/// duplicate results.
pub fn requires_distinct(spec: &FindSpec) -> bool {
return !is_unit_limited(spec);
}
// Note that the "implicit blank" rule applies.
// A pattern with a reversed attribute — :foo/_bar — is reversed
// at the point of parsing. These `Pattern` instances only represent
// one direction.
#[allow(dead_code)]
pub struct Pattern {
source: Option<SrcVar>,
entity: PatternNonValuePlace,
attribute: PatternNonValuePlace,
value: PatternValuePlace,
tx: PatternNonValuePlace,
}
#[allow(dead_code)]
pub enum WhereClause {
/*
Not,
NotJoin,
Or,
OrJoin,
Pred,
WhereFn,
RuleExpr,
*/
Pattern,
}
#[allow(dead_code)]
pub struct Query {
find: FindSpec,
with: Vec<Variable>,
in_vars: Vec<Variable>,
in_sources: Vec<SrcVar>,
where_clauses: Vec<WhereClause>,
// TODO: in_rules;
}