Implement basic query limits. (#361) r=nalexander

This commit is contained in:
Richard Newman 2017-03-06 16:27:13 -08:00
parent 85f3b79f75
commit e898df8842
7 changed files with 73 additions and 18 deletions

View file

@ -29,10 +29,26 @@ pub struct AlgebraicQuery {
default_source: SrcVar,
pub find_spec: FindSpec,
has_aggregates: bool,
pub limit: Option<i64>,
pub limit: Option<u64>,
pub cc: cc::ConjoiningClauses,
}
impl AlgebraicQuery {
pub fn apply_limit(&mut self, limit: Option<u64>) {
match self.limit {
None => self.limit = limit,
Some(existing) =>
match limit {
None => (),
Some(new) =>
if new < existing {
self.limit = limit;
},
},
};
}
}
#[allow(dead_code)]
pub fn algebrize(schema: &Schema, parsed: FindQuery) -> AlgebraicQuery {
// TODO: integrate default source into pattern processing.

View file

@ -108,6 +108,7 @@ pub struct SelectQuery {
pub projection: Projection,
pub from: FromClause,
pub constraints: Vec<Constraint>,
pub limit: Option<u64>,
}
// We know that DatomsColumns are safe to serialize.
@ -264,6 +265,12 @@ impl QueryFragment for SelectQuery {
constraint.push_sql(out)?;
}
// Guaranteed to be positive: u64.
if let Some(limit) = self.limit {
out.push_sql(" LIMIT ");
out.push_sql(limit.to_string().as_str());
}
Ok(())
}
}
@ -316,6 +323,7 @@ mod tests {
right: ColumnOrExpression::Entid(65536),
},
],
limit: None,
};
let SQLQuery { sql, args } = query.to_sql_query().unwrap();

View file

