2017-02-22 03:57:00 +00:00
// Copyright 2016 Mozilla
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
extern crate mentat_core ;
extern crate mentat_query ;
extern crate mentat_query_algebrizer ;
extern crate mentat_query_parser ;
2018-03-06 17:01:20 +00:00
extern crate mentat_query_projector ;
2017-02-22 03:57:00 +00:00
extern crate mentat_query_translator ;
extern crate mentat_sql ;
2017-06-12 21:19:35 +00:00
use std ::collections ::BTreeMap ;
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
use std ::rc ::Rc ;
2017-04-19 23:16:19 +00:00
use mentat_query ::{
2018-03-06 17:01:20 +00:00
FindSpec ,
2018-05-11 16:52:17 +00:00
Keyword ,
2017-04-19 23:16:19 +00:00
Variable ,
} ;
2017-02-22 03:57:00 +00:00
use mentat_core ::{
Attribute ,
Entid ,
Schema ,
2017-04-19 23:16:19 +00:00
TypedValue ,
2017-02-22 03:57:00 +00:00
ValueType ,
} ;
use mentat_query_parser ::parse_find_string ;
2017-04-19 23:16:19 +00:00
use mentat_query_algebrizer ::{
2018-02-14 00:51:21 +00:00
Known ,
2017-04-19 23:16:19 +00:00
QueryInputs ,
algebrize ,
algebrize_with_inputs ,
} ;
2018-03-06 17:01:20 +00:00
use mentat_query_projector ::{
ConstantProjector ,
} ;
2017-02-22 03:57:00 +00:00
use mentat_query_translator ::{
2018-03-06 17:01:20 +00:00
ProjectedSelect ,
2017-03-06 22:40:10 +00:00
query_to_select ,
2017-02-22 03:57:00 +00:00
} ;
use mentat_sql ::SQLQuery ;
2018-03-06 17:01:20 +00:00
/// Produce the appropriate `Variable` for the provided valid ?-prefixed name.
/// This lives here because we can't re-export macros:
/// https://github.com/rust-lang/rust/issues/29638.
macro_rules ! var {
( ? $var :ident ) = > {
$crate ::Variable ::from_valid_name ( concat! ( " ? " , stringify! ( $var ) ) )
} ;
}
2018-05-11 16:52:17 +00:00
fn associate_ident ( schema : & mut Schema , i : Keyword , e : Entid ) {
2017-02-22 03:57:00 +00:00
schema . entid_map . insert ( e , i . clone ( ) ) ;
schema . ident_map . insert ( i . clone ( ) , e ) ;
}
fn add_attribute ( schema : & mut Schema , e : Entid , a : Attribute ) {
2018-01-23 16:23:37 +00:00
schema . attribute_map . insert ( e , a ) ;
2017-02-22 03:57:00 +00:00
}
2018-03-06 17:01:20 +00:00
fn query_to_sql ( query : ProjectedSelect ) -> SQLQuery {
match query {
ProjectedSelect ::Query { query , projector : _projector } = > {
query . to_sql_query ( ) . expect ( " to_sql_query to succeed " )
} ,
ProjectedSelect ::Constant ( constant ) = > {
panic! ( " ProjectedSelect wasn't ::Query! Got constant {:#?} " , constant . project_without_rows ( ) ) ;
} ,
}
}
fn query_to_constant ( query : ProjectedSelect ) -> ConstantProjector {
match query {
ProjectedSelect ::Constant ( constant ) = > {
constant
} ,
_ = > panic! ( " ProjectedSelect wasn't ::Constant! " ) ,
}
}
fn assert_query_is_empty ( query : ProjectedSelect , expected_spec : FindSpec ) {
let constant = query_to_constant ( query ) . project_without_rows ( ) . expect ( " constant run " ) ;
assert_eq! ( * constant . spec , expected_spec ) ;
assert! ( constant . results . is_empty ( ) ) ;
}
fn inner_translate_with_inputs ( schema : & Schema , query : & 'static str , inputs : QueryInputs ) -> ProjectedSelect {
2018-02-14 00:51:21 +00:00
let known = Known ::for_schema ( schema ) ;
2017-11-30 23:02:07 +00:00
let parsed = parse_find_string ( query ) . expect ( " parse to succeed " ) ;
2018-02-14 00:51:21 +00:00
let algebrized = algebrize_with_inputs ( known , parsed , 0 , inputs ) . expect ( " algebrize to succeed " ) ;
2018-05-04 19:56:00 +00:00
query_to_select ( schema , algebrized ) . expect ( " translate to succeed " )
2018-03-06 17:01:20 +00:00
}
fn translate_with_inputs ( schema : & Schema , query : & 'static str , inputs : QueryInputs ) -> SQLQuery {
query_to_sql ( inner_translate_with_inputs ( schema , query , inputs ) )
2017-03-07 04:18:38 +00:00
}
2017-04-19 23:16:19 +00:00
fn translate ( schema : & Schema , query : & 'static str ) -> SQLQuery {
translate_with_inputs ( schema , query , QueryInputs ::default ( ) )
}
2018-03-06 17:01:20 +00:00
fn translate_with_inputs_to_constant ( schema : & Schema , query : & 'static str , inputs : QueryInputs ) -> ConstantProjector {
query_to_constant ( inner_translate_with_inputs ( schema , query , inputs ) )
}
fn translate_to_constant ( schema : & Schema , query : & 'static str ) -> ConstantProjector {
translate_with_inputs_to_constant ( schema , query , QueryInputs ::default ( ) )
}
2017-04-04 21:54:08 +00:00
fn prepopulated_typed_schema ( foo_type : ValueType ) -> Schema {
2017-03-06 22:40:10 +00:00
let mut schema = Schema ::default ( ) ;
2018-05-11 16:52:17 +00:00
associate_ident ( & mut schema , Keyword ::namespaced ( " foo " , " bar " ) , 99 ) ;
2017-03-06 22:40:10 +00:00
add_attribute ( & mut schema , 99 , Attribute {
2017-04-04 21:54:08 +00:00
value_type : foo_type ,
2017-03-06 22:40:10 +00:00
.. Default ::default ( )
} ) ;
2018-05-11 16:52:17 +00:00
associate_ident ( & mut schema , Keyword ::namespaced ( " foo " , " fts " ) , 100 ) ;
2017-06-12 21:24:56 +00:00
add_attribute ( & mut schema , 100 , Attribute {
value_type : ValueType ::String ,
index : true ,
fulltext : true ,
.. Default ::default ( )
} ) ;
2017-03-22 21:02:00 +00:00
schema
}
2017-04-04 21:54:08 +00:00
fn prepopulated_schema ( ) -> Schema {
prepopulated_typed_schema ( ValueType ::String )
}
2017-04-29 03:11:55 +00:00
fn make_arg ( name : & 'static str , value : & 'static str ) -> ( String , Rc < mentat_sql ::Value > ) {
( name . to_string ( ) , Rc ::new ( mentat_sql ::Value ::Text ( value . to_string ( ) ) ) )
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
}
2017-03-22 21:02:00 +00:00
#[ test ]
fn test_scalar ( ) {
let schema = prepopulated_schema ( ) ;
2017-04-19 23:16:19 +00:00
let query = r # "[:find ?x . :where [?x :foo/bar "yyy"]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2017-03-22 21:02:00 +00:00
assert_eq! ( sql , " SELECT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 1 " ) ;
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " yyy " ) ] ) ;
2017-03-22 21:02:00 +00:00
}
#[ test ]
fn test_tuple ( ) {
let schema = prepopulated_schema ( ) ;
2017-04-19 23:16:19 +00:00
let query = r # "[:find [?x] :where [?x :foo/bar "yyy"]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2017-03-22 21:02:00 +00:00
assert_eq! ( sql , " SELECT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 1 " ) ;
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " yyy " ) ] ) ;
2017-03-22 21:02:00 +00:00
}
#[ test ]
fn test_coll ( ) {
let schema = prepopulated_schema ( ) ;
2017-03-06 22:40:10 +00:00
2017-04-19 23:16:19 +00:00
let query = r # "[:find [?x ...] :where [?x :foo/bar "yyy"]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2017-03-22 21:02:00 +00:00
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 " ) ;
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " yyy " ) ] ) ;
2017-03-06 22:40:10 +00:00
}
#[ test ]
fn test_rel ( ) {
2017-03-22 21:02:00 +00:00
let schema = prepopulated_schema ( ) ;
2017-02-22 03:57:00 +00:00
2017-04-19 23:16:19 +00:00
let query = r # "[:find ?x :where [?x :foo/bar "yyy"]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2017-03-22 21:02:00 +00:00
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 " ) ;
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " yyy " ) ] ) ;
2017-02-22 03:57:00 +00:00
}
2017-03-07 00:27:13 +00:00
#[ test ]
fn test_limit ( ) {
2017-03-22 21:02:00 +00:00
let schema = prepopulated_schema ( ) ;
2017-03-07 00:27:13 +00:00
2017-04-19 23:16:19 +00:00
let query = r # "[:find ?x :where [?x :foo/bar "yyy"] :limit 5]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2017-03-22 21:02:00 +00:00
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 5 " ) ;
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " yyy " ) ] ) ;
2017-03-07 00:27:13 +00:00
}
2017-03-07 04:18:38 +00:00
2017-04-19 23:16:19 +00:00
#[ test ]
fn test_unbound_variable_limit ( ) {
let schema = prepopulated_schema ( ) ;
// We don't know the value of the limit var, so we produce an escaped SQL variable to handle
// later input.
let query = r # "[:find ?x :in ?limit-is-9-great :where [?x :foo/bar "yyy"] :limit ?limit-is-9-great]"# ;
let SQLQuery { sql , args } = translate_with_inputs ( & schema , query , QueryInputs ::default ( ) ) ;
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` \
FROM ` datoms ` AS ` datoms00 ` \
WHERE ` datoms00 ` . a = 99 AND ` datoms00 ` . v = $v0 \
LIMIT $ilimit_is_9_great " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " yyy " ) ] ) ;
}
#[ test ]
fn test_bound_variable_limit ( ) {
let schema = prepopulated_schema ( ) ;
// We know the value of `?limit` at algebrizing time, so we substitute directly.
let query = r # "[:find ?x :in ?limit :where [?x :foo/bar "yyy"] :limit ?limit]"# ;
let inputs = QueryInputs ::with_value_sequence ( vec! [ ( Variable ::from_valid_name ( " ?limit " ) , TypedValue ::Long ( 92 ) ) ] ) ;
let SQLQuery { sql , args } = translate_with_inputs ( & schema , query , inputs ) ;
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 92 " ) ;
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " yyy " ) ] ) ;
}
#[ test ]
fn test_bound_variable_limit_affects_distinct ( ) {
let schema = prepopulated_schema ( ) ;
// We know the value of `?limit` at algebrizing time, so we substitute directly.
// As it's `1`, we know we don't need `DISTINCT`!
let query = r # "[:find ?x :in ?limit :where [?x :foo/bar "yyy"] :limit ?limit]"# ;
let inputs = QueryInputs ::with_value_sequence ( vec! [ ( Variable ::from_valid_name ( " ?limit " ) , TypedValue ::Long ( 1 ) ) ] ) ;
let SQLQuery { sql , args } = translate_with_inputs ( & schema , query , inputs ) ;
assert_eq! ( sql , " SELECT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 1 " ) ;
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " yyy " ) ] ) ;
}
#[ test ]
fn test_bound_variable_limit_affects_types ( ) {
let schema = prepopulated_schema ( ) ;
2018-02-14 00:51:21 +00:00
let known = Known ::for_schema ( & schema ) ;
2017-04-19 23:16:19 +00:00
let query = r # "[:find ?x ?limit :in ?limit :where [?x _ ?limit] :limit ?limit]"# ;
let parsed = parse_find_string ( query ) . expect ( " parse failed " ) ;
2018-02-14 00:51:21 +00:00
let algebrized = algebrize ( known , parsed ) . expect ( " algebrize failed " ) ;
2017-04-19 23:16:19 +00:00
// The type is known.
assert_eq! ( Some ( ValueType ::Long ) ,
algebrized . cc . known_type ( & Variable ::from_valid_name ( " ?limit " ) ) ) ;
2018-05-04 19:56:00 +00:00
let select = query_to_select ( & schema , algebrized ) . expect ( " query to translate " ) ;
2018-03-06 17:01:20 +00:00
let SQLQuery { sql , args } = query_to_sql ( select ) ;
2017-04-19 23:16:19 +00:00
// TODO: this query isn't actually correct -- we don't yet algebrize for variables that are
// specified in `:in` but not provided at algebrizing time. But it shows what we care about
// at the moment: we don't project a type column, because we know it's a Long.
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x`, `datoms00`.v AS `?limit` FROM `datoms` AS `datoms00` LIMIT $ilimit " ) ;
assert_eq! ( args , vec! [ ] ) ;
}
2017-03-07 04:18:38 +00:00
#[ test ]
fn test_unknown_attribute_keyword_value ( ) {
let schema = Schema ::default ( ) ;
2017-04-19 23:16:19 +00:00
let query = r # "[:find ?x :where [?x _ :ab/yyy]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2017-03-07 04:18:38 +00:00
// Only match keywords, not strings: tag = 13.
2018-01-29 22:29:16 +00:00
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.v = $v0 AND (`datoms00`.value_type_tag = 13) " ) ;
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " :ab/yyy " ) ] ) ;
2017-03-07 04:18:38 +00:00
}
#[ test ]
fn test_unknown_attribute_string_value ( ) {
let schema = Schema ::default ( ) ;
2017-04-19 23:16:19 +00:00
let query = r # "[:find ?x :where [?x _ "horses"]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2017-03-07 04:18:38 +00:00
// We expect all_datoms because we're querying for a string. Magic, that.
// We don't want keywords etc., so tag = 10.
2018-01-29 22:29:16 +00:00
assert_eq! ( sql , " SELECT DISTINCT `all_datoms00`.e AS `?x` FROM `all_datoms` AS `all_datoms00` WHERE `all_datoms00`.v = $v0 AND (`all_datoms00`.value_type_tag = 10) " ) ;
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " horses " ) ] ) ;
2017-03-07 04:18:38 +00:00
}
#[ test ]
fn test_unknown_attribute_double_value ( ) {
let schema = Schema ::default ( ) ;
2017-04-19 23:16:19 +00:00
let query = r # "[:find ?x :where [?x _ 9.95]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2017-03-07 04:18:38 +00:00
// In general, doubles _could_ be 1.0, which might match a boolean or a ref. Set tag = 5 to
// make sure we only match numbers.
2018-01-29 22:29:16 +00:00
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.v = 9.95e0 AND (`datoms00`.value_type_tag = 5) " ) ;
2017-03-07 04:18:38 +00:00
assert_eq! ( args , vec! [ ] ) ;
}
#[ test ]
fn test_unknown_attribute_integer_value ( ) {
let schema = Schema ::default ( ) ;
let negative = r # "[:find ?x :where [?x _ -1]]"# ;
let zero = r # "[:find ?x :where [?x _ 0]]"# ;
let one = r # "[:find ?x :where [?x _ 1]]"# ;
let two = r # "[:find ?x :where [?x _ 2]]"# ;
// Can't match boolean; no need to filter it out.
2017-04-19 23:16:19 +00:00
let SQLQuery { sql , args } = translate ( & schema , negative ) ;
2017-03-22 21:02:00 +00:00
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.v = -1 " ) ;
2017-03-07 04:18:38 +00:00
assert_eq! ( args , vec! [ ] ) ;
// Excludes booleans.
2017-04-19 23:16:19 +00:00
let SQLQuery { sql , args } = translate ( & schema , zero ) ;
2017-03-22 21:02:00 +00:00
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE (`datoms00`.v = 0 AND `datoms00`.value_type_tag <> 1) " ) ;
2017-03-07 04:18:38 +00:00
assert_eq! ( args , vec! [ ] ) ;
// Excludes booleans.
2017-04-19 23:16:19 +00:00
let SQLQuery { sql , args } = translate ( & schema , one ) ;
2017-03-22 21:02:00 +00:00
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE (`datoms00`.v = 1 AND `datoms00`.value_type_tag <> 1) " ) ;
2017-03-07 04:18:38 +00:00
assert_eq! ( args , vec! [ ] ) ;
// Can't match boolean; no need to filter it out.
2017-04-19 23:16:19 +00:00
let SQLQuery { sql , args } = translate ( & schema , two ) ;
2017-03-22 21:02:00 +00:00
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.v = 2 " ) ;
2017-03-07 04:18:38 +00:00
assert_eq! ( args , vec! [ ] ) ;
}
#[ test ]
fn test_unknown_ident ( ) {
let schema = Schema ::default ( ) ;
2018-02-14 00:51:21 +00:00
let known = Known ::for_schema ( & schema ) ;
2017-03-07 04:18:38 +00:00
let impossible = r # "[:find ?x :where [?x :db/ident :no/exist]]"# ;
let parsed = parse_find_string ( impossible ) . expect ( " parse failed " ) ;
2018-02-14 00:51:21 +00:00
let algebrized = algebrize ( known , parsed ) . expect ( " algebrize failed " ) ;
2017-03-07 04:18:38 +00:00
// This query cannot return results: the ident doesn't resolve for a ref-typed attribute.
assert! ( algebrized . is_known_empty ( ) ) ;
// If you insist…
2018-05-04 19:56:00 +00:00
let select = query_to_select ( & schema , algebrized ) . expect ( " query to translate " ) ;
2018-03-06 17:01:20 +00:00
assert_query_is_empty ( select , FindSpec ::FindRel ( vec! [ var! ( ? x ) . into ( ) ] ) ) ;
2017-03-07 04:18:38 +00:00
}
2017-03-16 19:23:48 +00:00
2018-01-29 22:29:16 +00:00
#[ test ]
fn test_type_required_long ( ) {
let schema = Schema ::default ( ) ;
let query = r # "[:find ?x :where [?x _ ?e] [(long ?e)]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` \
FROM ` datoms ` AS ` datoms00 ` \
WHERE ( ( ` datoms00 ` . value_type_tag = 5 AND \
( typeof ( ` datoms00 ` . v ) = ' integer ' ) ) ) " );
assert_eq! ( args , vec! [ ] ) ;
}
#[ test ]
fn test_type_required_double ( ) {
let schema = Schema ::default ( ) ;
let query = r # "[:find ?x :where [?x _ ?e] [(double ?e)]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` \
FROM ` datoms ` AS ` datoms00 ` \
WHERE ( ( ` datoms00 ` . value_type_tag = 5 AND \
( typeof ( ` datoms00 ` . v ) = ' real ' ) ) ) " );
assert_eq! ( args , vec! [ ] ) ;
}
#[ test ]
fn test_type_required_boolean ( ) {
let schema = Schema ::default ( ) ;
let query = r # "[:find ?x :where [?x _ ?e] [(boolean ?e)]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` \
FROM ` datoms ` AS ` datoms00 ` \
WHERE ( ` datoms00 ` . value_type_tag = 1 ) " );
assert_eq! ( args , vec! [ ] ) ;
}
#[ test ]
fn test_type_required_string ( ) {
let schema = Schema ::default ( ) ;
let query = r # "[:find ?x :where [?x _ ?e] [(string ?e)]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
// Note: strings should use `all_datoms` and not `datoms`.
assert_eq! ( sql , " SELECT DISTINCT `all_datoms00`.e AS `?x` \
FROM ` all_datoms ` AS ` all_datoms00 ` \
WHERE ( ` all_datoms00 ` . value_type_tag = 10 ) " );
assert_eq! ( args , vec! [ ] ) ;
}
2017-03-16 19:23:48 +00:00
#[ test ]
fn test_numeric_less_than_unknown_attribute ( ) {
let schema = Schema ::default ( ) ;
2017-04-19 23:16:19 +00:00
let query = r # "[:find ?x :where [?x _ ?y] [(< ?y 10)]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2017-03-16 19:23:48 +00:00
2017-03-20 14:11:32 +00:00
// Although we infer numericness from numeric predicates, we've already assigned a table to the
// first pattern, and so this is _still_ `all_datoms`.
2017-03-22 21:02:00 +00:00
assert_eq! ( sql , " SELECT DISTINCT `all_datoms00`.e AS `?x` FROM `all_datoms` AS `all_datoms00` WHERE `all_datoms00`.v < 10 " ) ;
2017-03-16 19:23:48 +00:00
assert_eq! ( args , vec! [ ] ) ;
}
#[ test ]
fn test_numeric_gte_known_attribute ( ) {
2017-04-04 21:54:08 +00:00
let schema = prepopulated_typed_schema ( ValueType ::Double ) ;
2017-04-19 23:16:19 +00:00
let query = r # "[:find ?x :where [?x :foo/bar ?y] [(>= ?y 12.9)]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2017-06-14 00:26:53 +00:00
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v >= 1.29e1 " ) ;
2017-03-16 19:23:48 +00:00
assert_eq! ( args , vec! [ ] ) ;
}
#[ test ]
fn test_numeric_not_equals_known_attribute ( ) {
2017-04-04 21:54:08 +00:00
let schema = prepopulated_typed_schema ( ValueType ::Long ) ;
2017-04-19 23:16:19 +00:00
let query = r # "[:find ?x . :where [?x :foo/bar ?y] [(!= ?y 12)]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2017-03-22 21:02:00 +00:00
assert_eq! ( sql , " SELECT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v <> 12 LIMIT 1 " ) ;
2017-03-16 19:23:48 +00:00
assert_eq! ( args , vec! [ ] ) ;
2017-04-04 21:54:08 +00:00
}
2017-06-14 23:17:25 +00:00
#[ test ]
fn test_compare_long_to_double_constants ( ) {
let schema = prepopulated_typed_schema ( ValueType ::Double ) ;
let query = r #" [:find ?e .
:where
[ ? e :foo / bar ? v ]
[ ( < 99.0 1234512345 ) ] ] " #;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT `datoms00`.e AS `?e` FROM `datoms` AS `datoms00` \
WHERE ` datoms00 ` . a = 99 \
AND 9.9e1 < 1234512345 \
LIMIT 1 " );
assert_eq! ( args , vec! [ ] ) ;
}
#[ test ]
fn test_compare_long_to_double ( ) {
let schema = prepopulated_typed_schema ( ValueType ::Double ) ;
// You can compare longs to doubles.
let query = r #" [:find ?e .
:where
[ ? e :foo / bar ? t ]
[ ( < ? t 1234512345 ) ] ] " #;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT `datoms00`.e AS `?e` FROM `datoms` AS `datoms00` \
WHERE ` datoms00 ` . a = 99 \
AND ` datoms00 ` . v < 1234512345 \
LIMIT 1 " );
assert_eq! ( args , vec! [ ] ) ;
}
#[ test ]
fn test_compare_double_to_long ( ) {
let schema = prepopulated_typed_schema ( ValueType ::Long ) ;
// You can compare doubles to longs.
let query = r #" [:find ?e .
:where
[ ? e :foo / bar ? t ]
[ ( < ? t 1234512345.0 ) ] ] " #;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT `datoms00`.e AS `?e` FROM `datoms` AS `datoms00` \
WHERE ` datoms00 ` . a = 99 \
AND ` datoms00 ` . v < 1.234512345e9 \
LIMIT 1 " );
assert_eq! ( args , vec! [ ] ) ;
}
2017-04-04 21:54:08 +00:00
#[ test ]
fn test_simple_or_join ( ) {
let mut schema = Schema ::default ( ) ;
2018-05-11 16:52:17 +00:00
associate_ident ( & mut schema , Keyword ::namespaced ( " page " , " url " ) , 97 ) ;
associate_ident ( & mut schema , Keyword ::namespaced ( " page " , " title " ) , 98 ) ;
associate_ident ( & mut schema , Keyword ::namespaced ( " page " , " description " ) , 99 ) ;
2017-04-04 21:54:08 +00:00
for x in 97 .. 100 {
add_attribute ( & mut schema , x , Attribute {
value_type : ValueType ::String ,
.. Default ::default ( )
} ) ;
}
2017-04-19 23:16:19 +00:00
let query = r #" [:find [?url ?description]
2017-04-04 21:54:08 +00:00
:where
( or - join [ ? page ]
[ ? page :page / url " http://foo.com/ " ]
[ ? page :page / title " Foo " ] )
[ ? page :page / url ? url ]
[ ? page :page / description ? description ] ] " #;
2017-04-19 23:16:19 +00:00
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2017-04-04 21:54:08 +00:00
assert_eq! ( sql , " SELECT `datoms01`.v AS `?url`, `datoms02`.v AS `?description` FROM `datoms` AS `datoms00`, `datoms` AS `datoms01`, `datoms` AS `datoms02` WHERE ((`datoms00`.a = 97 AND `datoms00`.v = $v0) OR (`datoms00`.a = 98 AND `datoms00`.v = $v1)) AND `datoms01`.a = 97 AND `datoms02`.a = 99 AND `datoms00`.e = `datoms01`.e AND `datoms00`.e = `datoms02`.e LIMIT 1 " ) ;
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " http://foo.com/ " ) , make_arg ( " $v1 " , " Foo " ) ] ) ;
}
2017-04-11 17:31:31 +00:00
#[ test ]
fn test_complex_or_join ( ) {
let mut schema = Schema ::default ( ) ;
2018-05-11 16:52:17 +00:00
associate_ident ( & mut schema , Keyword ::namespaced ( " page " , " save " ) , 95 ) ;
2017-04-11 17:31:31 +00:00
add_attribute ( & mut schema , 95 , Attribute {
value_type : ValueType ::Ref ,
.. Default ::default ( )
} ) ;
2018-05-11 16:52:17 +00:00
associate_ident ( & mut schema , Keyword ::namespaced ( " save " , " title " ) , 96 ) ;
associate_ident ( & mut schema , Keyword ::namespaced ( " page " , " url " ) , 97 ) ;
associate_ident ( & mut schema , Keyword ::namespaced ( " page " , " title " ) , 98 ) ;
associate_ident ( & mut schema , Keyword ::namespaced ( " page " , " description " ) , 99 ) ;
2017-04-11 17:31:31 +00:00
for x in 96 .. 100 {
add_attribute ( & mut schema , x , Attribute {
value_type : ValueType ::String ,
.. Default ::default ( )
} ) ;
}
2017-04-19 23:16:19 +00:00
let query = r #" [:find [?url ?description]
2017-04-11 17:31:31 +00:00
:where
( or - join [ ? page ]
[ ? page :page / url " http://foo.com/ " ]
[ ? page :page / title " Foo " ]
( and
[ ? page :page / save ? save ]
[ ? save :save / title " Foo " ] ) )
[ ? page :page / url ? url ]
[ ? page :page / description ? description ] ] " #;
2017-04-19 23:16:19 +00:00
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2017-04-11 17:31:31 +00:00
assert_eq! ( sql , " SELECT `datoms04`.v AS `?url`, \
` datoms05 ` . v AS ` ? description ` \
FROM ( SELECT ` datoms00 ` . e AS ` ? page ` \
FROM ` datoms ` AS ` datoms00 ` \
WHERE ` datoms00 ` . a = 97 \
AND ` datoms00 ` . v = $v0 \
UNION \
SELECT ` datoms01 ` . e AS ` ? page ` \
FROM ` datoms ` AS ` datoms01 ` \
WHERE ` datoms01 ` . a = 98 \
AND ` datoms01 ` . v = $v1 \
UNION \
SELECT ` datoms02 ` . e AS ` ? page ` \
FROM ` datoms ` AS ` datoms02 ` , \
` datoms ` AS ` datoms03 ` \
WHERE ` datoms02 ` . a = 95 \
AND ` datoms03 ` . a = 96 \
2017-06-07 01:21:07 +00:00
AND ` datoms03 ` . v = $v1 \
2017-04-11 17:31:31 +00:00
AND ` datoms02 ` . v = ` datoms03 ` . e ) AS ` c00 ` , \
` datoms ` AS ` datoms04 ` , \
` datoms ` AS ` datoms05 ` \
WHERE ` datoms04 ` . a = 97 \
AND ` datoms05 ` . a = 99 \
AND ` c00 ` . ` ? page ` = ` datoms04 ` . e \
AND ` c00 ` . ` ? page ` = ` datoms05 ` . e \
LIMIT 1 " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " http://foo.com/ " ) ,
2017-06-07 01:21:07 +00:00
make_arg ( " $v1 " , " Foo " ) ] ) ;
2017-04-11 17:31:31 +00:00
}
#[ test ]
fn test_complex_or_join_type_projection ( ) {
let mut schema = Schema ::default ( ) ;
2018-05-11 16:52:17 +00:00
associate_ident ( & mut schema , Keyword ::namespaced ( " page " , " title " ) , 98 ) ;
2017-04-11 17:31:31 +00:00
add_attribute ( & mut schema , 98 , Attribute {
value_type : ValueType ::String ,
.. Default ::default ( )
} ) ;
2017-04-19 23:16:19 +00:00
let query = r #" [:find [?y]
2017-04-11 17:31:31 +00:00
:where
( or
[ 6 :page / title ? y ]
[ 5 _ ? y ] ) ] " #;
2017-04-19 23:16:19 +00:00
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2017-04-11 17:31:31 +00:00
assert_eq! ( sql , " SELECT `c00`.`?y` AS `?y`, \
` c00 ` . ` ? y_value_type_tag ` AS ` ? y_value_type_tag ` \
FROM ( SELECT ` datoms00 ` . v AS ` ? y ` , \
10 AS ` ? y_value_type_tag ` \
FROM ` datoms ` AS ` datoms00 ` \
WHERE ` datoms00 ` . e = 6 \
AND ` datoms00 ` . a = 98 \
UNION \
SELECT ` all_datoms01 ` . v AS ` ? y ` , \
` all_datoms01 ` . value_type_tag AS ` ? y_value_type_tag ` \
FROM ` all_datoms ` AS ` all_datoms01 ` \
WHERE ` all_datoms01 ` . e = 5 ) AS ` c00 ` \
LIMIT 1 " );
assert_eq! ( args , vec! [ ] ) ;
}
2017-04-17 16:23:55 +00:00
2017-04-28 09:44:11 +00:00
#[ test ]
fn test_not ( ) {
let mut schema = Schema ::default ( ) ;
2018-05-11 16:52:17 +00:00
associate_ident ( & mut schema , Keyword ::namespaced ( " page " , " url " ) , 97 ) ;
associate_ident ( & mut schema , Keyword ::namespaced ( " page " , " title " ) , 98 ) ;
associate_ident ( & mut schema , Keyword ::namespaced ( " page " , " bookmarked " ) , 99 ) ;
2017-04-28 09:44:11 +00:00
for x in 97 .. 99 {
add_attribute ( & mut schema , x , Attribute {
value_type : ValueType ::String ,
.. Default ::default ( )
} ) ;
}
add_attribute ( & mut schema , 99 , Attribute {
value_type : ValueType ::Boolean ,
.. Default ::default ( )
} ) ;
let query = r #" [:find ?title
:where [ ? page :page / title ? title ]
( not [ ? page :page / url " http://foo.com/ " ]
[ ? page :page / bookmarked true ] ) ] " #;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.v AS `?title` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 98 AND NOT EXISTS (SELECT 1 FROM `datoms` AS `datoms01`, `datoms` AS `datoms02` WHERE `datoms01`.a = 97 AND `datoms01`.v = $v0 AND `datoms02`.a = 99 AND `datoms02`.v = 1 AND `datoms00`.e = `datoms01`.e AND `datoms00`.e = `datoms02`.e) " ) ;
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " http://foo.com/ " ) ] ) ;
}
#[ test ]
fn test_not_join ( ) {
let mut schema = Schema ::default ( ) ;
2018-05-11 16:52:17 +00:00
associate_ident ( & mut schema , Keyword ::namespaced ( " page " , " url " ) , 97 ) ;
associate_ident ( & mut schema , Keyword ::namespaced ( " bookmarks " , " page " ) , 98 ) ;
associate_ident ( & mut schema , Keyword ::namespaced ( " bookmarks " , " date_created " ) , 99 ) ;
2017-04-28 09:44:11 +00:00
add_attribute ( & mut schema , 97 , Attribute {
value_type : ValueType ::String ,
.. Default ::default ( )
} ) ;
add_attribute ( & mut schema , 98 , Attribute {
value_type : ValueType ::Ref ,
.. Default ::default ( )
} ) ;
add_attribute ( & mut schema , 99 , Attribute {
value_type : ValueType ::String ,
.. Default ::default ( )
} ) ;
let query = r #" [:find ?url
:where [ ? url :page / url ]
( not - join [ ? url ]
[ ? page :bookmarks / page ? url ]
[ ? page :bookmarks / date_created " 4/4/2017 " ] ) ] " #;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?url` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 97 AND NOT EXISTS (SELECT 1 FROM `datoms` AS `datoms01`, `datoms` AS `datoms02` WHERE `datoms01`.a = 98 AND `datoms02`.a = 99 AND `datoms02`.v = $v0 AND `datoms01`.e = `datoms02`.e AND `datoms00`.e = `datoms01`.v) " ) ;
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " 4/4/2017 " ) ] ) ;
}
2017-04-17 16:23:55 +00:00
#[ test ]
fn test_with_without_aggregate ( ) {
let schema = prepopulated_schema ( ) ;
// Known type.
2017-04-19 23:16:19 +00:00
let query = r # "[:find ?x :with ?y :where [?x :foo/bar ?y]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2018-03-12 22:18:50 +00:00
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 " ) ;
2017-04-17 16:23:55 +00:00
assert_eq! ( args , vec! [ ] ) ;
// Unknown type.
2017-04-19 23:16:19 +00:00
let query = r # "[:find ?x :with ?y :where [?x _ ?y]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2018-03-12 22:18:50 +00:00
assert_eq! ( sql , " SELECT DISTINCT `all_datoms00`.e AS `?x` FROM `all_datoms` AS `all_datoms00` " ) ;
2017-04-17 16:23:55 +00:00
assert_eq! ( args , vec! [ ] ) ;
Implement :order. (#415) (#416) r=nalexander
This adds an `:order` keyword to `:find`.
If present, the results of the query will be an ordered set, rather than
an unordered set; rows will appear in an ordered defined by each
`:order` entry.
Each can be one of three things:
- A var, `?x`, meaning "order by ?x ascending".
- A pair, `(asc ?x)`, meaning "order by ?x ascending".
- A pair, `(desc ?x)`, meaning "order by ?x descending".
Values will be ordered in this sequence for asc, and in reverse for desc:
1. Entity IDs, in ascending numerical order.
2. Booleans, false then true.
3. Timestamps, in ascending numerical order.
4. Longs and doubles, intermixed, in ascending numerical order.
5. Strings, in ascending lexicographic order.
6. Keywords, in ascending lexicographic order, considering the entire
ns/name pair as a single string separated by '/'.
Subcommits:
Pre: make bound_value public.
Pre: generalize ErrorKind::UnboundVariable for use in order.
Part 1: parse (direction, var) pairs.
Part 2: parse :order clause into FindQuery.
Part 3: include order variables in algebrized query.
We add order variables to :with, so we can reuse its type tag projection
logic, and so that we can phrase ordering in terms of variables rather
than datoms columns.
Part 4: produce SQL for order clauses.
2017-04-14 23:10:56 +00:00
}
#[ test ]
fn test_order_by ( ) {
let schema = prepopulated_schema ( ) ;
// Known type.
2017-04-19 23:16:19 +00:00
let query = r # "[:find ?x :where [?x :foo/bar ?y] :order (desc ?y)]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
Implement :order. (#415) (#416) r=nalexander
This adds an `:order` keyword to `:find`.
If present, the results of the query will be an ordered set, rather than
an unordered set; rows will appear in an ordered defined by each
`:order` entry.
Each can be one of three things:
- A var, `?x`, meaning "order by ?x ascending".
- A pair, `(asc ?x)`, meaning "order by ?x ascending".
- A pair, `(desc ?x)`, meaning "order by ?x descending".
Values will be ordered in this sequence for asc, and in reverse for desc:
1. Entity IDs, in ascending numerical order.
2. Booleans, false then true.
3. Timestamps, in ascending numerical order.
4. Longs and doubles, intermixed, in ascending numerical order.
5. Strings, in ascending lexicographic order.
6. Keywords, in ascending lexicographic order, considering the entire
ns/name pair as a single string separated by '/'.
Subcommits:
Pre: make bound_value public.
Pre: generalize ErrorKind::UnboundVariable for use in order.
Part 1: parse (direction, var) pairs.
Part 2: parse :order clause into FindQuery.
Part 3: include order variables in algebrized query.
We add order variables to :with, so we can reuse its type tag projection
logic, and so that we can phrase ordering in terms of variables rather
than datoms columns.
Part 4: produce SQL for order clauses.
2017-04-14 23:10:56 +00:00
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?x`, `datoms00`.v AS `?y` \
FROM ` datoms ` AS ` datoms00 ` \
WHERE ` datoms00 ` . a = 99 \
ORDER BY ` ? y ` DESC " );
2017-04-17 20:29:40 +00:00
assert_eq! ( args , vec! [ ] ) ;
Implement :order. (#415) (#416) r=nalexander
This adds an `:order` keyword to `:find`.
If present, the results of the query will be an ordered set, rather than
an unordered set; rows will appear in an ordered defined by each
`:order` entry.
Each can be one of three things:
- A var, `?x`, meaning "order by ?x ascending".
- A pair, `(asc ?x)`, meaning "order by ?x ascending".
- A pair, `(desc ?x)`, meaning "order by ?x descending".
Values will be ordered in this sequence for asc, and in reverse for desc:
1. Entity IDs, in ascending numerical order.
2. Booleans, false then true.
3. Timestamps, in ascending numerical order.
4. Longs and doubles, intermixed, in ascending numerical order.
5. Strings, in ascending lexicographic order.
6. Keywords, in ascending lexicographic order, considering the entire
ns/name pair as a single string separated by '/'.
Subcommits:
Pre: make bound_value public.
Pre: generalize ErrorKind::UnboundVariable for use in order.
Part 1: parse (direction, var) pairs.
Part 2: parse :order clause into FindQuery.
Part 3: include order variables in algebrized query.
We add order variables to :with, so we can reuse its type tag projection
logic, and so that we can phrase ordering in terms of variables rather
than datoms columns.
Part 4: produce SQL for order clauses.
2017-04-14 23:10:56 +00:00
// Unknown type.
2017-04-19 23:16:19 +00:00
let query = r # "[:find ?x :with ?y :where [?x _ ?y] :order ?y ?x]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
Implement :order. (#415) (#416) r=nalexander
This adds an `:order` keyword to `:find`.
If present, the results of the query will be an ordered set, rather than
an unordered set; rows will appear in an ordered defined by each
`:order` entry.
Each can be one of three things:
- A var, `?x`, meaning "order by ?x ascending".
- A pair, `(asc ?x)`, meaning "order by ?x ascending".
- A pair, `(desc ?x)`, meaning "order by ?x descending".
Values will be ordered in this sequence for asc, and in reverse for desc:
1. Entity IDs, in ascending numerical order.
2. Booleans, false then true.
3. Timestamps, in ascending numerical order.
4. Longs and doubles, intermixed, in ascending numerical order.
5. Strings, in ascending lexicographic order.
6. Keywords, in ascending lexicographic order, considering the entire
ns/name pair as a single string separated by '/'.
Subcommits:
Pre: make bound_value public.
Pre: generalize ErrorKind::UnboundVariable for use in order.
Part 1: parse (direction, var) pairs.
Part 2: parse :order clause into FindQuery.
Part 3: include order variables in algebrized query.
We add order variables to :with, so we can reuse its type tag projection
logic, and so that we can phrase ordering in terms of variables rather
than datoms columns.
Part 4: produce SQL for order clauses.
2017-04-14 23:10:56 +00:00
assert_eq! ( sql , " SELECT DISTINCT `all_datoms00`.e AS `?x`, `all_datoms00`.v AS `?y`, \
` all_datoms00 ` . value_type_tag AS ` ? y_value_type_tag ` \
FROM ` all_datoms ` AS ` all_datoms00 ` \
ORDER BY ` ? y_value_type_tag ` ASC , ` ? y ` ASC , ` ? x ` ASC " );
2017-04-17 20:29:40 +00:00
assert_eq! ( args , vec! [ ] ) ;
2017-04-19 23:16:19 +00:00
}
2017-04-24 21:15:26 +00:00
#[ test ]
fn test_complex_nested_or_join_type_projection ( ) {
let mut schema = Schema ::default ( ) ;
2018-05-11 16:52:17 +00:00
associate_ident ( & mut schema , Keyword ::namespaced ( " page " , " title " ) , 98 ) ;
2017-04-24 21:15:26 +00:00
add_attribute ( & mut schema , 98 , Attribute {
value_type : ValueType ::String ,
.. Default ::default ( )
} ) ;
let input = r #" [:find [?y]
:where
( or
( or
[ _ :page / title ? y ] )
( or
[ _ :page / title ? y ] ) ) ] " #;
let SQLQuery { sql , args } = translate ( & schema , input ) ;
assert_eq! ( sql , " SELECT `c00`.`?y` AS `?y` \
FROM ( SELECT ` datoms00 ` . v AS ` ? y ` \
FROM ` datoms ` AS ` datoms00 ` \
WHERE ` datoms00 ` . a = 98 \
UNION \
SELECT ` datoms01 ` . v AS ` ? y ` \
FROM ` datoms ` AS ` datoms01 ` \
WHERE ` datoms01 ` . a = 98 ) \
AS ` c00 ` \
LIMIT 1 " );
assert_eq! ( args , vec! [ ] ) ;
2017-04-29 03:11:55 +00:00
}
2017-04-26 22:50:17 +00:00
#[ test ]
fn test_ground_scalar ( ) {
let schema = prepopulated_schema ( ) ;
// Verify that we accept inline constants.
let query = r # "[:find ?x . :where [(ground "yyy") ?x]]"# ;
2018-03-06 17:01:20 +00:00
let constant = translate_to_constant ( & schema , query ) ;
assert_eq! ( constant . project_without_rows ( ) . unwrap ( )
. into_scalar ( ) . unwrap ( ) ,
2018-04-24 22:08:38 +00:00
Some ( TypedValue ::typed_string ( " yyy " ) . into ( ) ) ) ;
2017-04-26 22:50:17 +00:00
// Verify that we accept bound input constants.
let query = r # "[:find ?x . :in ?v :where [(ground ?v) ?x]]"# ;
2018-04-25 21:23:27 +00:00
let inputs = QueryInputs ::with_value_sequence ( vec! [ ( Variable ::from_valid_name ( " ?v " ) , " aaa " . into ( ) ) ] ) ;
2018-03-06 17:01:20 +00:00
let constant = translate_with_inputs_to_constant ( & schema , query , inputs ) ;
assert_eq! ( constant . project_without_rows ( ) . unwrap ( )
. into_scalar ( ) . unwrap ( ) ,
2018-04-24 22:08:38 +00:00
Some ( TypedValue ::typed_string ( " aaa " ) . into ( ) ) ) ;
2017-04-26 22:50:17 +00:00
}
#[ test ]
fn test_ground_tuple ( ) {
let schema = prepopulated_schema ( ) ;
// Verify that we accept inline constants.
let query = r # "[:find ?x ?y :where [(ground [1 "yyy"]) [?x ?y]]]"# ;
2018-03-06 17:01:20 +00:00
let constant = translate_to_constant ( & schema , query ) ;
assert_eq! ( constant . project_without_rows ( ) . unwrap ( )
. into_rel ( ) . unwrap ( ) ,
2018-04-24 22:04:00 +00:00
vec! [ vec! [ TypedValue ::Long ( 1 ) , TypedValue ::typed_string ( " yyy " ) ] ] . into ( ) ) ;
2017-04-26 22:50:17 +00:00
// Verify that we accept bound input constants.
let query = r # "[:find [?x ?y] :in ?u ?v :where [(ground [?u ?v]) [?x ?y]]]"# ;
let inputs = QueryInputs ::with_value_sequence ( vec! [ ( Variable ::from_valid_name ( " ?u " ) , TypedValue ::Long ( 2 ) ) ,
2018-04-25 21:23:27 +00:00
( Variable ::from_valid_name ( " ?v " ) , " aaa " . into ( ) ) , ] ) ;
2018-03-06 17:01:20 +00:00
let constant = translate_with_inputs_to_constant ( & schema , query , inputs ) ;
assert_eq! ( constant . project_without_rows ( ) . unwrap ( )
. into_tuple ( ) . unwrap ( ) ,
2018-04-24 22:08:38 +00:00
Some ( vec! [ TypedValue ::Long ( 2 ) . into ( ) , TypedValue ::typed_string ( " aaa " ) . into ( ) ] ) ) ;
2018-03-06 17:01:20 +00:00
2017-04-26 22:50:17 +00:00
// TODO: treat 2 as an input variable that could be bound late, rather than eagerly binding it.
2018-03-06 17:01:20 +00:00
// In that case the query wouldn't be constant, and would look more like:
// let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs);
// assert_eq!(sql, "SELECT 2 AS `?x`, $v0 AS `?y` LIMIT 1");
// assert_eq!(args, vec![make_arg("$v0", "aaa"),]);
2017-04-26 22:50:17 +00:00
}
#[ test ]
fn test_ground_coll ( ) {
let schema = prepopulated_schema ( ) ;
// Verify that we accept inline constants.
let query = r # "[:find ?x :where [(ground ["xxx" "yyy"]) [?x ...]]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `c00`.`?x` AS `?x` FROM \
( SELECT 0 AS ` ? x ` WHERE 0 UNION ALL VALUES ( $v0 ) , ( $v1 ) ) AS ` c00 ` " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " xxx " ) ,
make_arg ( " $v1 " , " yyy " ) ] ) ;
// Verify that we accept bound input constants.
let query = r # "[:find ?x :in ?u ?v :where [(ground [?u ?v]) [?x ...]]]"# ;
let inputs = QueryInputs ::with_value_sequence ( vec! [ ( Variable ::from_valid_name ( " ?u " ) , TypedValue ::Long ( 2 ) ) ,
( Variable ::from_valid_name ( " ?v " ) , TypedValue ::Long ( 3 ) ) , ] ) ;
let SQLQuery { sql , args } = translate_with_inputs ( & schema , query , inputs ) ;
// TODO: treat 2 and 3 as input variables that could be bound late, rather than eagerly binding.
assert_eq! ( sql , " SELECT DISTINCT `c00`.`?x` AS `?x` FROM \
( SELECT 0 AS ` ? x ` WHERE 0 UNION ALL VALUES ( 2 ) , ( 3 ) ) AS ` c00 ` " );
assert_eq! ( args , vec! [ ] ) ;
}
#[ test ]
fn test_ground_rel ( ) {
let schema = prepopulated_schema ( ) ;
// Verify that we accept inline constants.
let query = r # "[:find ?x ?y :where [(ground [[1 "xxx"] [2 "yyy"]]) [[?x ?y]]]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `c00`.`?x` AS `?x`, `c00`.`?y` AS `?y` FROM \
( SELECT 0 AS ` ? x ` , 0 AS ` ? y ` WHERE 0 UNION ALL VALUES ( 1 , $v0 ) , ( 2 , $v1 ) ) AS ` c00 ` " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " xxx " ) ,
make_arg ( " $v1 " , " yyy " ) ] ) ;
// Verify that we accept bound input constants.
let query = r # "[:find ?x ?y :in ?u ?v :where [(ground [[?u 1] [?v 2]]) [[?x ?y]]]]"# ;
let inputs = QueryInputs ::with_value_sequence ( vec! [ ( Variable ::from_valid_name ( " ?u " ) , TypedValue ::Long ( 3 ) ) ,
( Variable ::from_valid_name ( " ?v " ) , TypedValue ::Long ( 4 ) ) , ] ) ;
let SQLQuery { sql , args } = translate_with_inputs ( & schema , query , inputs ) ;
// TODO: treat 3 and 4 as input variables that could be bound late, rather than eagerly binding.
assert_eq! ( sql , " SELECT DISTINCT `c00`.`?x` AS `?x`, `c00`.`?y` AS `?y` FROM \
( SELECT 0 AS ` ? x ` , 0 AS ` ? y ` WHERE 0 UNION ALL VALUES ( 3 , 1 ) , ( 4 , 2 ) ) AS ` c00 ` " );
assert_eq! ( args , vec! [ ] ) ;
}
#[ test ]
fn test_compound_with_ground ( ) {
let schema = prepopulated_schema ( ) ;
// Verify that we can use the resulting CCs as children in compound CCs.
let query = r #" [:find ?x :where (or [(ground " yyy " ) ?x]
[ ( ground " zzz " ) ? x ] ) ] " #;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
// This is confusing because the computed tables (like `c00`) are numbered sequentially in each
// arm of the `or` rather than numbered globally. But SQLite scopes the names correctly, so it
// works. In the future, we might number the computed tables globally to make this more clear.
assert_eq! ( sql , " SELECT DISTINCT `c00`.`?x` AS `?x` FROM ( \
SELECT $v0 AS ` ? x ` UNION \
SELECT $v1 AS ` ? x ` ) AS ` c00 ` " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " yyy " ) ,
make_arg ( " $v1 " , " zzz " ) , ] ) ;
// Verify that we can use ground to constrain the bindings produced by earlier clauses.
let query = r # "[:find ?x . :where [_ :foo/bar ?x] [(ground "yyy") ?x]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT $v0 AS `?x` FROM `datoms` AS `datoms00` \
WHERE ` datoms00 ` . a = 99 AND ` datoms00 ` . v = $v0 LIMIT 1 " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " yyy " ) ] ) ;
// Verify that we can further constrain the bindings produced by our clause.
let query = r # "[:find ?x . :where [(ground "yyy") ?x] [_ :foo/bar ?x]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT $v0 AS `?x` FROM `datoms` AS `datoms00` \
WHERE ` datoms00 ` . a = 99 AND ` datoms00 ` . v = $v0 LIMIT 1 " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " yyy " ) ] ) ;
}
#[ test ]
fn test_unbound_attribute_with_ground_entity ( ) {
let query = r # "[:find ?x ?v :where [?x _ ?v] (not [(ground 17) ?x])]"# ;
let schema = prepopulated_schema ( ) ;
let SQLQuery { sql , .. } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `all_datoms00`.e AS `?x`, \
` all_datoms00 ` . v AS ` ? v ` , \
` all_datoms00 ` . value_type_tag AS ` ? v_value_type_tag ` \
FROM ` all_datoms ` AS ` all_datoms00 ` \
WHERE NOT EXISTS ( SELECT 1 WHERE ` all_datoms00 ` . e = 17 ) " );
}
#[ test ]
fn test_unbound_attribute_with_ground ( ) {
2017-06-09 14:40:32 +00:00
let query = r # "[:find ?x ?v :where [?x _ ?v] (not [(ground 17) ?v])]"# ;
2017-04-26 22:50:17 +00:00
let schema = prepopulated_schema ( ) ;
let SQLQuery { sql , .. } = translate ( & schema , query ) ;
2017-06-09 14:40:32 +00:00
assert_eq! ( sql , " SELECT DISTINCT `all_datoms00`.e AS `?x`, \
` all_datoms00 ` . v AS ` ? v ` , \
` all_datoms00 ` . value_type_tag AS ` ? v_value_type_tag ` \
FROM ` all_datoms ` AS ` all_datoms00 ` \
WHERE NOT EXISTS ( SELECT 1 WHERE ` all_datoms00 ` . v = 17 AND \
2018-01-29 22:29:16 +00:00
( ` all_datoms00 ` . value_type_tag = 5 ) ) " );
2017-04-26 22:50:17 +00:00
}
#[ test ]
fn test_not_with_ground ( ) {
let mut schema = prepopulated_schema ( ) ;
2018-05-11 16:52:17 +00:00
associate_ident ( & mut schema , Keyword ::namespaced ( " db " , " valueType " ) , 7 ) ;
associate_ident ( & mut schema , Keyword ::namespaced ( " db.type " , " ref " ) , 23 ) ;
associate_ident ( & mut schema , Keyword ::namespaced ( " db.type " , " bool " ) , 28 ) ;
associate_ident ( & mut schema , Keyword ::namespaced ( " db.type " , " instant " ) , 29 ) ;
2017-04-26 22:50:17 +00:00
add_attribute ( & mut schema , 7 , Attribute {
value_type : ValueType ::Ref ,
multival : false ,
.. Default ::default ( )
} ) ;
// Scalar.
// TODO: this kind of simple `not` should be implemented without the subquery. #476.
let query = r # "[:find ?x :where [?x :db/valueType ?v] (not [(ground :db.type/instant) ?v])]"# ;
let SQLQuery { sql , .. } = translate ( & schema , query ) ;
assert_eq! ( sql ,
" SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 7 AND NOT \
EXISTS ( SELECT 1 WHERE ` datoms00 ` . v = 29 ) " );
// Coll.
// TODO: we can generate better SQL for this, too. #476.
let query = r # "[:find ?x :where [?x :db/valueType ?v] (not [(ground [:db.type/bool :db.type/instant]) [?v ...]])]"# ;
let SQLQuery { sql , .. } = translate ( & schema , query ) ;
assert_eq! ( sql ,
" SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` \
WHERE ` datoms00 ` . a = 7 AND NOT EXISTS \
( SELECT 1 FROM ( SELECT 0 AS ` ? v ` WHERE 0 UNION ALL VALUES ( 28 ) , ( 29 ) ) AS ` c00 ` \
WHERE ` datoms00 ` . v = ` c00 ` . ` ? v ` ) " );
}
2017-06-12 21:19:35 +00:00
#[ test ]
fn test_fulltext ( ) {
let schema = prepopulated_typed_schema ( ValueType ::Double ) ;
let query = r # "[:find ?entity ?value ?tx ?score :where [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx ?score]]]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `datoms01`.e AS `?entity`, \
` fulltext_values00 ` . text AS ` ? value ` , \
` datoms01 ` . tx AS ` ? tx ` , \
0e0 AS ` ? score ` \
FROM ` fulltext_values ` AS ` fulltext_values00 ` , \
` datoms ` AS ` datoms01 ` \
WHERE ` datoms01 ` . a = 100 \
AND ` datoms01 ` . v = ` fulltext_values00 ` . rowid \
AND ` fulltext_values00 ` . text MATCH $v0 " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " needle " ) , ] ) ;
let query = r # "[:find ?entity ?value ?tx :where [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx ?score]]]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
// Observe that the computed table isn't dropped, even though `?score` isn't bound in the final conjoining clause.
assert_eq! ( sql , " SELECT DISTINCT `datoms01`.e AS `?entity`, \
` fulltext_values00 ` . text AS ` ? value ` , \
` datoms01 ` . tx AS ` ? tx ` \
FROM ` fulltext_values ` AS ` fulltext_values00 ` , \
` datoms ` AS ` datoms01 ` \
WHERE ` datoms01 ` . a = 100 \
AND ` datoms01 ` . v = ` fulltext_values00 ` . rowid \
AND ` fulltext_values00 ` . text MATCH $v0 " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " needle " ) , ] ) ;
let query = r # "[:find ?entity ?value ?tx :where [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx _]]]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
// Observe that the computed table isn't included at all when `?score` isn't bound.
assert_eq! ( sql , " SELECT DISTINCT `datoms01`.e AS `?entity`, \
` fulltext_values00 ` . text AS ` ? value ` , \
` datoms01 ` . tx AS ` ? tx ` \
FROM ` fulltext_values ` AS ` fulltext_values00 ` , \
` datoms ` AS ` datoms01 ` \
WHERE ` datoms01 ` . a = 100 \
AND ` datoms01 ` . v = ` fulltext_values00 ` . rowid \
AND ` fulltext_values00 ` . text MATCH $v0 " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " needle " ) , ] ) ;
let query = r # "[:find ?entity ?value ?tx :where [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx ?score]]] [?entity :foo/bar ?score]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `datoms01`.e AS `?entity`, \
` fulltext_values00 ` . text AS ` ? value ` , \
` datoms01 ` . tx AS ` ? tx ` \
FROM ` fulltext_values ` AS ` fulltext_values00 ` , \
` datoms ` AS ` datoms01 ` , \
` datoms ` AS ` datoms02 ` \
WHERE ` datoms01 ` . a = 100 \
AND ` datoms01 ` . v = ` fulltext_values00 ` . rowid \
AND ` fulltext_values00 ` . text MATCH $v0 \
AND ` datoms02 ` . a = 99 \
AND ` datoms02 ` . v = 0e0 \
AND ` datoms01 ` . e = ` datoms02 ` . e " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " needle " ) , ] ) ;
let query = r # "[:find ?entity ?value ?tx :where [?entity :foo/bar ?score] [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx ?score]]]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?entity`, \
` fulltext_values01 ` . text AS ` ? value ` , \
` datoms02 ` . tx AS ` ? tx ` \
FROM ` datoms ` AS ` datoms00 ` , \
` fulltext_values ` AS ` fulltext_values01 ` , \
` datoms ` AS ` datoms02 ` \
WHERE ` datoms00 ` . a = 99 \
AND ` datoms02 ` . a = 100 \
AND ` datoms02 ` . v = ` fulltext_values01 ` . rowid \
AND ` fulltext_values01 ` . text MATCH $v0 \
AND ` datoms00 ` . v = 0e0 \
AND ` datoms00 ` . e = ` datoms02 ` . e " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " needle " ) , ] ) ;
}
#[ test ]
fn test_fulltext_inputs ( ) {
let schema = prepopulated_typed_schema ( ValueType ::String ) ;
// Bind ?entity. We expect the output to collide.
let query = r #" [:find ?val
:in ? entity
:where [ ( fulltext $ :foo / fts " hello " ) [ [ ? entity ? val _ _ ] ] ] ] " #;
let mut types = BTreeMap ::default ( ) ;
types . insert ( Variable ::from_valid_name ( " ?entity " ) , ValueType ::Ref ) ;
let inputs = QueryInputs ::new ( types , BTreeMap ::default ( ) ) . expect ( " valid inputs " ) ;
// Without binding the value. q_once will err if you try this!
let SQLQuery { sql , args } = translate_with_inputs ( & schema , query , inputs ) ;
assert_eq! ( sql , " SELECT DISTINCT `fulltext_values00`.text AS `?val` \
FROM \
` fulltext_values ` AS ` fulltext_values00 ` , \
` datoms ` AS ` datoms01 ` \
WHERE ` datoms01 ` . a = 100 \
AND ` datoms01 ` . v = ` fulltext_values00 ` . rowid \
AND ` fulltext_values00 ` . text MATCH $v0 " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " hello " ) , ] ) ;
// With the value bound.
let inputs = QueryInputs ::with_value_sequence ( vec! [ ( Variable ::from_valid_name ( " ?entity " ) , TypedValue ::Ref ( 111 ) ) ] ) ;
let SQLQuery { sql , args } = translate_with_inputs ( & schema , query , inputs ) ;
assert_eq! ( sql , " SELECT DISTINCT `fulltext_values00`.text AS `?val` \
FROM \
` fulltext_values ` AS ` fulltext_values00 ` , \
` datoms ` AS ` datoms01 ` \
WHERE ` datoms01 ` . a = 100 \
AND ` datoms01 ` . v = ` fulltext_values00 ` . rowid \
AND ` fulltext_values00 ` . text MATCH $v0 \
AND ` datoms01 ` . e = 111 " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " hello " ) , ] ) ;
// Same again, but retrieving the entity.
let query = r #" [:find ?entity .
:in ? entity
:where [ ( fulltext $ :foo / fts " hello " ) [ [ ? entity _ _ ] ] ] ] " #;
let inputs = QueryInputs ::with_value_sequence ( vec! [ ( Variable ::from_valid_name ( " ?entity " ) , TypedValue ::Ref ( 111 ) ) ] ) ;
let SQLQuery { sql , args } = translate_with_inputs ( & schema , query , inputs ) ;
assert_eq! ( sql , " SELECT 111 AS `?entity` FROM \
` fulltext_values ` AS ` fulltext_values00 ` , \
` datoms ` AS ` datoms01 ` \
WHERE ` datoms01 ` . a = 100 \
AND ` datoms01 ` . v = ` fulltext_values00 ` . rowid \
AND ` fulltext_values00 ` . text MATCH $v0 \
AND ` datoms01 ` . e = 111 \
LIMIT 1 " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " hello " ) , ] ) ;
// A larger pattern.
let query = r #" [:find ?entity ?value ?friend
:in ? entity
:where
[ ( fulltext $ :foo / fts " hello " ) [ [ ? entity ? value ] ] ]
[ ? entity :foo / bar ? friend ] ] " #;
let inputs = QueryInputs ::with_value_sequence ( vec! [ ( Variable ::from_valid_name ( " ?entity " ) , TypedValue ::Ref ( 121 ) ) ] ) ;
let SQLQuery { sql , args } = translate_with_inputs ( & schema , query , inputs ) ;
assert_eq! ( sql , " SELECT DISTINCT 121 AS `?entity`, \
` fulltext_values00 ` . text AS ` ? value ` , \
` datoms02 ` . v AS ` ? friend ` \
FROM \
` fulltext_values ` AS ` fulltext_values00 ` , \
` datoms ` AS ` datoms01 ` , \
` datoms ` AS ` datoms02 ` \
WHERE ` datoms01 ` . a = 100 \
AND ` datoms01 ` . v = ` fulltext_values00 ` . rowid \
AND ` fulltext_values00 ` . text MATCH $v0 \
AND ` datoms01 ` . e = 121 \
AND ` datoms02 ` . e = 121 \
AND ` datoms02 ` . a = 99 " );
assert_eq! ( args , vec! [ make_arg ( " $v0 " , " hello " ) , ] ) ;
}
2017-06-14 23:17:25 +00:00
#[ test ]
fn test_instant_range ( ) {
let schema = prepopulated_typed_schema ( ValueType ::Instant ) ;
let query = r #" [:find ?e
:where
[ ? e :foo / bar ? t ]
[ ( > ? t #inst " 2017-06-16T00:56:41.257Z " ) ] ] " #;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `datoms00`.e AS `?e` \
FROM \
` datoms ` AS ` datoms00 ` \
WHERE ` datoms00 ` . a = 99 \
AND ` datoms00 ` . v > 1497574601257000 " );
assert_eq! ( args , vec! [ ] ) ;
}
2018-03-12 22:18:50 +00:00
#[ test ]
fn test_project_aggregates ( ) {
let schema = prepopulated_typed_schema ( ValueType ::Long ) ;
let query = r #" [:find ?e (max ?t)
:where
[ ? e :foo / bar ? t ] ] " #;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
// No outer DISTINCT: we aggregate or group by every variable.
2018-06-01 21:17:31 +00:00
assert_eq! ( sql , " SELECT * \
2018-03-12 22:18:50 +00:00
FROM \
2018-06-01 21:17:31 +00:00
( SELECT ` ? e ` AS ` ? e ` , max ( ` ? t ` ) AS ` ( max ? t ) ` \
FROM \
( SELECT DISTINCT \
` datoms00 ` . e AS ` ? e ` , \
` datoms00 ` . v AS ` ? t ` \
FROM ` datoms ` AS ` datoms00 ` \
WHERE ` datoms00 ` . a = 99 ) \
GROUP BY ` ? e ` ) \
WHERE ` ( max ? t ) ` IS NOT NULL " );
2018-03-12 22:18:50 +00:00
assert_eq! ( args , vec! [ ] ) ;
let query = r #" [:find (max ?t)
:with ? e
:where
[ ? e :foo / bar ? t ] ] " #;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
2018-06-01 21:17:31 +00:00
assert_eq! ( sql , " SELECT * \
FROM \
( SELECT max ( ` ? t ` ) AS ` ( max ? t ) ` \
FROM \
( SELECT DISTINCT \
` datoms00 ` . v AS ` ? t ` , \
` datoms00 ` . e AS ` ? e ` \
FROM ` datoms ` AS ` datoms00 ` \
WHERE ` datoms00 ` . a = 99 ) \
) \
WHERE ` ( max ? t ) ` IS NOT NULL " );
assert_eq! ( args , vec! [ ] ) ;
// ORDER BY lifted to outer query if there is no LIMIT.
let query = r #" [:find (max ?x)
:with ? e
:where
[ ? e ? a ? t ]
[ ? t :foo / bar ? x ]
:order ? a ] " #;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT * \
FROM \
( SELECT max ( ` ? x ` ) AS ` ( max ? x ) ` , ` ? a ` AS ` ? a ` \
FROM \
( SELECT DISTINCT \
` datoms01 ` . v AS ` ? x ` , \
` datoms00 ` . a AS ` ? a ` , \
` datoms00 ` . e AS ` ? e ` \
FROM ` datoms ` AS ` datoms00 ` , ` datoms ` AS ` datoms01 ` \
WHERE ` datoms01 ` . a = 99 AND ` datoms00 ` . v = ` datoms01 ` . e ) \
GROUP BY ` ? a ` ) \
WHERE ` ( max ? x ) ` IS NOT NULL \
ORDER BY ` ? a ` ASC " );
assert_eq! ( args , vec! [ ] ) ;
// ORDER BY duplicated in outer query if there is a LIMIT.
let query = r #" [:find (max ?x)
:with ? e
:where
[ ? e ? a ? t ]
[ ? t :foo / bar ? x ]
:order ( desc ? a )
:limit 10 ] " #;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT * \
FROM \
( SELECT max ( ` ? x ` ) AS ` ( max ? x ) ` , ` ? a ` AS ` ? a ` \
FROM \
( SELECT DISTINCT \
` datoms01 ` . v AS ` ? x ` , \
` datoms00 ` . a AS ` ? a ` , \
` datoms00 ` . e AS ` ? e ` \
FROM ` datoms ` AS ` datoms00 ` , ` datoms ` AS ` datoms01 ` \
WHERE ` datoms01 ` . a = 99 AND ` datoms00 ` . v = ` datoms01 ` . e ) \
GROUP BY ` ? a ` \
ORDER BY ` ? a ` DESC \
LIMIT 10 ) \
WHERE ` ( max ? x ) ` IS NOT NULL \
ORDER BY ` ? a ` DESC " );
assert_eq! ( args , vec! [ ] ) ;
// No outer SELECT * for non-nullable aggregates.
let query = r #" [:find (count ?t)
:with ? e
:where
[ ? e :foo / bar ? t ] ] " #;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT count(`?t`) AS `(count ?t)` \
2018-03-12 22:18:50 +00:00
FROM \
( SELECT DISTINCT \
` datoms00 ` . v AS ` ? t ` , \
` datoms00 ` . e AS ` ? e ` \
FROM ` datoms ` AS ` datoms00 ` \
WHERE ` datoms00 ` . a = 99 ) " );
assert_eq! ( args , vec! [ ] ) ;
}
2018-04-05 17:49:06 +00:00
2018-06-01 21:17:31 +00:00
#[ test ]
fn test_project_the ( ) {
let schema = prepopulated_typed_schema ( ValueType ::Long ) ;
let query = r #" [:find (the ?e) (max ?t)
:where
[ ? e :foo / bar ? t ] ] " #;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
// We shouldn't NULL-check (the).
assert_eq! ( sql , " SELECT * \
FROM \
( SELECT ` ? e ` AS ` ? e ` , max ( ` ? t ` ) AS ` ( max ? t ) ` \
FROM \
( SELECT DISTINCT \
` datoms00 ` . e AS ` ? e ` , \
` datoms00 ` . v AS ` ? t ` \
FROM ` datoms ` AS ` datoms00 ` \
WHERE ` datoms00 ` . a = 99 ) ) \
WHERE ` ( max ? t ) ` IS NOT NULL " );
assert_eq! ( args , vec! [ ] ) ;
}
2018-04-05 17:49:06 +00:00
#[ test ]
fn test_tx_before_and_after ( ) {
let schema = prepopulated_typed_schema ( ValueType ::Long ) ;
let query = r # "[:find ?x :where [?x _ _ ?tx] [(tx-after ?tx 12345)]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT \
` datoms00 ` . e AS ` ? x ` \
FROM ` datoms ` AS ` datoms00 ` \
WHERE ` datoms00 ` . tx > 12345 " );
assert_eq! ( args , vec! [ ] ) ;
let query = r # "[:find ?x :where [?x _ _ ?tx] [(tx-before ?tx 12345)]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT \
` datoms00 ` . e AS ` ? x ` \
FROM ` datoms ` AS ` datoms00 ` \
WHERE ` datoms00 ` . tx < 12345 " );
assert_eq! ( args , vec! [ ] ) ;
}
2018-04-16 21:08:00 +00:00
#[ test ]
fn test_tx_ids ( ) {
let mut schema = prepopulated_typed_schema ( ValueType ::Double ) ;
2018-05-11 16:52:17 +00:00
associate_ident ( & mut schema , Keyword ::namespaced ( " db " , " txInstant " ) , 101 ) ;
2018-04-16 21:08:00 +00:00
add_attribute ( & mut schema , 101 , Attribute {
value_type : ValueType ::Instant ,
multival : false ,
index : true ,
.. Default ::default ( )
} ) ;
let query = r # "[:find ?tx :where [(tx-ids $ 1000 2000) [[?tx]]]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `transactions00`.tx AS `?tx` \
FROM ` transactions ` AS ` transactions00 ` \
WHERE 1000 < = ` transactions00 ` . tx \
AND ` transactions00 ` . tx < 2000 " );
assert_eq! ( args , vec! [ ] ) ;
// This is rather artificial but verifies that binding the arguments to (tx-ids) works.
let query = r # "[:find ?tx :where [?first :db/txInstant #inst "2016-01-01T11:00:00.000Z"] [?last :db/txInstant #inst "2017-01-01T11:00:00.000Z"] [(tx-ids $ ?first ?last) [?tx ...]]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `transactions02`.tx AS `?tx` \
FROM ` datoms ` AS ` datoms00 ` , \
` datoms ` AS ` datoms01 ` , \
` transactions ` AS ` transactions02 ` \
WHERE ` datoms00 ` . a = 101 \
AND ` datoms00 ` . v = 1451646000000000 \
AND ` datoms01 ` . a = 101 \
AND ` datoms01 ` . v = 1483268400000000 \
AND ` datoms00 ` . e < = ` transactions02 ` . tx \
AND ` transactions02 ` . tx < ` datoms01 ` . e " );
assert_eq! ( args , vec! [ ] ) ;
// In practice the following query would be inefficient because of the filter on all_datoms.tx,
// but that is what (tx-data) is for.
let query = r # "[:find ?e ?a ?v ?tx :where [(tx-ids $ 1000 2000) [[?tx]]] [?e ?a ?v ?tx]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `all_datoms01`.e AS `?e`, \
` all_datoms01 ` . a AS ` ? a ` , \
` all_datoms01 ` . v AS ` ? v ` , \
` all_datoms01 ` . value_type_tag AS ` ? v_value_type_tag ` , \
` transactions00 ` . tx AS ` ? tx ` \
FROM ` transactions ` AS ` transactions00 ` , \
` all_datoms ` AS ` all_datoms01 ` \
WHERE 1000 < = ` transactions00 ` . tx \
AND ` transactions00 ` . tx < 2000 \
AND ` transactions00 ` . tx = ` all_datoms01 ` . tx " );
assert_eq! ( args , vec! [ ] ) ;
}
#[ test ]
fn test_tx_data ( ) {
let schema = prepopulated_typed_schema ( ValueType ::Double ) ;
let query = r # "[:find ?e ?a ?v ?tx ?added :where [(tx-data $ 1000) [[?e ?a ?v ?tx ?added]]]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `transactions00`.e AS `?e`, \
` transactions00 ` . a AS ` ? a ` , \
` transactions00 ` . v AS ` ? v ` , \
` transactions00 ` . value_type_tag AS ` ? v_value_type_tag ` , \
` transactions00 ` . tx AS ` ? tx ` , \
` transactions00 ` . added AS ` ? added ` \
FROM ` transactions ` AS ` transactions00 ` \
WHERE ` transactions00 ` . tx = 1000 " );
assert_eq! ( args , vec! [ ] ) ;
// Ensure that we don't project columns that we don't need, even if they are bound to named
// variables or to placeholders.
let query = r # "[:find [?a ?v ?added] :where [(tx-data $ 1000) [[?e ?a ?v _ ?added]]]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT `transactions00`.a AS `?a`, \
` transactions00 ` . v AS ` ? v ` , \
` transactions00 ` . value_type_tag AS ` ? v_value_type_tag ` , \
` transactions00 ` . added AS ` ? added ` \
FROM ` transactions ` AS ` transactions00 ` \
WHERE ` transactions00 ` . tx = 1000 \
LIMIT 1 " );
assert_eq! ( args , vec! [ ] ) ;
// This is awkward since the transactions table is queried twice, once to list transaction IDs
// and a second time to extract data. https://github.com/mozilla/mentat/issues/644 tracks
// improving this, perhaps by optimizing certain combinations of functions and bindings.
let query = r # "[:find ?e ?a ?v ?tx ?added :where [(tx-ids $ 1000 2000) [[?tx]]] [(tx-data $ ?tx) [[?e ?a ?v _ ?added]]]]"# ;
let SQLQuery { sql , args } = translate ( & schema , query ) ;
assert_eq! ( sql , " SELECT DISTINCT `transactions01`.e AS `?e`, \
` transactions01 ` . a AS ` ? a ` , \
` transactions01 ` . v AS ` ? v ` , \
` transactions01 ` . value_type_tag AS ` ? v_value_type_tag ` , \
` transactions00 ` . tx AS ` ? tx ` , \
` transactions01 ` . added AS ` ? added ` \
FROM ` transactions ` AS ` transactions00 ` , \
` transactions ` AS ` transactions01 ` \
WHERE 1000 < = ` transactions00 ` . tx \
AND ` transactions00 ` . tx < 2000 \
AND ` transactions01 ` . tx = ` transactions00 ` . tx " );
assert_eq! ( args , vec! [ ] ) ;
}