2017-02-16 23:39:19 +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.
2020-01-14 15:46:21 +00:00
#[ macro_use ]
extern crate mentat_core ;
2018-08-08 17:35:06 +00:00
extern crate core_traits ;
2018-08-08 18:23:07 +00:00
extern crate edn ;
2017-03-06 22:40:10 +00:00
extern crate mentat_query_algebrizer ;
extern crate mentat_sql ;
2020-01-14 15:46:21 +00:00
extern crate sql_traits ;
2017-02-16 23:39:19 +00:00
2017-04-28 09:44:11 +00:00
use std ::boxed ::Box ;
2018-08-08 17:35:06 +00:00
2020-01-14 15:46:21 +00:00
use core_traits ::{ Entid , TypedValue , ValueType } ;
2018-08-08 17:35:06 +00:00
2020-01-14 15:46:21 +00:00
use mentat_core ::SQLTypeAffinity ;
2017-02-22 03:57:00 +00:00
2020-01-14 15:46:21 +00:00
use edn ::query ::{ Direction , Limit , Variable } ;
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
2017-02-16 23:39:19 +00:00
use mentat_query_algebrizer ::{
2020-01-14 15:46:21 +00:00
Column , OrderBy , QualifiedAlias , QueryValue , SourceAlias , TableAlias , VariableColumn ,
2017-02-16 23:39:19 +00:00
} ;
2020-01-14 15:46:21 +00:00
use sql_traits ::errors ::{ BuildQueryResult , SQLError } ;
2018-08-08 20:19:08 +00:00
2020-01-14 15:46:21 +00:00
use mentat_sql ::{ QueryBuilder , QueryFragment , SQLQuery , SQLiteQueryBuilder } ;
2017-02-16 23:39:19 +00:00
//---------------------------------------------------------
// A Mentat-focused representation of a SQL query.
2017-02-22 03:57:00 +00:00
/// One of the things that can appear in a projection or a constraint. Note that we use
/// `TypedValue` here; it's not pure SQL, but it avoids us having to concern ourselves at this
/// point with the translation between a `TypedValue` and the storage-layer representation.
///
/// Eventually we might allow different translations by providing a different `QueryBuilder`
/// implementation for each storage backend. Passing `TypedValue`s here allows for that.
pub enum ColumnOrExpression {
2017-02-16 23:39:19 +00:00
Column ( QualifiedAlias ) ,
2018-03-12 22:18:50 +00:00
ExistingColumn ( Name ) ,
2020-01-14 15:46:21 +00:00
Entid ( Entid ) , // Because it's so common.
Integer ( i32 ) , // We use these for type codes etc.
2017-03-16 19:23:48 +00:00
Long ( i64 ) ,
2017-02-22 03:57:00 +00:00
Value ( TypedValue ) ,
2018-06-01 21:17:31 +00:00
// Some aggregates (`min`, `max`, `avg`) can be over 0 rows, and therefore can be `NULL`; that
// needs special treatment.
2020-01-14 15:46:21 +00:00
NullableAggregate ( Box < Expression > , ValueType ) , // Track the return type.
Expression ( Box < Expression > , ValueType ) , // Track the return type.
2018-03-12 22:18:50 +00:00
}
pub enum Expression {
2020-01-14 15:46:21 +00:00
Unary {
sql_op : & 'static str ,
arg : ColumnOrExpression ,
} ,
2017-02-22 03:57:00 +00:00
}
2017-03-16 19:23:48 +00:00
/// `QueryValue` and `ColumnOrExpression` are almost identical… merge somehow?
impl From < QueryValue > for ColumnOrExpression {
fn from ( v : QueryValue ) -> Self {
match v {
QueryValue ::Column ( c ) = > ColumnOrExpression ::Column ( c ) ,
QueryValue ::Entid ( e ) = > ColumnOrExpression ::Entid ( e ) ,
QueryValue ::PrimitiveLong ( v ) = > ColumnOrExpression ::Long ( v ) ,
QueryValue ::TypedValue ( v ) = > ColumnOrExpression ::Value ( v ) ,
}
}
}
2017-02-22 03:57:00 +00:00
pub type Name = String ;
pub struct ProjectedColumn ( pub ColumnOrExpression , pub Name ) ;
pub enum Projection {
Columns ( Vec < ProjectedColumn > ) ,
Star ,
One ,
2017-02-16 23:39:19 +00:00
}
2018-03-12 22:18:50 +00:00
#[ derive(Debug, PartialEq, Eq) ]
pub enum GroupBy {
ProjectedColumn ( Name ) ,
QueryColumn ( QualifiedAlias ) ,
// TODO: non-projected expressions, etc.
}
impl QueryFragment for GroupBy {
2020-01-14 15:46:21 +00:00
fn push_sql ( & self , out : & mut dyn QueryBuilder ) -> BuildQueryResult {
2018-03-12 22:18:50 +00:00
match self {
2020-02-21 14:53:40 +00:00
GroupBy ::ProjectedColumn ( ref name ) = > out . push_identifier ( name . as_str ( ) ) ,
GroupBy ::QueryColumn ( ref qa ) = > qualified_alias_push_sql ( out , qa ) ,
2018-03-12 22:18:50 +00:00
}
}
}
2017-02-22 03:57:00 +00:00
#[ derive(Copy, Clone) ]
2020-01-14 15:46:21 +00:00
pub struct Op ( pub & 'static str ) ; // TODO: we can do better than this!
2017-02-16 23:39:19 +00:00
2017-02-22 03:57:00 +00:00
pub enum Constraint {
2017-02-16 23:39:19 +00:00
Infix {
op : Op ,
left : ColumnOrExpression ,
2017-03-07 04:18:38 +00:00
right : ColumnOrExpression ,
} ,
2017-03-28 02:35:39 +00:00
Or {
constraints : Vec < Constraint > ,
} ,
2017-03-07 04:18:38 +00:00
And {
constraints : Vec < Constraint > ,
} ,
In {
left : ColumnOrExpression ,
list : Vec < ColumnOrExpression > ,
2017-04-28 09:44:11 +00:00
} ,
2018-06-01 21:17:31 +00:00
IsNull {
value : ColumnOrExpression ,
} ,
IsNotNull {
value : ColumnOrExpression ,
} ,
2017-04-28 09:44:11 +00:00
NotExists {
subquery : TableOrSubquery ,
2018-01-29 22:29:16 +00:00
} ,
TypeCheck {
value : ColumnOrExpression ,
2020-01-14 15:46:21 +00:00
affinity : SQLTypeAffinity ,
} ,
2017-02-16 23:39:19 +00:00
}
2017-02-22 03:57:00 +00:00
impl Constraint {
2017-03-07 04:18:38 +00:00
pub fn not_equal ( left : ColumnOrExpression , right : ColumnOrExpression ) -> Constraint {
Constraint ::Infix {
2020-01-14 15:46:21 +00:00
op : Op ( " <> " ) , // ANSI SQL for future-proofing!
2020-02-21 14:53:40 +00:00
left ,
right ,
2017-03-07 04:18:38 +00:00
}
}
2017-02-22 03:57:00 +00:00
pub fn equal ( left : ColumnOrExpression , right : ColumnOrExpression ) -> Constraint {
Constraint ::Infix {
op : Op ( " = " ) ,
2020-02-21 14:53:40 +00:00
left ,
right ,
2017-02-22 03:57:00 +00:00
}
}
2017-06-12 21:24:56 +00:00
pub fn fulltext_match ( left : ColumnOrExpression , right : ColumnOrExpression ) -> Constraint {
Constraint ::Infix {
op : Op ( " MATCH " ) , // SQLite specific!
2020-02-21 14:53:40 +00:00
left ,
right ,
2017-06-12 21:24:56 +00:00
}
}
2017-02-22 03:57:00 +00:00
}
2017-03-06 22:40:10 +00:00
#[ allow(dead_code) ]
2017-02-16 23:39:19 +00:00
enum JoinOp {
Inner ,
}
// Short-hand for a list of tables all inner-joined.
2017-04-08 00:23:41 +00:00
pub struct TableList ( pub Vec < TableOrSubquery > ) ;
2017-02-16 23:39:19 +00:00
2017-03-07 04:18:38 +00:00
impl TableList {
fn is_empty ( & self ) -> bool {
self . 0. is_empty ( )
}
}
2017-02-22 03:57:00 +00:00
pub struct Join {
2017-02-16 23:39:19 +00:00
left : TableOrSubquery ,
op : JoinOp ,
right : TableOrSubquery ,
// TODO: constraints (ON, USING).
}
2017-03-06 22:40:10 +00:00
#[ allow(dead_code) ]
2017-04-08 00:23:41 +00:00
pub enum TableOrSubquery {
2017-02-16 23:39:19 +00:00
Table ( SourceAlias ) ,
2017-04-08 00:23:41 +00:00
Union ( Vec < SelectQuery > , TableAlias ) ,
2017-04-28 09:44:11 +00:00
Subquery ( Box < SelectQuery > ) ,
2017-04-19 20:00:14 +00:00
Values ( Values , TableAlias ) ,
}
pub enum Values {
/// Like "VALUES (0, 1), (2, 3), ...".
2017-06-02 20:03:16 +00:00
/// The vector must be of a length that is a multiple of the given size.
Unnamed ( usize , Vec < TypedValue > ) ,
2017-04-19 20:00:14 +00:00
/// Like "SELECT 0 AS x, SELECT 0 AS y WHERE 0 UNION ALL VALUES (0, 1), (2, 3), ...".
2017-06-02 20:03:16 +00:00
/// The vector of values must be of a length that is a multiple of the length
/// of the vector of names.
Named ( Vec < Variable > , Vec < TypedValue > ) ,
2017-02-16 23:39:19 +00:00
}
2017-02-22 03:57:00 +00:00
pub enum FromClause {
2020-01-14 15:46:21 +00:00
TableList ( TableList ) , // Short-hand for a pile of inner joins.
2017-02-16 23:39:19 +00:00
Join ( Join ) ,
2017-03-07 04:18:38 +00:00
Nothing ,
2017-02-16 23:39:19 +00:00
}
2017-02-22 03:57:00 +00:00
pub struct SelectQuery {
2017-03-22 21:02:00 +00:00
pub distinct : bool ,
2017-02-22 03:57:00 +00:00
pub projection : Projection ,
pub from : FromClause ,
pub constraints : Vec < Constraint > ,
2018-03-12 22:18:50 +00:00
pub group_by : Vec < GroupBy > ,
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
pub order : Vec < OrderBy > ,
2017-04-19 23:16:19 +00:00
pub limit : Limit ,
2017-02-22 03:57:00 +00:00
}
2020-01-14 15:46:21 +00:00
fn push_variable_column ( qb : & mut dyn QueryBuilder , vc : & VariableColumn ) -> BuildQueryResult {
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
match vc {
2020-02-21 14:53:40 +00:00
VariableColumn ::Variable ( ref v ) = > qb . push_identifier ( v . as_str ( ) ) ,
VariableColumn ::VariableTypeTag ( ref v ) = > {
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
qb . push_identifier ( format! ( " {} _value_type_tag " , v . name ( ) ) . as_str ( ) )
2020-01-14 15:46:21 +00:00
}
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
}
}
2020-01-14 15:46:21 +00:00
fn push_column ( qb : & mut dyn QueryBuilder , col : & Column ) -> BuildQueryResult {
2017-04-08 00:23:41 +00:00
match col {
2020-02-21 14:53:40 +00:00
Column ::Fixed ( ref d ) = > {
2017-04-08 00:23:41 +00:00
qb . push_sql ( d . as_str ( ) ) ;
Ok ( ( ) )
2020-01-14 15:46:21 +00:00
}
2020-02-21 14:53:40 +00:00
Column ::Fulltext ( ref d ) = > {
2017-06-12 21:24:56 +00:00
qb . push_sql ( d . as_str ( ) ) ;
Ok ( ( ) )
2020-01-14 15:46:21 +00:00
}
2020-02-21 14:53:40 +00:00
Column ::Variable ( ref vc ) = > push_variable_column ( qb , vc ) ,
Column ::Transactions ( ref d ) = > {
2018-04-16 21:08:00 +00:00
qb . push_sql ( d . as_str ( ) ) ;
Ok ( ( ) )
2020-01-14 15:46:21 +00:00
}
2017-04-08 00:23:41 +00:00
}
2017-02-16 23:39:19 +00:00
}
//---------------------------------------------------------
// Turn that representation into SQL.
impl QueryFragment for ColumnOrExpression {
2020-01-14 15:46:21 +00:00
fn push_sql ( & self , out : & mut dyn QueryBuilder ) -> BuildQueryResult {
2017-02-16 23:39:19 +00:00
use self ::ColumnOrExpression ::* ;
match self {
2020-02-21 14:53:40 +00:00
Column ( ref qa ) = > qualified_alias_push_sql ( out , qa ) ,
ExistingColumn ( ref alias ) = > out . push_identifier ( alias . as_str ( ) ) ,
Entid ( entid ) = > {
2017-02-22 03:57:00 +00:00
out . push_sql ( entid . to_string ( ) . as_str ( ) ) ;
2017-02-16 23:39:19 +00:00
Ok ( ( ) )
2020-01-14 15:46:21 +00:00
}
2020-02-21 14:53:40 +00:00
Integer ( integer ) = > {
2017-03-07 04:18:38 +00:00
out . push_sql ( integer . to_string ( ) . as_str ( ) ) ;
Ok ( ( ) )
2020-01-14 15:46:21 +00:00
}
2020-02-21 14:53:40 +00:00
Long ( long ) = > {
2017-03-16 19:23:48 +00:00
out . push_sql ( long . to_string ( ) . as_str ( ) ) ;
Ok ( ( ) )
2020-01-14 15:46:21 +00:00
}
2020-02-21 14:53:40 +00:00
Value ( ref v ) = > out . push_typed_value ( v ) ,
NullableAggregate ( ref e , _ ) | & Expression ( ref e , _ ) = > e . push_sql ( out ) ,
2018-03-12 22:18:50 +00:00
}
}
}
impl QueryFragment for Expression {
2020-01-14 15:46:21 +00:00
fn push_sql ( & self , out : & mut dyn QueryBuilder ) -> BuildQueryResult {
2018-03-12 22:18:50 +00:00
match self {
2020-02-21 14:53:40 +00:00
Expression ::Unary {
2020-01-14 15:46:21 +00:00
ref sql_op ,
ref arg ,
} = > {
out . push_sql ( sql_op ) ; // No need to escape built-ins.
2018-03-12 22:18:50 +00:00
out . push_sql ( " ( " ) ;
arg . push_sql ( out ) ? ;
out . push_sql ( " ) " ) ;
Ok ( ( ) )
2020-01-14 15:46:21 +00:00
}
2017-02-16 23:39:19 +00:00
}
}
}
impl QueryFragment for Projection {
2020-01-14 15:46:21 +00:00
fn push_sql ( & self , out : & mut dyn QueryBuilder ) -> BuildQueryResult {
2017-02-22 03:57:00 +00:00
use self ::Projection ::* ;
match self {
2020-02-21 14:53:40 +00:00
One = > out . push_sql ( " 1 " ) ,
Star = > out . push_sql ( " * " ) ,
Columns ( ref cols ) = > {
2017-02-22 03:57:00 +00:00
let & ProjectedColumn ( ref col , ref alias ) = & cols [ 0 ] ;
col . push_sql ( out ) ? ;
out . push_sql ( " AS " ) ;
out . push_identifier ( alias . as_str ( ) ) ? ;
for & ProjectedColumn ( ref col , ref alias ) in & cols [ 1 .. ] {
out . push_sql ( " , " ) ;
col . push_sql ( out ) ? ;
out . push_sql ( " AS " ) ;
out . push_identifier ( alias . as_str ( ) ) ? ;
}
2020-01-14 15:46:21 +00:00
}
2017-02-22 03:57:00 +00:00
} ;
Ok ( ( ) )
2017-02-16 23:39:19 +00:00
}
}
impl QueryFragment for Op {
2020-01-14 15:46:21 +00:00
fn push_sql ( & self , out : & mut dyn QueryBuilder ) -> BuildQueryResult {
2017-02-16 23:39:19 +00:00
// No escaping needed.
2017-02-22 03:57:00 +00:00
out . push_sql ( self . 0 ) ;
2017-02-16 23:39:19 +00:00
Ok ( ( ) )
}
}
impl QueryFragment for Constraint {
2020-01-14 15:46:21 +00:00
fn push_sql ( & self , out : & mut dyn QueryBuilder ) -> BuildQueryResult {
2017-02-16 23:39:19 +00:00
use self ::Constraint ::* ;
match self {
2020-02-21 14:53:40 +00:00
Infix {
2020-01-14 15:46:21 +00:00
ref op ,
ref left ,
ref right ,
} = > {
2017-02-16 23:39:19 +00:00
left . push_sql ( out ) ? ;
out . push_sql ( " " ) ;
op . push_sql ( out ) ? ;
out . push_sql ( " " ) ;
right . push_sql ( out )
2020-01-14 15:46:21 +00:00
}
2017-03-07 04:18:38 +00:00
2020-02-21 14:53:40 +00:00
IsNull { ref value } = > {
2018-06-01 21:17:31 +00:00
value . push_sql ( out ) ? ;
out . push_sql ( " IS NULL " ) ;
Ok ( ( ) )
2020-01-14 15:46:21 +00:00
}
2018-06-01 21:17:31 +00:00
2020-02-21 14:53:40 +00:00
IsNotNull { ref value } = > {
2018-06-01 21:17:31 +00:00
value . push_sql ( out ) ? ;
out . push_sql ( " IS NOT NULL " ) ;
Ok ( ( ) )
2020-01-14 15:46:21 +00:00
}
2018-06-01 21:17:31 +00:00
2020-02-21 14:53:40 +00:00
And { ref constraints } = > {
2017-03-28 02:35:39 +00:00
// An empty intersection is true.
if constraints . is_empty ( ) {
out . push_sql ( " 1 " ) ;
2020-01-14 15:46:21 +00:00
return Ok ( ( ) ) ;
2017-03-28 02:35:39 +00:00
}
2017-03-07 04:18:38 +00:00
out . push_sql ( " ( " ) ;
2020-01-14 15:46:21 +00:00
interpose! ( constraint , constraints , { constraint . push_sql ( out ) ? } , {
out . push_sql ( " AND " )
} ) ;
2017-03-07 04:18:38 +00:00
out . push_sql ( " ) " ) ;
Ok ( ( ) )
2020-01-14 15:46:21 +00:00
}
2017-03-07 04:18:38 +00:00
2020-02-21 14:53:40 +00:00
Or { ref constraints } = > {
2017-03-28 02:35:39 +00:00
// An empty alternation is false.
if constraints . is_empty ( ) {
out . push_sql ( " 0 " ) ;
2020-01-14 15:46:21 +00:00
return Ok ( ( ) ) ;
2017-03-28 02:35:39 +00:00
}
out . push_sql ( " ( " ) ;
2020-01-14 15:46:21 +00:00
interpose! ( constraint , constraints , { constraint . push_sql ( out ) ? } , {
out . push_sql ( " OR " )
} ) ;
2017-03-28 02:35:39 +00:00
out . push_sql ( " ) " ) ;
Ok ( ( ) )
}
2020-02-21 14:53:40 +00:00
In { ref left , ref list } = > {
2017-03-07 04:18:38 +00:00
left . push_sql ( out ) ? ;
out . push_sql ( " IN ( " ) ;
2020-01-14 15:46:21 +00:00
interpose! ( item , list , { item . push_sql ( out ) ? } , { out . push_sql ( " , " ) } ) ;
2017-03-07 04:18:38 +00:00
out . push_sql ( " ) " ) ;
Ok ( ( ) )
2020-01-14 15:46:21 +00:00
}
2020-02-21 14:53:40 +00:00
NotExists { ref subquery } = > {
2018-03-12 22:18:50 +00:00
out . push_sql ( " NOT EXISTS " ) ;
subquery . push_sql ( out )
2020-01-14 15:46:21 +00:00
}
2020-02-21 14:53:40 +00:00
TypeCheck {
2020-01-14 15:46:21 +00:00
ref value ,
ref affinity ,
} = > {
2018-01-29 22:29:16 +00:00
out . push_sql ( " typeof( " ) ;
value . push_sql ( out ) ? ;
out . push_sql ( " ) = " ) ;
out . push_sql ( match * affinity {
SQLTypeAffinity ::Null = > " 'null' " ,
SQLTypeAffinity ::Integer = > " 'integer' " ,
SQLTypeAffinity ::Real = > " 'real' " ,
SQLTypeAffinity ::Text = > " 'text' " ,
SQLTypeAffinity ::Blob = > " 'blob' " ,
} ) ;
Ok ( ( ) )
2020-01-14 15:46:21 +00:00
}
2017-02-16 23:39:19 +00:00
}
}
}
impl QueryFragment for JoinOp {
2020-01-14 15:46:21 +00:00
fn push_sql ( & self , out : & mut dyn QueryBuilder ) -> BuildQueryResult {
2017-02-16 23:39:19 +00:00
out . push_sql ( " JOIN " ) ;
Ok ( ( ) )
}
}
2018-03-12 22:18:50 +00:00
// We don't own QualifiedAlias or QueryFragment, so we can't implement the trait.
2020-01-14 15:46:21 +00:00
fn qualified_alias_push_sql ( out : & mut dyn QueryBuilder , qa : & QualifiedAlias ) -> BuildQueryResult {
2018-03-12 22:18:50 +00:00
out . push_identifier ( qa . 0. as_str ( ) ) ? ;
out . push_sql ( " . " ) ;
push_column ( out , & qa . 1 )
}
2017-02-16 23:39:19 +00:00
// We don't own SourceAlias or QueryFragment, so we can't implement the trait.
2020-01-14 15:46:21 +00:00
fn source_alias_push_sql ( out : & mut dyn QueryBuilder , sa : & SourceAlias ) -> BuildQueryResult {
2017-02-16 23:39:19 +00:00
let & SourceAlias ( ref table , ref alias ) = sa ;
out . push_identifier ( table . name ( ) ) ? ;
out . push_sql ( " AS " ) ;
out . push_identifier ( alias . as_str ( ) )
}
impl QueryFragment for TableList {
2020-01-14 15:46:21 +00:00
fn push_sql ( & self , out : & mut dyn QueryBuilder ) -> BuildQueryResult {
2017-02-16 23:39:19 +00:00
if self . 0. is_empty ( ) {
return Ok ( ( ) ) ;
}
2020-01-14 15:46:21 +00:00
interpose! ( t , self . 0 , { t . push_sql ( out ) ? } , { out . push_sql ( " , " ) } ) ;
2017-02-16 23:39:19 +00:00
Ok ( ( ) )
}
}
impl QueryFragment for Join {
2020-01-14 15:46:21 +00:00
fn push_sql ( & self , out : & mut dyn QueryBuilder ) -> BuildQueryResult {
2017-02-16 23:39:19 +00:00
self . left . push_sql ( out ) ? ;
self . op . push_sql ( out ) ? ;
self . right . push_sql ( out )
}
}
impl QueryFragment for TableOrSubquery {
2020-01-14 15:46:21 +00:00
fn push_sql ( & self , out : & mut dyn QueryBuilder ) -> BuildQueryResult {
2017-02-16 23:39:19 +00:00
use self ::TableOrSubquery ::* ;
2020-02-21 14:53:40 +00:00
match * self {
Table ( ref sa ) = > source_alias_push_sql ( out , sa ) ,
Union ( ref subqueries , ref table_alias ) = > {
2017-04-08 00:23:41 +00:00
out . push_sql ( " ( " ) ;
2020-01-14 15:46:21 +00:00
interpose! ( subquery , subqueries , { subquery . push_sql ( out ) ? } , {
out . push_sql ( " UNION " )
} ) ;
2017-04-08 00:23:41 +00:00
out . push_sql ( " ) AS " ) ;
out . push_identifier ( table_alias . as_str ( ) )
2020-01-14 15:46:21 +00:00
}
2020-02-21 14:53:40 +00:00
Subquery ( ref subquery ) = > {
2018-03-12 22:18:50 +00:00
out . push_sql ( " ( " ) ;
subquery . push_sql ( out ) ? ;
out . push_sql ( " ) " ) ;
Ok ( ( ) )
2020-01-14 15:46:21 +00:00
}
2020-02-21 14:53:40 +00:00
Values ( ref values , ref table_alias ) = > {
2017-04-19 20:00:14 +00:00
// XXX: does this work for Values::Unnamed?
out . push_sql ( " ( " ) ;
values . push_sql ( out ) ? ;
out . push_sql ( " ) AS " ) ;
out . push_identifier ( table_alias . as_str ( ) )
2020-01-14 15:46:21 +00:00
}
2017-02-16 23:39:19 +00:00
}
}
}
2017-04-19 20:00:14 +00:00
impl QueryFragment for Values {
2020-01-14 15:46:21 +00:00
fn push_sql ( & self , out : & mut dyn QueryBuilder ) -> BuildQueryResult {
2017-04-19 20:00:14 +00:00
// There are at least 3 ways to name the columns of a VALUES table:
// 1) the columns are named "", ":1", ":2", ... -- but this is undocumented. See
// http://stackoverflow.com/a/40921724.
// 2) A CTE ("WITH" statement) can declare the shape of the table, like "WITH
// table_name(column_name, ...) AS (VALUES ...)".
// 3) We can "UNION ALL" a dummy "SELECT" statement in place.
//
// We don't want to use an undocumented SQLite quirk, and we're a little concerned that some
// SQL systems will not optimize WITH statements well. It's also convenient to have an in
2017-06-02 20:34:02 +00:00
// place table to query, so for now we implement option 3.
2020-02-21 14:53:40 +00:00
if let Values ::Named ( ref names , _ ) = * self {
2017-06-02 20:34:02 +00:00
out . push_sql ( " SELECT " ) ;
2020-01-14 15:46:21 +00:00
interpose! (
alias ,
names ,
{
out . push_sql ( " 0 AS " ) ;
out . push_identifier ( alias . as_str ( ) ) ?
} ,
{ out . push_sql ( " , " ) }
) ;
2017-04-19 20:00:14 +00:00
out . push_sql ( " WHERE 0 UNION ALL " ) ;
}
2020-02-21 14:53:40 +00:00
let values = match * self {
Values ::Named ( ref names , ref values ) = > values . chunks ( names . len ( ) ) ,
Values ::Unnamed ( ref size , ref values ) = > values . chunks ( * size ) ,
2017-04-19 20:00:14 +00:00
} ;
out . push_sql ( " VALUES " ) ;
2020-01-14 15:46:21 +00:00
interpose_iter! (
outer ,
values ,
{
out . push_sql ( " ( " ) ;
interpose! ( inner , outer , { out . push_typed_value ( inner ) ? } , {
out . push_sql ( " , " )
} ) ;
out . push_sql ( " ) " ) ;
} ,
{ out . push_sql ( " , " ) }
) ;
2017-04-19 20:00:14 +00:00
Ok ( ( ) )
}
}
2017-02-16 23:39:19 +00:00
impl QueryFragment for FromClause {
2020-01-14 15:46:21 +00:00
fn push_sql ( & self , out : & mut dyn QueryBuilder ) -> BuildQueryResult {
2017-02-16 23:39:19 +00:00
use self ::FromClause ::* ;
2020-02-21 14:53:40 +00:00
match * self {
TableList ( ref table_list ) = > {
2017-03-07 04:18:38 +00:00
if table_list . is_empty ( ) {
Ok ( ( ) )
} else {
out . push_sql ( " FROM " ) ;
table_list . push_sql ( out )
}
2020-01-14 15:46:21 +00:00
}
2020-02-21 14:53:40 +00:00
Join ( ref join ) = > {
2017-03-07 04:18:38 +00:00
out . push_sql ( " FROM " ) ;
join . push_sql ( out )
2020-01-14 15:46:21 +00:00
}
2020-02-21 14:53:40 +00:00
Nothing = > Ok ( ( ) ) ,
2017-02-16 23:39:19 +00:00
}
}
}
2018-06-28 01:54:28 +00:00
/// `var` is something like `?foo99-people`.
/// Trim the `?` and escape the rest. Prepend `i` to distinguish from
/// the inline value space `v`.
fn format_select_var ( var : & str ) -> String {
use std ::iter ::once ;
let without_question = var . split_at ( 1 ) . 1 ;
2020-01-14 15:46:21 +00:00
let replaced_iter =
without_question
. chars ( )
. map ( | c | if c . is_ascii_alphanumeric ( ) { c } else { '_' } ) ;
2018-06-28 01:54:28 +00:00
// Prefix with `i` (Avoiding this copy is probably not worth the trouble but whatever).
once ( 'i' ) . chain ( replaced_iter ) . collect ( )
}
2017-04-19 23:16:19 +00:00
impl SelectQuery {
2020-01-14 15:46:21 +00:00
fn push_variable_param ( & self , var : & Variable , out : & mut dyn QueryBuilder ) -> BuildQueryResult {
2018-06-28 01:54:28 +00:00
let bind_param = format_select_var ( var . as_str ( ) ) ;
2017-04-19 23:16:19 +00:00
out . push_bind_param ( bind_param . as_str ( ) )
}
}
2017-02-16 23:39:19 +00:00
impl QueryFragment for SelectQuery {
2020-01-14 15:46:21 +00:00
fn push_sql ( & self , out : & mut dyn QueryBuilder ) -> BuildQueryResult {
2017-03-22 21:02:00 +00:00
if self . distinct {
out . push_sql ( " SELECT DISTINCT " ) ;
} else {
out . push_sql ( " SELECT " ) ;
}
2017-02-22 03:57:00 +00:00
self . projection . push_sql ( out ) ? ;
2017-02-16 23:39:19 +00:00
self . from . push_sql ( out ) ? ;
2017-03-07 04:18:38 +00:00
if ! self . constraints . is_empty ( ) {
out . push_sql ( " WHERE " ) ;
2020-01-14 15:46:21 +00:00
interpose! (
constraint ,
self . constraints ,
{ constraint . push_sql ( out ) ? } ,
{ out . push_sql ( " AND " ) }
) ;
2017-02-16 23:39:19 +00:00
}
2018-03-12 22:18:50 +00:00
match & self . group_by {
group_by if ! group_by . is_empty ( ) = > {
out . push_sql ( " GROUP BY " ) ;
2020-01-14 15:46:21 +00:00
interpose! ( group , group_by , { group . push_sql ( out ) ? } , {
out . push_sql ( " , " )
} ) ;
}
_ = > { }
2018-03-12 22:18:50 +00:00
}
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
if ! self . order . is_empty ( ) {
out . push_sql ( " ORDER BY " ) ;
2020-01-14 15:46:21 +00:00
interpose! (
& OrderBy ( ref dir , ref var ) ,
self . order ,
{
push_variable_column ( out , var ) ? ;
2020-02-21 14:53:40 +00:00
match * dir {
Direction ::Ascending = > {
2020-01-14 15:46:21 +00:00
out . push_sql ( " ASC " ) ;
}
2020-02-21 14:53:40 +00:00
Direction ::Descending = > {
2020-01-14 15:46:21 +00:00
out . push_sql ( " DESC " ) ;
}
} ;
} ,
{ out . push_sql ( " , " ) }
) ;
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
}
2020-02-21 14:53:40 +00:00
match self . limit {
Limit ::None = > ( ) ,
Limit ::Fixed ( limit ) = > {
2017-04-19 23:16:19 +00:00
// Guaranteed to be non-negative: u64.
out . push_sql ( " LIMIT " ) ;
out . push_sql ( limit . to_string ( ) . as_str ( ) ) ;
2020-01-14 15:46:21 +00:00
}
2020-02-21 14:53:40 +00:00
Limit ::Variable ( ref var ) = > {
2017-04-19 23:16:19 +00:00
// Guess this wasn't bound yet. Produce an argument.
out . push_sql ( " LIMIT " ) ;
self . push_variable_param ( var , out ) ? ;
2020-01-14 15:46:21 +00:00
}
2017-03-07 00:27:13 +00:00
}
2017-02-16 23:39:19 +00:00
Ok ( ( ) )
}
}
impl SelectQuery {
2018-06-01 02:30:25 +00:00
pub fn to_sql_query ( & self ) -> Result < SQLQuery , SQLError > {
2017-02-16 23:39:19 +00:00
let mut builder = SQLiteQueryBuilder ::new ( ) ;
self . push_sql ( & mut builder ) . map ( | _ | builder . finish ( ) )
}
}
#[ cfg(test) ]
mod tests {
use super ::* ;
2017-06-12 21:24:56 +00:00
use std ::rc ::Rc ;
2020-01-14 15:46:21 +00:00
use mentat_query_algebrizer ::{ Column , DatomsColumn , DatomsTable , FulltextColumn } ;
2017-02-16 23:39:19 +00:00
2020-01-14 15:46:21 +00:00
fn build_query ( c : & dyn QueryFragment ) -> SQLQuery {
2017-03-07 04:18:38 +00:00
let mut builder = SQLiteQueryBuilder ::new ( ) ;
c . push_sql ( & mut builder )
2020-01-14 15:46:21 +00:00
. map ( | _ | builder . finish ( ) )
. expect ( " to produce a query for the given constraint " )
2017-06-12 21:24:56 +00:00
}
2020-01-14 15:46:21 +00:00
fn build ( c : & dyn QueryFragment ) -> String {
2017-06-12 21:24:56 +00:00
build_query ( c ) . sql
2017-03-07 04:18:38 +00:00
}
#[ test ]
fn test_in_constraint ( ) {
let none = Constraint ::In {
2020-01-14 15:46:21 +00:00
left : ColumnOrExpression ::Column ( QualifiedAlias ::new (
" datoms01 " . to_string ( ) ,
Column ::Fixed ( DatomsColumn ::Value ) ,
) ) ,
2017-03-07 04:18:38 +00:00
list : vec ! [ ] ,
} ;
let one = Constraint ::In {
2020-01-14 15:46:21 +00:00
left : ColumnOrExpression ::Column ( QualifiedAlias ::new (
" datoms01 " . to_string ( ) ,
DatomsColumn ::Value ,
) ) ,
list : vec ! [ ColumnOrExpression ::Entid ( 123 ) ] ,
2017-03-07 04:18:38 +00:00
} ;
let three = Constraint ::In {
2020-01-14 15:46:21 +00:00
left : ColumnOrExpression ::Column ( QualifiedAlias ::new (
" datoms01 " . to_string ( ) ,
DatomsColumn ::Value ,
) ) ,
2017-03-07 04:18:38 +00:00
list : vec ! [
ColumnOrExpression ::Entid ( 123 ) ,
ColumnOrExpression ::Entid ( 456 ) ,
ColumnOrExpression ::Entid ( 789 ) ,
] ,
} ;
2017-04-19 20:00:14 +00:00
assert_eq! ( " `datoms01`.v IN () " , build ( & none ) ) ;
assert_eq! ( " `datoms01`.v IN (123) " , build ( & one ) ) ;
assert_eq! ( " `datoms01`.v IN (123, 456, 789) " , build ( & three ) ) ;
2017-03-07 04:18:38 +00:00
}
#[ test ]
fn test_and_constraint ( ) {
let c = Constraint ::And {
2020-01-14 15:46:21 +00:00
constraints : vec ! [ Constraint ::And {
constraints : vec ! [
Constraint ::Infix {
op : Op ( " = " ) ,
left : ColumnOrExpression ::Entid ( 123 ) ,
right : ColumnOrExpression ::Entid ( 456 ) ,
} ,
Constraint ::Infix {
op : Op ( " = " ) ,
left : ColumnOrExpression ::Entid ( 789 ) ,
right : ColumnOrExpression ::Entid ( 246 ) ,
} ,
] ,
} ] ,
2017-03-07 04:18:38 +00:00
} ;
// Two sets of parens: the outermost AND only has one child,
// but still contributes parens.
2017-04-19 20:00:14 +00:00
assert_eq! ( " ((123 = 456 AND 789 = 246)) " , build ( & c ) ) ;
}
#[ test ]
fn test_unnamed_values ( ) {
2017-06-02 20:34:02 +00:00
let build = | len , values | build ( & Values ::Unnamed ( len , values ) ) ;
2020-01-14 15:46:21 +00:00
assert_eq! ( build ( 1 , vec! [ TypedValue ::Long ( 1 ) ] ) , " VALUES (1) " ) ;
2017-04-19 20:00:14 +00:00
2020-01-14 15:46:21 +00:00
assert_eq! (
build ( 2 , vec! [ TypedValue ::Boolean ( false ) , TypedValue ::Long ( 1 ) ] ) ,
" VALUES (0, 1) "
) ;
2017-04-19 20:00:14 +00:00
2020-01-14 15:46:21 +00:00
assert_eq! (
build (
2 ,
vec! [
TypedValue ::Boolean ( false ) ,
TypedValue ::Long ( 1 ) ,
TypedValue ::Boolean ( true ) ,
TypedValue ::Long ( 2 )
]
) ,
" VALUES (0, 1), (1, 2) "
) ;
2017-04-19 20:00:14 +00:00
}
#[ test ]
fn test_named_values ( ) {
2020-01-14 15:46:21 +00:00
let build = | names : Vec < _ > , values | {
build ( & Values ::Named (
names . into_iter ( ) . map ( Variable ::from_valid_name ) . collect ( ) ,
values ,
) )
} ;
assert_eq! (
build ( vec! [ " ?a " ] , vec! [ TypedValue ::Long ( 1 ) ] ) ,
" SELECT 0 AS `?a` WHERE 0 UNION ALL VALUES (1) "
) ;
assert_eq! (
build (
vec! [ " ?a " , " ?b " ] ,
vec! [ TypedValue ::Boolean ( false ) , TypedValue ::Long ( 1 ) ]
) ,
" SELECT 0 AS `?a`, 0 AS `?b` WHERE 0 UNION ALL VALUES (0, 1) "
) ;
assert_eq! (
build (
vec! [ " ?a " , " ?b " ] ,
vec! [
TypedValue ::Boolean ( false ) ,
TypedValue ::Long ( 1 ) ,
TypedValue ::Boolean ( true ) ,
TypedValue ::Long ( 2 )
]
) ,
" SELECT 0 AS `?a`, 0 AS `?b` WHERE 0 UNION ALL VALUES (0, 1), (1, 2) "
) ;
2017-03-07 04:18:38 +00:00
}
2017-06-12 21:24:56 +00:00
#[ test ]
fn test_matches_constraint ( ) {
let c = Constraint ::Infix {
op : Op ( " MATCHES " ) ,
2020-01-14 15:46:21 +00:00
left : ColumnOrExpression ::Column ( QualifiedAlias (
" fulltext01 " . to_string ( ) ,
Column ::Fulltext ( FulltextColumn ::Text ) ,
) ) ,
2018-04-25 21:23:27 +00:00
right : ColumnOrExpression ::Value ( " needle " . into ( ) ) ,
2017-06-12 21:24:56 +00:00
} ;
let q = build_query ( & c ) ;
assert_eq! ( " `fulltext01`.text MATCHES $v0 " , q . sql ) ;
2020-01-14 15:46:21 +00:00
assert_eq! (
vec! [ (
" $v0 " . to_string ( ) ,
Rc ::new ( rusqlite ::types ::Value ::Text ( " needle " . to_string ( ) ) )
) ] ,
q . args
) ;
2017-06-12 21:24:56 +00:00
let c = Constraint ::Infix {
op : Op ( " = " ) ,
2020-01-14 15:46:21 +00:00
left : ColumnOrExpression ::Column ( QualifiedAlias (
" fulltext01 " . to_string ( ) ,
Column ::Fulltext ( FulltextColumn ::Rowid ) ,
) ) ,
right : ColumnOrExpression ::Column ( QualifiedAlias (
" datoms02 " . to_string ( ) ,
Column ::Fixed ( DatomsColumn ::Value ) ,
) ) ,
2017-06-12 21:24:56 +00:00
} ;
assert_eq! ( " `fulltext01`.rowid = `datoms02`.v " , build ( & c ) ) ;
}
2017-02-16 23:39:19 +00:00
#[ test ]
fn test_end_to_end ( ) {
// [:find ?x :where [?x 65537 ?v] [?x 65536 ?v]]
let datoms00 = " datoms00 " . to_string ( ) ;
let datoms01 = " datoms01 " . to_string ( ) ;
2017-02-22 03:57:00 +00:00
let eq = Op ( " = " ) ;
2017-02-16 23:39:19 +00:00
let source_aliases = vec! [
2017-04-08 00:23:41 +00:00
TableOrSubquery ::Table ( SourceAlias ( DatomsTable ::Datoms , datoms00 . clone ( ) ) ) ,
TableOrSubquery ::Table ( SourceAlias ( DatomsTable ::Datoms , datoms01 . clone ( ) ) ) ,
2017-02-16 23:39:19 +00:00
] ;
2017-03-22 21:02:00 +00:00
let mut query = SelectQuery {
distinct : true ,
2020-01-14 15:46:21 +00:00
projection : Projection ::Columns ( vec! [ ProjectedColumn (
ColumnOrExpression ::Column ( QualifiedAlias ::new (
datoms00 . clone ( ) ,
DatomsColumn ::Entity ,
) ) ,
" x " . to_string ( ) ,
) ] ) ,
2017-02-16 23:39:19 +00:00
from : FromClause ::TableList ( TableList ( source_aliases ) ) ,
constraints : vec ! [
Constraint ::Infix {
2020-08-06 03:03:58 +00:00
op : eq ,
2020-01-14 15:46:21 +00:00
left : ColumnOrExpression ::Column ( QualifiedAlias ::new (
datoms01 . clone ( ) ,
DatomsColumn ::Value ,
) ) ,
right : ColumnOrExpression ::Column ( QualifiedAlias ::new (
datoms00 . clone ( ) ,
DatomsColumn ::Value ,
) ) ,
2017-02-16 23:39:19 +00:00
} ,
Constraint ::Infix {
2020-08-06 03:03:58 +00:00
op : eq ,
2020-01-14 15:46:21 +00:00
left : ColumnOrExpression ::Column ( QualifiedAlias ::new (
2020-08-06 03:03:58 +00:00
datoms00 ,
2020-01-14 15:46:21 +00:00
DatomsColumn ::Attribute ,
) ) ,
2017-02-22 03:57:00 +00:00
right : ColumnOrExpression ::Entid ( 65537 ) ,
2017-02-16 23:39:19 +00:00
} ,
Constraint ::Infix {
2020-08-06 03:03:58 +00:00
op : eq ,
2020-01-14 15:46:21 +00:00
left : ColumnOrExpression ::Column ( QualifiedAlias ::new (
2020-08-06 03:03:58 +00:00
datoms01 ,
2020-01-14 15:46:21 +00:00
DatomsColumn ::Attribute ,
) ) ,
2017-02-22 03:57:00 +00:00
right : ColumnOrExpression ::Entid ( 65536 ) ,
2017-02-16 23:39:19 +00:00
} ,
] ,
2018-03-12 22:18:50 +00:00
group_by : 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
order : vec ! [ ] ,
2017-04-19 23:16:19 +00:00
limit : Limit ::None ,
2017-02-16 23:39:19 +00:00
} ;
2017-03-22 21:02:00 +00:00
let SQLQuery { sql , args } = query . to_sql_query ( ) . unwrap ( ) ;
println! ( " {} " , sql ) ;
assert_eq! ( " SELECT DISTINCT `datoms00`.e AS `x` FROM `datoms` AS `datoms00`, `datoms` AS `datoms01` WHERE `datoms01`.v = `datoms00`.v AND `datoms00`.a = 65537 AND `datoms01`.a = 65536 " , sql ) ;
assert! ( args . is_empty ( ) ) ;
// And without distinct…
query . distinct = false ;
2017-02-22 03:57:00 +00:00
let SQLQuery { sql , args } = query . to_sql_query ( ) . unwrap ( ) ;
2017-02-16 23:39:19 +00:00
println! ( " {} " , sql ) ;
2017-02-22 03:57:00 +00:00
assert_eq! ( " SELECT `datoms00`.e AS `x` FROM `datoms` AS `datoms00`, `datoms` AS `datoms01` WHERE `datoms01`.v = `datoms00`.v AND `datoms00`.a = 65537 AND `datoms01`.a = 65536 " , sql ) ;
assert! ( args . is_empty ( ) ) ;
2017-02-16 23:39:19 +00:00
}
2018-06-28 01:54:28 +00:00
#[ test ]
fn test_format_select_var ( ) {
assert_eq! ( format_select_var ( " ?foo99-people " ) , " ifoo99_people " ) ;
assert_eq! ( format_select_var ( " ?FOO99-pëople.123 " ) , " iFOO99_p_ople_123 " ) ;
assert_eq! ( format_select_var ( " ?foo①bar越 " ) , " ifoo_bar_ " ) ;
}
2017-02-16 23:39:19 +00:00
}