Define Store, use TabWriter in the CLI for aligning columnar output. (#540) r=emily
* Define Store, which is a simple container for a SQLite connection and a Conn. This is a breaking change. * Return the FindSpec as part of QueryOutput, not just results. * Switch to using stderr in appropriate places in CLI. * Print columns in CLI output.
This commit is contained in:
parent
37a7c9ea48
commit
66e6fef75e
13 changed files with 395 additions and 247 deletions
|
@ -8,7 +8,7 @@ authors = [
|
|||
"Emily Toop <etoop@mozilla.com>",
|
||||
]
|
||||
name = "mentat"
|
||||
version = "0.5.1"
|
||||
version = "0.6.0"
|
||||
build = "build/version.rs"
|
||||
|
||||
[workspace]
|
||||
|
|
|
@ -20,6 +20,7 @@ extern crate mentat_query;
|
|||
|
||||
use std::collections::BTreeSet;
|
||||
use std::ops::Sub;
|
||||
use std::rc::Rc;
|
||||
|
||||
mod errors;
|
||||
mod types;
|
||||
|
@ -61,7 +62,7 @@ pub use types::{
|
|||
#[derive(Debug)]
|
||||
pub struct AlgebraicQuery {
|
||||
default_source: SrcVar,
|
||||
pub find_spec: FindSpec,
|
||||
pub find_spec: Rc<FindSpec>,
|
||||
has_aggregates: bool,
|
||||
pub with: BTreeSet<Variable>,
|
||||
pub order: Option<Vec<OrderBy>>,
|
||||
|
@ -192,7 +193,7 @@ pub fn algebrize_with_inputs(schema: &Schema,
|
|||
let limit = if parsed.find_spec.is_unit_limited() { Limit::Fixed(1) } else { parsed.limit };
|
||||
let q = AlgebraicQuery {
|
||||
default_source: parsed.default_source,
|
||||
find_spec: parsed.find_spec,
|
||||
find_spec: Rc::new(parsed.find_spec),
|
||||
has_aggregates: false, // TODO: we don't parse them yet.
|
||||
with: with,
|
||||
order: order,
|
||||
|
|
|
@ -20,6 +20,8 @@ extern crate mentat_query_sql;
|
|||
extern crate mentat_sql;
|
||||
|
||||
use std::iter;
|
||||
use std::rc::Rc;
|
||||
|
||||
use rusqlite::{
|
||||
Row,
|
||||
Rows,
|
||||
|
@ -78,6 +80,12 @@ error_chain! {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct QueryOutput {
|
||||
pub spec: Rc<FindSpec>,
|
||||
pub results: QueryResults,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum QueryResults {
|
||||
Scalar(Option<TypedValue>),
|
||||
|
@ -86,6 +94,63 @@ pub enum QueryResults {
|
|||
Rel(Vec<Vec<TypedValue>>),
|
||||
}
|
||||
|
||||
impl From<QueryOutput> for QueryResults {
|
||||
fn from(o: QueryOutput) -> QueryResults {
|
||||
o.results
|
||||
}
|
||||
}
|
||||
|
||||
impl QueryOutput {
|
||||
pub fn empty_factory(spec: &FindSpec) -> Box<Fn() -> QueryResults> {
|
||||
use self::FindSpec::*;
|
||||
match spec {
|
||||
&FindScalar(_) => Box::new(|| QueryResults::Scalar(None)),
|
||||
&FindTuple(_) => Box::new(|| QueryResults::Tuple(None)),
|
||||
&FindColl(_) => Box::new(|| QueryResults::Coll(vec![])),
|
||||
&FindRel(_) => Box::new(|| QueryResults::Rel(vec![])),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.results.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.results.is_empty()
|
||||
}
|
||||
|
||||
pub fn empty(spec: &Rc<FindSpec>) -> QueryOutput {
|
||||
use self::FindSpec::*;
|
||||
let results =
|
||||
match &**spec {
|
||||
&FindScalar(_) => QueryResults::Scalar(None),
|
||||
&FindTuple(_) => QueryResults::Tuple(None),
|
||||
&FindColl(_) => QueryResults::Coll(vec![]),
|
||||
&FindRel(_) => QueryResults::Rel(vec![]),
|
||||
};
|
||||
QueryOutput {
|
||||
spec: spec.clone(),
|
||||
results: results,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_scalar(self) -> Result<Option<TypedValue>> {
|
||||
self.results.into_scalar()
|
||||
}
|
||||
|
||||
pub fn into_coll(self) -> Result<Vec<TypedValue>> {
|
||||
self.results.into_coll()
|
||||
}
|
||||
|
||||
pub fn into_tuple(self) -> Result<Option<Vec<TypedValue>>> {
|
||||
self.results.into_tuple()
|
||||
}
|
||||
|
||||
pub fn into_rel(self) -> Result<Vec<Vec<TypedValue>>> {
|
||||
self.results.into_rel()
|
||||
}
|
||||
}
|
||||
|
||||
impl QueryResults {
|
||||
pub fn len(&self) -> usize {
|
||||
use QueryResults::*;
|
||||
|
@ -107,26 +172,6 @@ impl QueryResults {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn empty(spec: &FindSpec) -> QueryResults {
|
||||
use self::FindSpec::*;
|
||||
match spec {
|
||||
&FindScalar(_) => QueryResults::Scalar(None),
|
||||
&FindTuple(_) => QueryResults::Tuple(None),
|
||||
&FindColl(_) => QueryResults::Coll(vec![]),
|
||||
&FindRel(_) => QueryResults::Rel(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_factory(spec: &FindSpec) -> Box<Fn() -> QueryResults> {
|
||||
use self::FindSpec::*;
|
||||
match spec {
|
||||
&FindScalar(_) => Box::new(|| QueryResults::Scalar(None)),
|
||||
&FindTuple(_) => Box::new(|| QueryResults::Tuple(None)),
|
||||
&FindColl(_) => Box::new(|| QueryResults::Coll(vec![])),
|
||||
&FindRel(_) => Box::new(|| QueryResults::Rel(vec![])),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_scalar(self) -> Result<Option<TypedValue>> {
|
||||
match self {
|
||||
QueryResults::Scalar(o) => Ok(o),
|
||||
|
@ -304,69 +349,87 @@ fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
|
|||
}
|
||||
|
||||
pub trait Projector {
|
||||
fn project<'stmt>(&self, rows: Rows<'stmt>) -> Result<QueryResults>;
|
||||
fn project<'stmt>(&self, rows: Rows<'stmt>) -> Result<QueryOutput>;
|
||||
}
|
||||
|
||||
/// A projector that produces a `QueryResult` containing fixed data.
|
||||
/// Takes a boxed function that should return an empty result set of the desired type.
|
||||
struct ConstantProjector {
|
||||
spec: Rc<FindSpec>,
|
||||
results_factory: Box<Fn() -> QueryResults>,
|
||||
}
|
||||
|
||||
impl ConstantProjector {
|
||||
fn new(results_factory: Box<Fn() -> QueryResults>) -> ConstantProjector {
|
||||
ConstantProjector { results_factory: results_factory }
|
||||
fn new(spec: Rc<FindSpec>, results_factory: Box<Fn() -> QueryResults>) -> ConstantProjector {
|
||||
ConstantProjector {
|
||||
spec: spec,
|
||||
results_factory: results_factory,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Projector for ConstantProjector {
|
||||
fn project<'stmt>(&self, _: Rows<'stmt>) -> Result<QueryResults> {
|
||||
Ok((self.results_factory)())
|
||||
fn project<'stmt>(&self, _: Rows<'stmt>) -> Result<QueryOutput> {
|
||||
let results = (self.results_factory)();
|
||||
let spec = self.spec.clone();
|
||||
Ok(QueryOutput {
|
||||
spec: spec,
|
||||
results: results,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct ScalarProjector {
|
||||
spec: Rc<FindSpec>,
|
||||
template: TypedIndex,
|
||||
}
|
||||
|
||||
impl ScalarProjector {
|
||||
fn with_template(template: TypedIndex) -> ScalarProjector {
|
||||
fn with_template(spec: Rc<FindSpec>, template: TypedIndex) -> ScalarProjector {
|
||||
ScalarProjector {
|
||||
spec: spec,
|
||||
template: template,
|
||||
}
|
||||
}
|
||||
|
||||
fn combine(sql: Projection, mut templates: Vec<TypedIndex>) -> Result<CombinedProjection> {
|
||||
fn combine(spec: Rc<FindSpec>, sql: Projection, mut templates: Vec<TypedIndex>) -> Result<CombinedProjection> {
|
||||
let template = templates.pop().expect("Expected a single template");
|
||||
Ok(CombinedProjection {
|
||||
sql_projection: sql,
|
||||
datalog_projector: Box::new(ScalarProjector::with_template(template)),
|
||||
datalog_projector: Box::new(ScalarProjector::with_template(spec, template)),
|
||||
distinct: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Projector for ScalarProjector {
|
||||
fn project<'stmt>(&self, mut rows: Rows<'stmt>) -> Result<QueryResults> {
|
||||
fn project<'stmt>(&self, mut rows: Rows<'stmt>) -> Result<QueryOutput> {
|
||||
let results =
|
||||
if let Some(r) = rows.next() {
|
||||
let row = r?;
|
||||
let binding = self.template.lookup(&row)?;
|
||||
Ok(QueryResults::Scalar(Some(binding)))
|
||||
QueryResults::Scalar(Some(binding))
|
||||
} else {
|
||||
Ok(QueryResults::Scalar(None))
|
||||
}
|
||||
QueryResults::Scalar(None)
|
||||
};
|
||||
Ok(QueryOutput {
|
||||
spec: self.spec.clone(),
|
||||
results: results,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A tuple projector produces a single vector. It's the single-result version of rel.
|
||||
struct TupleProjector {
|
||||
spec: Rc<FindSpec>,
|
||||
len: usize,
|
||||
templates: Vec<TypedIndex>,
|
||||
}
|
||||
|
||||
impl TupleProjector {
|
||||
fn with_templates(len: usize, templates: Vec<TypedIndex>) -> TupleProjector {
|
||||
fn with_templates(spec: Rc<FindSpec>, len: usize, templates: Vec<TypedIndex>) -> TupleProjector {
|
||||
TupleProjector {
|
||||
spec: spec,
|
||||
len: len,
|
||||
templates: templates,
|
||||
}
|
||||
|
@ -382,8 +445,8 @@ impl TupleProjector {
|
|||
.collect::<Result<Vec<TypedValue>>>()
|
||||
}
|
||||
|
||||
fn combine(column_count: usize, sql: Projection, templates: Vec<TypedIndex>) -> Result<CombinedProjection> {
|
||||
let p = TupleProjector::with_templates(column_count, templates);
|
||||
fn combine(spec: Rc<FindSpec>, column_count: usize, sql: Projection, templates: Vec<TypedIndex>) -> Result<CombinedProjection> {
|
||||
let p = TupleProjector::with_templates(spec, column_count, templates);
|
||||
Ok(CombinedProjection {
|
||||
sql_projection: sql,
|
||||
datalog_projector: Box::new(p),
|
||||
|
@ -393,14 +456,19 @@ impl TupleProjector {
|
|||
}
|
||||
|
||||
impl Projector for TupleProjector {
|
||||
fn project<'stmt>(&self, mut rows: Rows<'stmt>) -> Result<QueryResults> {
|
||||
fn project<'stmt>(&self, mut rows: Rows<'stmt>) -> Result<QueryOutput> {
|
||||
let results =
|
||||
if let Some(r) = rows.next() {
|
||||
let row = r?;
|
||||
let bindings = self.collect_bindings(row)?;
|
||||
Ok(QueryResults::Tuple(Some(bindings)))
|
||||
QueryResults::Tuple(Some(bindings))
|
||||
} else {
|
||||
Ok(QueryResults::Tuple(None))
|
||||
}
|
||||
QueryResults::Tuple(None)
|
||||
};
|
||||
Ok(QueryOutput {
|
||||
spec: self.spec.clone(),
|
||||
results: results,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -410,13 +478,15 @@ impl Projector for TupleProjector {
|
|||
/// Each column in the inner vector is the result of taking one or two columns from
|
||||
/// the `Row`: one for the value and optionally one for the type tag.
|
||||
struct RelProjector {
|
||||
spec: Rc<FindSpec>,
|
||||
len: usize,
|
||||
templates: Vec<TypedIndex>,
|
||||
}
|
||||
|
||||
impl RelProjector {
|
||||
fn with_templates(len: usize, templates: Vec<TypedIndex>) -> RelProjector {
|
||||
fn with_templates(spec: Rc<FindSpec>, len: usize, templates: Vec<TypedIndex>) -> RelProjector {
|
||||
RelProjector {
|
||||
spec: spec,
|
||||
len: len,
|
||||
templates: templates,
|
||||
}
|
||||
|
@ -431,8 +501,8 @@ impl RelProjector {
|
|||
.collect::<Result<Vec<TypedValue>>>()
|
||||
}
|
||||
|
||||
fn combine(column_count: usize, sql: Projection, templates: Vec<TypedIndex>) -> Result<CombinedProjection> {
|
||||
let p = RelProjector::with_templates(column_count, templates);
|
||||
fn combine(spec: Rc<FindSpec>, column_count: usize, sql: Projection, templates: Vec<TypedIndex>) -> Result<CombinedProjection> {
|
||||
let p = RelProjector::with_templates(spec, column_count, templates);
|
||||
Ok(CombinedProjection {
|
||||
sql_projection: sql,
|
||||
datalog_projector: Box::new(p),
|
||||
|
@ -442,49 +512,57 @@ impl RelProjector {
|
|||
}
|
||||
|
||||
impl Projector for RelProjector {
|
||||
fn project<'stmt>(&self, mut rows: Rows<'stmt>) -> Result<QueryResults> {
|
||||
fn project<'stmt>(&self, mut rows: Rows<'stmt>) -> Result<QueryOutput> {
|
||||
let mut out: Vec<Vec<TypedValue>> = vec![];
|
||||
while let Some(r) = rows.next() {
|
||||
let row = r?;
|
||||
let bindings = self.collect_bindings(row)?;
|
||||
out.push(bindings);
|
||||
}
|
||||
Ok(QueryResults::Rel(out))
|
||||
Ok(QueryOutput {
|
||||
spec: self.spec.clone(),
|
||||
results: QueryResults::Rel(out),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A coll projector produces a vector of values.
|
||||
/// Each value is sourced from the same column.
|
||||
struct CollProjector {
|
||||
spec: Rc<FindSpec>,
|
||||
template: TypedIndex,
|
||||
}
|
||||
|
||||
impl CollProjector {
|
||||
fn with_template(template: TypedIndex) -> CollProjector {
|
||||
fn with_template(spec: Rc<FindSpec>, template: TypedIndex) -> CollProjector {
|
||||
CollProjector {
|
||||
spec: spec,
|
||||
template: template,
|
||||
}
|
||||
}
|
||||
|
||||
fn combine(sql: Projection, mut templates: Vec<TypedIndex>) -> Result<CombinedProjection> {
|
||||
fn combine(spec: Rc<FindSpec>, sql: Projection, mut templates: Vec<TypedIndex>) -> Result<CombinedProjection> {
|
||||
let template = templates.pop().expect("Expected a single template");
|
||||
Ok(CombinedProjection {
|
||||
sql_projection: sql,
|
||||
datalog_projector: Box::new(CollProjector::with_template(template)),
|
||||
datalog_projector: Box::new(CollProjector::with_template(spec, template)),
|
||||
distinct: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Projector for CollProjector {
|
||||
fn project<'stmt>(&self, mut rows: Rows<'stmt>) -> Result<QueryResults> {
|
||||
fn project<'stmt>(&self, mut rows: Rows<'stmt>) -> Result<QueryOutput> {
|
||||
let mut out: Vec<TypedValue> = vec![];
|
||||
while let Some(r) = rows.next() {
|
||||
let row = r?;
|
||||
let binding = self.template.lookup(&row)?;
|
||||
out.push(binding);
|
||||
}
|
||||
Ok(QueryResults::Coll(out))
|
||||
Ok(QueryOutput {
|
||||
spec: self.spec.clone(),
|
||||
results: QueryResults::Coll(out),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -523,37 +601,38 @@ impl CombinedProjection {
|
|||
pub fn query_projection(query: &AlgebraicQuery) -> Result<CombinedProjection> {
|
||||
use self::FindSpec::*;
|
||||
|
||||
let spec = query.find_spec.clone();
|
||||
if query.is_known_empty() {
|
||||
// Do a few gyrations to produce empty results of the right kind for the query.
|
||||
let empty = QueryResults::empty_factory(&query.find_spec);
|
||||
let constant_projector = ConstantProjector::new(empty);
|
||||
let empty = QueryOutput::empty_factory(&spec);
|
||||
let constant_projector = ConstantProjector::new(spec, empty);
|
||||
Ok(CombinedProjection {
|
||||
sql_projection: Projection::One,
|
||||
datalog_projector: Box::new(constant_projector),
|
||||
distinct: false,
|
||||
})
|
||||
} else {
|
||||
match query.find_spec {
|
||||
match *query.find_spec {
|
||||
FindColl(ref element) => {
|
||||
let (cols, templates) = project_elements(1, iter::once(element), query)?;
|
||||
CollProjector::combine(cols, templates).map(|p| p.flip_distinct_for_limit(&query.limit))
|
||||
CollProjector::combine(spec, cols, templates).map(|p| p.flip_distinct_for_limit(&query.limit))
|
||||
},
|
||||
|
||||
FindScalar(ref element) => {
|
||||
let (cols, templates) = project_elements(1, iter::once(element), query)?;
|
||||
ScalarProjector::combine(cols, templates)
|
||||
ScalarProjector::combine(spec, cols, templates)
|
||||
},
|
||||
|
||||
FindRel(ref elements) => {
|
||||
let column_count = query.find_spec.expected_column_count();
|
||||
let (cols, templates) = project_elements(column_count, elements, query)?;
|
||||
RelProjector::combine(column_count, cols, templates).map(|p| p.flip_distinct_for_limit(&query.limit))
|
||||
RelProjector::combine(spec, column_count, cols, templates).map(|p| p.flip_distinct_for_limit(&query.limit))
|
||||
},
|
||||
|
||||
FindTuple(ref elements) => {
|
||||
let column_count = query.find_spec.expected_column_count();
|
||||
let (cols, templates) = project_elements(column_count, elements, query)?;
|
||||
TupleProjector::combine(column_count, cols, templates)
|
||||
TupleProjector::combine(spec, column_count, cols, templates)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
76
src/conn.rs
76
src/conn.rs
|
@ -56,7 +56,7 @@ use query::{
|
|||
q_explain,
|
||||
QueryExplanation,
|
||||
QueryInputs,
|
||||
QueryResults,
|
||||
QueryOutput,
|
||||
};
|
||||
|
||||
use entity_builder::{
|
||||
|
@ -104,10 +104,28 @@ pub struct Conn {
|
|||
// the schema changes. #315.
|
||||
}
|
||||
|
||||
/// A convenience wrapper around a single SQLite connection and a Conn. This is suitable
|
||||
/// for applications that don't require complex connection management.
|
||||
pub struct Store {
|
||||
conn: Conn,
|
||||
sqlite: rusqlite::Connection,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn open(path: &str) -> Result<Store> {
|
||||
let mut connection = ::new_connection(path)?;
|
||||
let conn = Conn::connect(&mut connection)?;
|
||||
Ok(Store {
|
||||
conn: conn,
|
||||
sqlite: connection,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Queryable {
|
||||
fn q_explain<T>(&self, query: &str, inputs: T) -> Result<QueryExplanation>
|
||||
where T: Into<Option<QueryInputs>>;
|
||||
fn q_once<T>(&self, query: &str, inputs: T) -> Result<QueryResults>
|
||||
fn q_once<T>(&self, query: &str, inputs: T) -> Result<QueryOutput>
|
||||
where T: Into<Option<QueryInputs>>;
|
||||
fn lookup_values_for_attribute<E>(&self, entity: E, attribute: &edn::NamespacedKeyword) -> Result<Vec<TypedValue>>
|
||||
where E: Into<Entid>;
|
||||
|
@ -132,7 +150,7 @@ pub struct InProgress<'a, 'c> {
|
|||
pub struct InProgressRead<'a, 'c>(InProgress<'a, 'c>);
|
||||
|
||||
impl<'a, 'c> Queryable for InProgressRead<'a, 'c> {
|
||||
fn q_once<T>(&self, query: &str, inputs: T) -> Result<QueryResults>
|
||||
fn q_once<T>(&self, query: &str, inputs: T) -> Result<QueryOutput>
|
||||
where T: Into<Option<QueryInputs>> {
|
||||
self.0.q_once(query, inputs)
|
||||
}
|
||||
|
@ -154,7 +172,7 @@ impl<'a, 'c> Queryable for InProgressRead<'a, 'c> {
|
|||
}
|
||||
|
||||
impl<'a, 'c> Queryable for InProgress<'a, 'c> {
|
||||
fn q_once<T>(&self, query: &str, inputs: T) -> Result<QueryResults>
|
||||
fn q_once<T>(&self, query: &str, inputs: T) -> Result<QueryOutput>
|
||||
where T: Into<Option<QueryInputs>> {
|
||||
|
||||
q_once(&*(self.transaction),
|
||||
|
@ -323,6 +341,46 @@ impl<'a, 'c> InProgress<'a, 'c> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn dismantle(self) -> (rusqlite::Connection, Conn) {
|
||||
(self.sqlite, self.conn)
|
||||
}
|
||||
|
||||
pub fn conn(&self) -> &Conn {
|
||||
&self.conn
|
||||
}
|
||||
|
||||
pub fn begin_read<'m>(&'m mut self) -> Result<InProgressRead<'m, 'm>> {
|
||||
self.conn.begin_read(&mut self.sqlite)
|
||||
}
|
||||
|
||||
pub fn begin_transaction<'m>(&'m mut self) -> Result<InProgress<'m, 'm>> {
|
||||
self.conn.begin_transaction(&mut self.sqlite)
|
||||
}
|
||||
}
|
||||
|
||||
impl Queryable for Store {
|
||||
fn q_once<T>(&self, query: &str, inputs: T) -> Result<QueryOutput>
|
||||
where T: Into<Option<QueryInputs>> {
|
||||
self.conn.q_once(&self.sqlite, query, inputs)
|
||||
}
|
||||
|
||||
fn q_explain<T>(&self, query: &str, inputs: T) -> Result<QueryExplanation>
|
||||
where T: Into<Option<QueryInputs>> {
|
||||
self.conn.q_explain(&self.sqlite, query, inputs)
|
||||
}
|
||||
|
||||
fn lookup_values_for_attribute<E>(&self, entity: E, attribute: &edn::NamespacedKeyword) -> Result<Vec<TypedValue>>
|
||||
where E: Into<Entid> {
|
||||
self.conn.lookup_values_for_attribute(&self.sqlite, entity.into(), attribute)
|
||||
}
|
||||
|
||||
fn lookup_value_for_attribute<E>(&self, entity: E, attribute: &edn::NamespacedKeyword) -> Result<Option<TypedValue>>
|
||||
where E: Into<Entid> {
|
||||
self.conn.lookup_value_for_attribute(&self.sqlite, entity.into(), attribute)
|
||||
}
|
||||
}
|
||||
|
||||
impl Conn {
|
||||
// Intentionally not public.
|
||||
fn new(partition_map: PartitionMap, schema: Schema) -> Conn {
|
||||
|
@ -358,7 +416,7 @@ impl Conn {
|
|||
pub fn q_once<T>(&self,
|
||||
sqlite: &rusqlite::Connection,
|
||||
query: &str,
|
||||
inputs: T) -> Result<QueryResults>
|
||||
inputs: T) -> Result<QueryOutput>
|
||||
where T: Into<Option<QueryInputs>> {
|
||||
|
||||
let metadata = self.metadata.lock().unwrap();
|
||||
|
@ -458,6 +516,8 @@ mod tests {
|
|||
TypedValue,
|
||||
};
|
||||
|
||||
use ::QueryResults;
|
||||
|
||||
use mentat_db::USER0;
|
||||
|
||||
#[test]
|
||||
|
@ -545,7 +605,7 @@ mod tests {
|
|||
|
||||
let during = in_progress.q_once("[:find ?x . :where [?x :db/ident :a/keyword1]]", None)
|
||||
.expect("query succeeded");
|
||||
assert_eq!(during, QueryResults::Scalar(Some(TypedValue::Ref(one))));
|
||||
assert_eq!(during.results, QueryResults::Scalar(Some(TypedValue::Ref(one))));
|
||||
|
||||
let report = in_progress.transact(t2).expect("t2 succeeded");
|
||||
in_progress.commit().expect("commit succeeded");
|
||||
|
@ -589,7 +649,7 @@ mod tests {
|
|||
let during = in_progress.q_once("[:find ?x . :where [?x :db/ident :a/keyword1]]", None)
|
||||
.expect("query succeeded");
|
||||
|
||||
assert_eq!(during, QueryResults::Scalar(Some(TypedValue::Ref(one))));
|
||||
assert_eq!(during.results, QueryResults::Scalar(Some(TypedValue::Ref(one))));
|
||||
|
||||
// And we can do direct lookup, too.
|
||||
let kw = in_progress.lookup_value_for_attribute(one, &edn::NamespacedKeyword::new("db", "ident"))
|
||||
|
@ -602,7 +662,7 @@ mod tests {
|
|||
|
||||
let after = conn.q_once(&mut sqlite, "[:find ?x . :where [?x :db/ident :a/keyword1]]", None)
|
||||
.expect("query succeeded");
|
||||
assert_eq!(after, QueryResults::Scalar(None));
|
||||
assert_eq!(after.results, QueryResults::Scalar(None));
|
||||
|
||||
// The DB part table is unchanged.
|
||||
let tempid_offset_after = get_next_entid(&conn);
|
||||
|
|
13
src/lib.rs
13
src/lib.rs
|
@ -40,6 +40,10 @@ pub use mentat_core::{
|
|||
ValueType,
|
||||
};
|
||||
|
||||
pub use mentat_query::{
|
||||
FindSpec,
|
||||
};
|
||||
|
||||
pub use mentat_db::{
|
||||
CORE_SCHEMA_VERSION,
|
||||
DB_SCHEMA_CORE,
|
||||
|
@ -80,19 +84,13 @@ pub fn get_name() -> String {
|
|||
return String::from("mentat");
|
||||
}
|
||||
|
||||
/// Open a Mentat store at the provided path.
|
||||
pub fn open(path: &str) -> errors::Result<(rusqlite::Connection, Conn)> {
|
||||
let mut connection = new_connection(path)?;
|
||||
let conn = Conn::connect(&mut connection)?;
|
||||
Ok((connection, conn))
|
||||
}
|
||||
|
||||
pub use query::{
|
||||
IntoResult,
|
||||
PlainSymbol,
|
||||
QueryExecutionResult,
|
||||
QueryExplanation,
|
||||
QueryInputs,
|
||||
QueryOutput,
|
||||
QueryPlanStep,
|
||||
QueryResults,
|
||||
Variable,
|
||||
|
@ -104,6 +102,7 @@ pub use conn::{
|
|||
InProgress,
|
||||
Metadata,
|
||||
Queryable,
|
||||
Store,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -60,7 +60,8 @@ use mentat_query_translator::{
|
|||
};
|
||||
|
||||
pub use mentat_query_projector::{
|
||||
QueryResults,
|
||||
QueryOutput, // Includes the columns/find spec.
|
||||
QueryResults, // The results themselves.
|
||||
};
|
||||
|
||||
use errors::{
|
||||
|
@ -68,7 +69,7 @@ use errors::{
|
|||
Result,
|
||||
};
|
||||
|
||||
pub type QueryExecutionResult = Result<QueryResults>;
|
||||
pub type QueryExecutionResult = Result<QueryOutput>;
|
||||
|
||||
pub trait IntoResult {
|
||||
fn into_scalar_result(self) -> Result<Option<TypedValue>>;
|
||||
|
@ -256,7 +257,7 @@ fn run_algebrized_query<'sqlite>(sqlite: &'sqlite rusqlite::Connection, algebriz
|
|||
"Unbound variables should be checked by now");
|
||||
if algebrized.is_known_empty() {
|
||||
// We don't need to do any SQL work at all.
|
||||
return Ok(QueryResults::empty(&algebrized.find_spec));
|
||||
return Ok(QueryOutput::empty(&algebrized.find_spec));
|
||||
}
|
||||
|
||||
let select = query_to_select(algebrized)?;
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
//! extern crate mentat;
|
||||
//!
|
||||
//! use mentat::{
|
||||
//! Conn,
|
||||
//! Store,
|
||||
//! ValueType,
|
||||
//! };
|
||||
//!
|
||||
|
@ -40,11 +40,11 @@
|
|||
//! };
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let (mut sqlite, mut conn) = mentat::open("").expect("connected");
|
||||
//! let mut store = Store::open("").expect("connected");
|
||||
//!
|
||||
//! {
|
||||
//! // Read the list of installed vocabularies.
|
||||
//! let reader = conn.begin_read(&mut sqlite).expect("began read");
|
||||
//! let reader = store.begin_read().expect("began read");
|
||||
//! let vocabularies = reader.read_vocabularies().expect("read");
|
||||
//! for (name, vocabulary) in vocabularies.iter() {
|
||||
//! println!("Vocab {} is at version {}.", name, vocabulary.version);
|
||||
|
@ -55,7 +55,7 @@
|
|||
//! }
|
||||
//!
|
||||
//! {
|
||||
//! let mut in_progress = conn.begin_transaction(&mut sqlite).expect("began transaction");
|
||||
//! let mut in_progress = store.begin_transaction().expect("began transaction");
|
||||
//!
|
||||
//! // Make sure the core vocabulary exists.
|
||||
//! in_progress.verify_core_schema().expect("verified");
|
||||
|
@ -569,12 +569,18 @@ impl<T> HasVocabularies for T where T: HasSchema + Queryable {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::HasVocabularies;
|
||||
use ::{
|
||||
Store,
|
||||
};
|
||||
|
||||
use super::{
|
||||
HasVocabularies,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_read_vocabularies() {
|
||||
let (mut sqlite, mut conn) = ::open("").expect("opened");
|
||||
let vocabularies = conn.begin_read(&mut sqlite).expect("in progress")
|
||||
let mut store = Store::open("").expect("opened");
|
||||
let vocabularies = store.begin_read().expect("in progress")
|
||||
.read_vocabularies().expect("OK");
|
||||
assert_eq!(vocabularies.len(), 1);
|
||||
let core = vocabularies.get(&kw!(:db.schema/core)).expect("exists");
|
||||
|
@ -583,8 +589,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_core_schema() {
|
||||
let (mut sqlite, mut conn) = ::open("").expect("opened");
|
||||
let in_progress = conn.begin_transaction(&mut sqlite).expect("in progress");
|
||||
let mut store = Store::open("").expect("opened");
|
||||
let in_progress = store.begin_transaction().expect("in progress");
|
||||
let vocab = in_progress.read_vocabularies().expect("vocabulary");
|
||||
assert_eq!(1, vocab.len());
|
||||
assert_eq!(1, vocab.get(&kw!(:db.schema/core)).expect("core vocab").version);
|
||||
|
|
|
@ -57,7 +57,8 @@ fn test_rel() {
|
|||
let start = time::PreciseTime::now();
|
||||
let results = q_once(&c, &db.schema,
|
||||
"[:find ?x ?ident :where [?x :db/ident ?ident]]", None)
|
||||
.expect("Query failed");
|
||||
.expect("Query failed")
|
||||
.results;
|
||||
let end = time::PreciseTime::now();
|
||||
|
||||
// This will need to change each time we add a default ident.
|
||||
|
@ -87,7 +88,8 @@ fn test_failing_scalar() {
|
|||
let start = time::PreciseTime::now();
|
||||
let results = q_once(&c, &db.schema,
|
||||
"[:find ?x . :where [?x :db/fulltext true]]", None)
|
||||
.expect("Query failed");
|
||||
.expect("Query failed")
|
||||
.results;
|
||||
let end = time::PreciseTime::now();
|
||||
|
||||
assert_eq!(0, results.len());
|
||||
|
@ -109,7 +111,8 @@ fn test_scalar() {
|
|||
let start = time::PreciseTime::now();
|
||||
let results = q_once(&c, &db.schema,
|
||||
"[:find ?ident . :where [24 :db/ident ?ident]]", None)
|
||||
.expect("Query failed");
|
||||
.expect("Query failed")
|
||||
.results;
|
||||
let end = time::PreciseTime::now();
|
||||
|
||||
assert_eq!(1, results.len());
|
||||
|
@ -139,7 +142,8 @@ fn test_tuple() {
|
|||
:where [:db/txInstant :db/index ?index]
|
||||
[:db/txInstant :db/cardinality ?cardinality]]",
|
||||
None)
|
||||
.expect("Query failed");
|
||||
.expect("Query failed")
|
||||
.results;
|
||||
let end = time::PreciseTime::now();
|
||||
|
||||
assert_eq!(1, results.len());
|
||||
|
@ -166,7 +170,8 @@ fn test_coll() {
|
|||
let start = time::PreciseTime::now();
|
||||
let results = q_once(&c, &db.schema,
|
||||
"[:find [?e ...] :where [?e :db/ident _]]", None)
|
||||
.expect("Query failed");
|
||||
.expect("Query failed")
|
||||
.results;
|
||||
let end = time::PreciseTime::now();
|
||||
|
||||
assert_eq!(40, results.len());
|
||||
|
@ -191,7 +196,8 @@ fn test_inputs() {
|
|||
let inputs = QueryInputs::with_value_sequence(vec![ee]);
|
||||
let results = q_once(&c, &db.schema,
|
||||
"[:find ?i . :in ?e :where [?e :db/ident ?i]]", inputs)
|
||||
.expect("query to succeed");
|
||||
.expect("query to succeed")
|
||||
.results;
|
||||
|
||||
if let QueryResults::Scalar(Some(TypedValue::Keyword(value))) = results {
|
||||
assert_eq!(value.as_ref(), &NamespacedKeyword::new("db.install", "valueType"));
|
||||
|
@ -239,9 +245,11 @@ fn test_instants_and_uuids() {
|
|||
let r = conn.q_once(&mut c,
|
||||
r#"[:find [?x ?u ?when]
|
||||
:where [?x :foo/uuid ?u ?tx]
|
||||
[?tx :db/txInstant ?when]]"#, None);
|
||||
[?tx :db/txInstant ?when]]"#, None)
|
||||
.expect("results")
|
||||
.into();
|
||||
match r {
|
||||
Result::Ok(QueryResults::Tuple(Some(vals))) => {
|
||||
QueryResults::Tuple(Some(vals)) => {
|
||||
let mut vals = vals.into_iter();
|
||||
match (vals.next(), vals.next(), vals.next(), vals.next()) {
|
||||
(Some(TypedValue::Ref(e)),
|
||||
|
@ -279,9 +287,11 @@ fn test_tx() {
|
|||
|
||||
let r = conn.q_once(&mut c,
|
||||
r#"[:find ?tx
|
||||
:where [?x :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4" ?tx]]"#, None);
|
||||
:where [?x :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4" ?tx]]"#, None)
|
||||
.expect("results")
|
||||
.into();
|
||||
match r {
|
||||
Result::Ok(QueryResults::Rel(ref v)) => {
|
||||
QueryResults::Rel(ref v) => {
|
||||
assert_eq!(*v, vec![
|
||||
vec![TypedValue::Ref(t.tx_id),]
|
||||
]);
|
||||
|
@ -314,9 +324,11 @@ fn test_tx_as_input() {
|
|||
let r = conn.q_once(&mut c,
|
||||
r#"[:find ?uuid
|
||||
:in ?tx
|
||||
:where [?x :foo/uuid ?uuid ?tx]]"#, inputs);
|
||||
:where [?x :foo/uuid ?uuid ?tx]]"#, inputs)
|
||||
.expect("results")
|
||||
.into();
|
||||
match r {
|
||||
Result::Ok(QueryResults::Rel(ref v)) => {
|
||||
QueryResults::Rel(ref v) => {
|
||||
assert_eq!(*v, vec![
|
||||
vec![TypedValue::Uuid(Uuid::from_str("cf62d552-6569-4d1b-b667-04703041dfc4").expect("Valid UUID")),]
|
||||
]);
|
||||
|
@ -350,9 +362,11 @@ fn test_fulltext() {
|
|||
|
||||
let r = conn.q_once(&mut c,
|
||||
r#"[:find [?x ?val ?score]
|
||||
:where [(fulltext $ :foo/fts "darkness") [[?x ?val _ ?score]]]]"#, None);
|
||||
:where [(fulltext $ :foo/fts "darkness") [[?x ?val _ ?score]]]]"#, None)
|
||||
.expect("results")
|
||||
.into();
|
||||
match r {
|
||||
Result::Ok(QueryResults::Tuple(Some(vals))) => {
|
||||
QueryResults::Tuple(Some(vals)) => {
|
||||
let mut vals = vals.into_iter();
|
||||
match (vals.next(), vals.next(), vals.next(), vals.next()) {
|
||||
(Some(TypedValue::Ref(x)),
|
||||
|
@ -413,9 +427,11 @@ fn test_fulltext() {
|
|||
[?a :foo/term ?term]
|
||||
[(fulltext $ :foo/fts ?term) [[?x ?val]]]]"#;
|
||||
let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?a"), TypedValue::Ref(a))]);
|
||||
let r = conn.q_once(&mut c, query, inputs);
|
||||
let r = conn.q_once(&mut c, query, inputs)
|
||||
.expect("results")
|
||||
.into();
|
||||
match r {
|
||||
Result::Ok(QueryResults::Rel(rels)) => {
|
||||
QueryResults::Rel(rels) => {
|
||||
assert_eq!(rels, vec![
|
||||
vec![TypedValue::Ref(v),
|
||||
TypedValue::String("I've come to talk with you again".to_string().into()),
|
||||
|
@ -449,9 +465,11 @@ fn test_instant_range_query() {
|
|||
:order (asc ?date)
|
||||
:where
|
||||
[?x :foo/date ?date]
|
||||
[(< ?date #inst "2017-01-01T11:00:02.000Z")]]"#, None);
|
||||
[(< ?date #inst "2017-01-01T11:00:02.000Z")]]"#, None)
|
||||
.expect("results")
|
||||
.into();
|
||||
match r {
|
||||
Result::Ok(QueryResults::Coll(vals)) => {
|
||||
QueryResults::Coll(vals) => {
|
||||
assert_eq!(vals,
|
||||
vec![TypedValue::Ref(*ids.get("b").unwrap()),
|
||||
TypedValue::Ref(*ids.get("c").unwrap())]);
|
||||
|
@ -533,7 +551,9 @@ fn test_type_reqs() {
|
|||
|
||||
let eid_query = r#"[:find ?eid :where [?eid :test/string "foo"]]"#;
|
||||
|
||||
let res = conn.q_once(&mut c, eid_query, None).unwrap();
|
||||
let res = conn.q_once(&mut c, eid_query, None)
|
||||
.expect("results")
|
||||
.into();
|
||||
|
||||
let entid = match res {
|
||||
QueryResults::Rel(ref vs) if vs.len() == 1 && vs[0].len() == 1 && vs[0][0].matches_type(ValueType::Ref) =>
|
||||
|
@ -563,7 +583,9 @@ fn test_type_reqs() {
|
|||
let q = format!("[:find [?v ...] :in ?e :where [?e _ ?v] [({} ?v)]]", name);
|
||||
let results = conn.q_once(&mut c, &q, QueryInputs::with_value_sequence(vec![
|
||||
(Variable::from_valid_name("?e"), TypedValue::Ref(entid)),
|
||||
])).unwrap();
|
||||
]))
|
||||
.expect("results")
|
||||
.into();
|
||||
match results {
|
||||
QueryResults::Coll(vals) => {
|
||||
assert_eq!(vals.len(), 1, "Query should find exactly 1 item");
|
||||
|
@ -586,7 +608,9 @@ fn test_type_reqs() {
|
|||
|
||||
let res = conn.q_once(&mut c, longs_query, QueryInputs::with_value_sequence(vec![
|
||||
(Variable::from_valid_name("?e"), TypedValue::Ref(entid)),
|
||||
])).unwrap();
|
||||
]))
|
||||
.expect("results")
|
||||
.into();
|
||||
match res {
|
||||
QueryResults::Coll(vals) => {
|
||||
assert_eq!(vals, vec![TypedValue::Long(5), TypedValue::Long(33)])
|
||||
|
|
|
@ -12,13 +12,14 @@ doc = false
|
|||
test = false
|
||||
|
||||
[dependencies]
|
||||
getopts = "0.2"
|
||||
combine = "2.2.2"
|
||||
env_logger = "0.3"
|
||||
getopts = "0.2"
|
||||
lazy_static = "0.2"
|
||||
linefeed = "0.4"
|
||||
log = "0.3"
|
||||
tabwriter = "1"
|
||||
tempfile = "1.1"
|
||||
combine = "2.2.2"
|
||||
lazy_static = "0.2"
|
||||
error-chain = { git = "https://github.com/rnewman/error-chain", branch = "rnewman/sync" }
|
||||
|
||||
[dependencies.rusqlite]
|
||||
|
|
|
@ -21,6 +21,7 @@ error_chain! {
|
|||
|
||||
foreign_links {
|
||||
Rusqlite(rusqlite::Error);
|
||||
IoError(::std::io::Error);
|
||||
}
|
||||
|
||||
links {
|
||||
|
|
|
@ -19,6 +19,7 @@ extern crate env_logger;
|
|||
extern crate getopts;
|
||||
extern crate linefeed;
|
||||
extern crate rusqlite;
|
||||
extern crate tabwriter;
|
||||
|
||||
extern crate mentat;
|
||||
extern crate edn;
|
||||
|
@ -29,7 +30,6 @@ extern crate mentat_db;
|
|||
use getopts::Options;
|
||||
|
||||
pub mod command_parser;
|
||||
pub mod store;
|
||||
pub mod input;
|
||||
pub mod repl;
|
||||
pub mod errors;
|
||||
|
|
|
@ -9,13 +9,20 @@
|
|||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
use std::process;
|
||||
|
||||
use mentat::query::{
|
||||
use tabwriter::TabWriter;
|
||||
|
||||
use mentat::{
|
||||
Queryable,
|
||||
QueryExplanation,
|
||||
QueryOutput,
|
||||
QueryResults,
|
||||
Store,
|
||||
TxReport,
|
||||
TypedValue,
|
||||
};
|
||||
use mentat_core::TypedValue;
|
||||
|
||||
use command_parser::{
|
||||
Command,
|
||||
|
@ -31,16 +38,13 @@ use command_parser::{
|
|||
LONG_QUERY_EXPLAIN_COMMAND,
|
||||
SHORT_QUERY_EXPLAIN_COMMAND,
|
||||
};
|
||||
|
||||
use input::InputReader;
|
||||
use input::InputResult::{
|
||||
MetaCommand,
|
||||
Empty,
|
||||
More,
|
||||
Eof
|
||||
};
|
||||
use store::{
|
||||
Store,
|
||||
db_output_name
|
||||
Eof,
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
|
@ -64,14 +68,24 @@ lazy_static! {
|
|||
|
||||
/// Executes input and maintains state of persistent items.
|
||||
pub struct Repl {
|
||||
store: Store
|
||||
path: String,
|
||||
store: Store,
|
||||
}
|
||||
|
||||
impl Repl {
|
||||
pub fn db_name(&self) -> String {
|
||||
if self.path.is_empty() {
|
||||
"in-memory db".to_string()
|
||||
} else {
|
||||
self.path.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new `Repl`.
|
||||
pub fn new() -> Result<Repl, String> {
|
||||
let store = Store::new(None).map_err(|e| e.to_string())?;
|
||||
let store = Store::open("").map_err(|e| e.to_string())?;
|
||||
Ok(Repl{
|
||||
path: "".to_string(),
|
||||
store: store,
|
||||
})
|
||||
}
|
||||
|
@ -103,7 +117,7 @@ impl Repl {
|
|||
}
|
||||
break;
|
||||
},
|
||||
Err(e) => println!("{}", e.to_string()),
|
||||
Err(e) => eprintln!("{}", e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,36 +127,49 @@ impl Repl {
|
|||
match cmd {
|
||||
Command::Help(args) => self.help_command(args),
|
||||
Command::Open(db) => {
|
||||
match self.store.open(Some(db.clone())) {
|
||||
Ok(_) => println!("Database {:?} opened", db_output_name(&db)),
|
||||
Err(e) => println!("{}", e.to_string())
|
||||
match self.open(db) {
|
||||
Ok(_) => println!("Database {:?} opened", self.db_name()),
|
||||
Err(e) => eprintln!("{}", e.to_string()),
|
||||
};
|
||||
},
|
||||
Command::Close => self.close(),
|
||||
Command::Query(query) => self.execute_query(query),
|
||||
Command::QueryExplain(query) => self.explain_query(query),
|
||||
Command::Schema => {
|
||||
let edn = self.store.fetch_schema();
|
||||
let edn = self.store.conn().current_schema().to_edn_value();
|
||||
match edn.to_pretty(120) {
|
||||
Ok(s) => println!("{}", s),
|
||||
Err(e) => println!("{}", e)
|
||||
Err(e) => eprintln!("{}", e)
|
||||
};
|
||||
|
||||
}
|
||||
Command::Transact(transaction) => self.execute_transact(transaction),
|
||||
Command::Exit => {
|
||||
self.close();
|
||||
println!("Exiting...");
|
||||
eprintln!("Exiting...");
|
||||
process::exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open<T>(&mut self, path: T) -> ::mentat::errors::Result<()>
|
||||
where T: Into<String> {
|
||||
let path = path.into();
|
||||
if self.path.is_empty() || path != self.path {
|
||||
let next = Store::open(path.as_str())?;
|
||||
self.path = path;
|
||||
self.store = next;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Close the current store by opening a new in-memory store in its place.
|
||||
fn close(&mut self) {
|
||||
let old_db_name = self.store.db_name.clone();
|
||||
match self.store.close() {
|
||||
Ok(_) => println!("Database {:?} closed", db_output_name(&old_db_name)),
|
||||
Err(e) => println!("{}", e)
|
||||
let old_db_name = self.db_name();
|
||||
match self.open("") {
|
||||
Ok(_) => println!("Database {:?} closed.", old_db_name),
|
||||
Err(e) => eprintln!("{}", e),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -160,54 +187,76 @@ impl Repl {
|
|||
if msg.is_some() {
|
||||
println!(".{} - {}", arg, msg.unwrap());
|
||||
} else {
|
||||
println!("Unrecognised command {}", arg);
|
||||
eprintln!("Unrecognised command {}", arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_query(&self, query: String) {
|
||||
let results = match self.store.query(query){
|
||||
Result::Ok(vals) => {
|
||||
vals
|
||||
},
|
||||
Result::Err(err) => return println!("{:?}.", err),
|
||||
};
|
||||
|
||||
if results.is_empty() {
|
||||
println!("No results found.")
|
||||
self.store.q_once(query.as_str(), None)
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|o| self.print_results(o))
|
||||
.map_err(|err| {
|
||||
eprintln!("{:?}.", err);
|
||||
}).ok();
|
||||
}
|
||||
|
||||
let mut output:String = String::new();
|
||||
match results {
|
||||
QueryResults::Scalar(Some(val)) => {
|
||||
output.push_str(&self.typed_value_as_string(val) );
|
||||
fn print_results(&self, query_output: QueryOutput) -> Result<(), ::errors::Error> {
|
||||
let stdout = ::std::io::stdout();
|
||||
let mut output = TabWriter::new(stdout.lock());
|
||||
|
||||
// Print the column headers.
|
||||
for e in query_output.spec.columns() {
|
||||
write!(output, "| {}\t", e)?;
|
||||
}
|
||||
writeln!(output, "|")?;
|
||||
for _ in 0..query_output.spec.expected_column_count() {
|
||||
write!(output, "---\t")?;
|
||||
}
|
||||
writeln!(output, "")?;
|
||||
|
||||
match query_output.results {
|
||||
QueryResults::Scalar(v) => {
|
||||
if let Some(val) = v {
|
||||
writeln!(output, "| {}\t |", &self.typed_value_as_string(val))?;
|
||||
}
|
||||
},
|
||||
QueryResults::Tuple(Some(vals)) => {
|
||||
|
||||
QueryResults::Tuple(vv) => {
|
||||
if let Some(vals) = vv {
|
||||
for val in vals {
|
||||
output.push_str(&format!("{}\t", self.typed_value_as_string(val)));
|
||||
write!(output, "| {}\t", self.typed_value_as_string(val))?;
|
||||
}
|
||||
writeln!(output, "|")?;
|
||||
}
|
||||
},
|
||||
|
||||
QueryResults::Coll(vv) => {
|
||||
for val in vv {
|
||||
output.push_str(&format!("{}\n", self.typed_value_as_string(val)));
|
||||
writeln!(output, "| {}\t|", self.typed_value_as_string(val))?;
|
||||
}
|
||||
},
|
||||
|
||||
QueryResults::Rel(vvv) => {
|
||||
for vv in vvv {
|
||||
for v in vv {
|
||||
output.push_str(&format!("{}\t", self.typed_value_as_string(v)));
|
||||
write!(output, "| {}\t", self.typed_value_as_string(v))?;
|
||||
}
|
||||
output.push_str("\n");
|
||||
writeln!(output, "|")?;
|
||||
}
|
||||
},
|
||||
_ => output.push_str(&format!("No results found."))
|
||||
}
|
||||
println!("\n{}", output);
|
||||
for _ in 0..query_output.spec.expected_column_count() {
|
||||
write!(output, "---\t")?;
|
||||
}
|
||||
writeln!(output, "")?;
|
||||
output.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn explain_query(&self, query: String) {
|
||||
match self.store.explain_query(query) {
|
||||
match self.store.q_explain(query.as_str(), None) {
|
||||
Result::Err(err) =>
|
||||
println!("{:?}.", err),
|
||||
Result::Ok(QueryExplanation::KnownEmpty(empty_because)) =>
|
||||
|
@ -244,12 +293,19 @@ impl Repl {
|
|||
}
|
||||
|
||||
pub fn execute_transact(&mut self, transaction: String) {
|
||||
match self.store.transact(transaction) {
|
||||
match self.transact(transaction) {
|
||||
Result::Ok(report) => println!("{:?}", report),
|
||||
Result::Err(err) => println!("{:?}.", err),
|
||||
Result::Err(err) => eprintln!("Error: {:?}.", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn transact(&mut self, transaction: String) -> ::mentat::errors::Result<TxReport> {
|
||||
let mut tx = self.store.begin_transaction()?;
|
||||
let report = tx.transact(&transaction)?;
|
||||
tx.commit()?;
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
fn typed_value_as_string(&self, value: TypedValue) -> String {
|
||||
match value {
|
||||
TypedValue::Boolean(b) => if b { "true".to_string() } else { "false".to_string() },
|
||||
|
@ -263,10 +319,3 @@ impl Repl {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright 2017 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.
|
||||
|
||||
use rusqlite;
|
||||
|
||||
use edn;
|
||||
|
||||
use errors as cli;
|
||||
|
||||
use mentat::{
|
||||
new_connection,
|
||||
QueryExplanation,
|
||||
};
|
||||
|
||||
use mentat::query::QueryResults;
|
||||
|
||||
use mentat::conn::Conn;
|
||||
use mentat_db::types::TxReport;
|
||||
|
||||
pub struct Store {
|
||||
handle: rusqlite::Connection,
|
||||
conn: Conn,
|
||||
pub db_name: String,
|
||||
}
|
||||
|
||||
pub fn db_output_name(db_name: &String) -> String {
|
||||
if db_name.is_empty() { "in-memory db".to_string() } else { db_name.clone() }
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn new(database: Option<String>) -> Result<Store, cli::Error> {
|
||||
let db_name = database.unwrap_or("".to_string());
|
||||
|
||||
let mut handle = try!(new_connection(&db_name));
|
||||
let conn = try!(Conn::connect(&mut handle));
|
||||
Ok(Store { handle, conn, db_name })
|
||||
}
|
||||
|
||||
pub fn open(&mut self, database: Option<String>) -> Result<(), cli::Error> {
|
||||
self.db_name = database.unwrap_or("".to_string());
|
||||
self.handle = try!(new_connection(&self.db_name));
|
||||
self.conn = try!(Conn::connect(&mut self.handle));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn close(&mut self) -> Result<(), cli::Error> {
|
||||
self.db_name = "".to_string();
|
||||
self.open(None)
|
||||
}
|
||||
|
||||
pub fn query(&self, query: String) -> Result<QueryResults, cli::Error> {
|
||||
Ok(self.conn.q_once(&self.handle, &query, None)?)
|
||||
}
|
||||
|
||||
pub fn explain_query(&self, query: String) -> Result<QueryExplanation, cli::Error> {
|
||||
Ok(self.conn.q_explain(&self.handle, &query, None)?)
|
||||
}
|
||||
|
||||
pub fn transact(&mut self, transaction: String) -> Result<TxReport, cli::Error> {
|
||||
Ok(self.conn.transact(&mut self.handle, &transaction)?)
|
||||
}
|
||||
|
||||
pub fn fetch_schema(&self) -> edn::Value {
|
||||
self.conn.current_schema().to_edn_value()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue