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