@ -77,7 +77,7 @@ pub struct CombinedSelectQuery {
pub projector: Box<Projector>,
}
fn cc_to_select_query(projection: Projection, cc: ConjoiningClauses) -> SelectQuery {
fn cc_to_select_query<T: Into<Option<u64>>>(projection: Projection, cc: ConjoiningClauses, limit: T) -> SelectQuery {
SelectQuery {
projection: projection,
from: FromClause::TableList(TableList(cc.from)),
@ -85,23 +85,24 @@ fn cc_to_select_query(projection: Projection, cc: ConjoiningClauses) -> SelectQu
.into_iter()
.map(|c| c.to_constraint())
.collect(),
limit: limit.into(),
}
}
/// Consume a provided `ConjoiningClauses` to yield a new
/// `SelectQuery`. A projection list must also be provided.
pub fn cc_to_select(projection: CombinedProjection, cc: ConjoiningClauses) -> CombinedSelectQuery {
pub fn cc_to_select(projection: CombinedProjection, cc: ConjoiningClauses, limit: Option<u64>) -> CombinedSelectQuery {
let CombinedProjection { sql_projection, datalog_projector } = projection;
CombinedSelectQuery {
query: cc_to_select_query(sql_projection, cc),
query: cc_to_select_query(sql_projection, cc, limit),
projector: datalog_projector,
}
}
pub fn query_to_select(query: AlgebraicQuery) -> CombinedSelectQuery {
cc_to_select(query_projection(&query), query.cc)
cc_to_select(query_projection(&query), query.cc, query.limit)
}
pub fn cc_to_exists(cc: ConjoiningClauses) -> SelectQuery {
cc_to_select_query(Projection::One, cc)
cc_to_select_query(Projection::One, cc, 1)
}

View file

@ -76,3 +76,22 @@ fn test_rel() {
assert_eq!(sql, "SELECT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0");
assert_eq!(args, vec![("$v0".to_string(), "yyy".to_string())]);
}
#[test]
fn test_limit() {
let mut schema = Schema::default();
associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99);
add_attribute(&mut schema, 99, Attribute {
value_type: ValueType::String,
..Default::default()
});
let input = r#"[:find ?x :where [?x :foo/bar "yyy"]]"#;
let parsed = parse_find_string(input).expect("parse failed");
let mut algebrized = algebrize(&schema, parsed);
algebrized.limit = Some(5);
let select = query_to_select(algebrized);
let SQLQuery { sql, args } = select.query.to_sql_query().unwrap();
assert_eq!(sql, "SELECT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 5");
assert_eq!(args, vec![("$v0".to_string(), "yyy".to_string())]);
}

View file

@ -107,16 +107,20 @@ impl Conn {
}
/// Query the Mentat store, using the given connection and the current metadata.
pub fn q_once<T>(&self,
pub fn q_once<T, U>(&self,
sqlite: &rusqlite::Connection,
query: &str,
inputs: T) -> Result<QueryResults>
where T: Into<Option<HashMap<String, TypedValue>>> {
inputs: T,
limit: U) -> Result<QueryResults>
where T: Into<Option<HashMap<String, TypedValue>>>,
U: Into<Option<u64>>
{
q_once(sqlite,
&*self.current_schema(),
query,
inputs.into())
inputs,
limit)
}
/// Transact entities against the Mentat store, using the given connection and the current

View file

@ -53,14 +53,21 @@ pub type QueryExecutionResult = Result<QueryResults>;
/// The caller is responsible for ensuring that the SQLite connection is in a transaction if
/// isolation is required.
#[allow(unused_variables)]
pub fn q_once<'sqlite, 'schema, 'query>
pub fn q_once<'sqlite, 'schema, 'query, T, U>
(sqlite: &'sqlite rusqlite::Connection,
schema: &'schema Schema,
query: &'query str,
inputs: Option<HashMap<String, TypedValue>>) -> QueryExecutionResult {
inputs: T,
limit: U) -> QueryExecutionResult
where T: Into<Option<HashMap<String, TypedValue>>>,
U: Into<Option<u64>>
{
// TODO: validate inputs.
let parsed = parse_find_string(query)?;
let algebrized = algebrize(schema, parsed);
let mut algebrized = algebrize(schema, parsed);
algebrized.apply_limit(limit.into());
let select = query_to_select(algebrized);
let SQLQuery { sql, args } = select.query.to_sql_query()?;

View file

@ -34,7 +34,7 @@ fn test_rel() {
// Rel.
let start = time::PreciseTime::now();
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, None)
.expect("Query failed");
let end = time::PreciseTime::now();
@ -64,7 +64,7 @@ fn test_failing_scalar() {
// Scalar that fails.
let start = time::PreciseTime::now();
let results = q_once(&c, &db.schema,
"[:find ?x . :where [?x :db/fulltext true]]", None)
"[:find ?x . :where [?x :db/fulltext true]]", None, None)
.expect("Query failed");
let end = time::PreciseTime::now();
@ -86,7 +86,7 @@ fn test_scalar() {
// Scalar that succeeds.
let start = time::PreciseTime::now();
let results = q_once(&c, &db.schema,
"[:find ?ident . :where [24 :db/ident ?ident]]", None)
"[:find ?ident . :where [24 :db/ident ?ident]]", None, None)
.expect("Query failed");
let end = time::PreciseTime::now();
@ -116,7 +116,7 @@ fn test_tuple() {
"[:find [?index ?cardinality]
:where [:db/txInstant :db/index ?index]
[:db/txInstant :db/cardinality ?cardinality]]",
None)
None, None)
.expect("Query failed");
let end = time::PreciseTime::now();
@ -143,7 +143,7 @@ fn test_coll() {
// Coll.
let start = time::PreciseTime::now();
let results = q_once(&c, &db.schema,
"[:find [?e ...] :where [?e :db/ident _]]", None)
"[:find [?e ...] :where [?e :db/ident _]]", None, None)
.expect("Query failed");
let end = time::PreciseTime::now();