Implement basic query limits. (#361) r=nalexander
This commit is contained in:
parent
85f3b79f75
commit
e898df8842
7 changed files with 73 additions and 18 deletions
|
@ -29,10 +29,26 @@ pub struct AlgebraicQuery {
|
||||||
default_source: SrcVar,
|
default_source: SrcVar,
|
||||||
pub find_spec: FindSpec,
|
pub find_spec: FindSpec,
|
||||||
has_aggregates: bool,
|
has_aggregates: bool,
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<u64>,
|
||||||
pub cc: cc::ConjoiningClauses,
|
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)]
|
#[allow(dead_code)]
|
||||||
pub fn algebrize(schema: &Schema, parsed: FindQuery) -> AlgebraicQuery {
|
pub fn algebrize(schema: &Schema, parsed: FindQuery) -> AlgebraicQuery {
|
||||||
// TODO: integrate default source into pattern processing.
|
// TODO: integrate default source into pattern processing.
|
||||||
|
|
|
@ -108,6 +108,7 @@ pub struct SelectQuery {
|
||||||
pub projection: Projection,
|
pub projection: Projection,
|
||||||
pub from: FromClause,
|
pub from: FromClause,
|
||||||
pub constraints: Vec<Constraint>,
|
pub constraints: Vec<Constraint>,
|
||||||
|
pub limit: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We know that DatomsColumns are safe to serialize.
|
// We know that DatomsColumns are safe to serialize.
|
||||||
|
@ -264,6 +265,12 @@ impl QueryFragment for SelectQuery {
|
||||||
constraint.push_sql(out)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,6 +323,7 @@ mod tests {
|
||||||
right: ColumnOrExpression::Entid(65536),
|
right: ColumnOrExpression::Entid(65536),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
limit: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let SQLQuery { sql, args } = query.to_sql_query().unwrap();
|
let SQLQuery { sql, args } = query.to_sql_query().unwrap();
|
||||||
|
|
|
@ -77,7 +77,7 @@ pub struct CombinedSelectQuery {
|
||||||
pub projector: Box<Projector>,
|
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 {
|
SelectQuery {
|
||||||
projection: projection,
|
projection: projection,
|
||||||
from: FromClause::TableList(TableList(cc.from)),
|
from: FromClause::TableList(TableList(cc.from)),
|
||||||
|
@ -85,23 +85,24 @@ fn cc_to_select_query(projection: Projection, cc: ConjoiningClauses) -> SelectQu
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|c| c.to_constraint())
|
.map(|c| c.to_constraint())
|
||||||
.collect(),
|
.collect(),
|
||||||
|
limit: limit.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consume a provided `ConjoiningClauses` to yield a new
|
/// Consume a provided `ConjoiningClauses` to yield a new
|
||||||
/// `SelectQuery`. A projection list must also be provided.
|
/// `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;
|
let CombinedProjection { sql_projection, datalog_projector } = projection;
|
||||||
CombinedSelectQuery {
|
CombinedSelectQuery {
|
||||||
query: cc_to_select_query(sql_projection, cc),
|
query: cc_to_select_query(sql_projection, cc, limit),
|
||||||
projector: datalog_projector,
|
projector: datalog_projector,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_to_select(query: AlgebraicQuery) -> CombinedSelectQuery {
|
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 {
|
pub fn cc_to_exists(cc: ConjoiningClauses) -> SelectQuery {
|
||||||
cc_to_select_query(Projection::One, cc)
|
cc_to_select_query(Projection::One, cc, 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!(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())]);
|
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())]);
|
||||||
|
}
|
||||||
|
|
12
src/conn.rs
12
src/conn.rs
|
@ -107,16 +107,20 @@ impl Conn {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query the Mentat store, using the given connection and the current metadata.
|
/// 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,
|
sqlite: &rusqlite::Connection,
|
||||||
query: &str,
|
query: &str,
|
||||||
inputs: T) -> Result<QueryResults>
|
inputs: T,
|
||||||
where T: Into<Option<HashMap<String, TypedValue>>> {
|
limit: U) -> Result<QueryResults>
|
||||||
|
where T: Into<Option<HashMap<String, TypedValue>>>,
|
||||||
|
U: Into<Option<u64>>
|
||||||
|
{
|
||||||
|
|
||||||
q_once(sqlite,
|
q_once(sqlite,
|
||||||
&*self.current_schema(),
|
&*self.current_schema(),
|
||||||
query,
|
query,
|
||||||
inputs.into())
|
inputs,
|
||||||
|
limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transact entities against the Mentat store, using the given connection and the current
|
/// Transact entities against the Mentat store, using the given connection and the current
|
||||||
|
|
13
src/query.rs
13
src/query.rs
|
@ -53,14 +53,21 @@ pub type QueryExecutionResult = Result<QueryResults>;
|
||||||
/// The caller is responsible for ensuring that the SQLite connection is in a transaction if
|
/// The caller is responsible for ensuring that the SQLite connection is in a transaction if
|
||||||
/// isolation is required.
|
/// isolation is required.
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub fn q_once<'sqlite, 'schema, 'query>
|
pub fn q_once<'sqlite, 'schema, 'query, T, U>
|
||||||
(sqlite: &'sqlite rusqlite::Connection,
|
(sqlite: &'sqlite rusqlite::Connection,
|
||||||
schema: &'schema Schema,
|
schema: &'schema Schema,
|
||||||
query: &'query str,
|
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.
|
// TODO: validate inputs.
|
||||||
|
|
||||||
let parsed = parse_find_string(query)?;
|
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 select = query_to_select(algebrized);
|
||||||
let SQLQuery { sql, args } = select.query.to_sql_query()?;
|
let SQLQuery { sql, args } = select.query.to_sql_query()?;
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ fn test_rel() {
|
||||||
// Rel.
|
// 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, None)
|
||||||
.expect("Query failed");
|
.expect("Query failed");
|
||||||
let end = time::PreciseTime::now();
|
let end = time::PreciseTime::now();
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ fn test_failing_scalar() {
|
||||||
// Scalar that fails.
|
// Scalar that fails.
|
||||||
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, None)
|
||||||
.expect("Query failed");
|
.expect("Query failed");
|
||||||
let end = time::PreciseTime::now();
|
let end = time::PreciseTime::now();
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ fn test_scalar() {
|
||||||
// Scalar that succeeds.
|
// Scalar that succeeds.
|
||||||
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, None)
|
||||||
.expect("Query failed");
|
.expect("Query failed");
|
||||||
let end = time::PreciseTime::now();
|
let end = time::PreciseTime::now();
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ fn test_tuple() {
|
||||||
"[:find [?index ?cardinality]
|
"[:find [?index ?cardinality]
|
||||||
:where [:db/txInstant :db/index ?index]
|
:where [:db/txInstant :db/index ?index]
|
||||||
[:db/txInstant :db/cardinality ?cardinality]]",
|
[:db/txInstant :db/cardinality ?cardinality]]",
|
||||||
None)
|
None, None)
|
||||||
.expect("Query failed");
|
.expect("Query failed");
|
||||||
let end = time::PreciseTime::now();
|
let end = time::PreciseTime::now();
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ fn test_coll() {
|
||||||
// Coll.
|
// 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, None)
|
||||||
.expect("Query failed");
|
.expect("Query failed");
|
||||||
let end = time::PreciseTime::now();
|
let end = time::PreciseTime::now();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue