/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Sql.cal
* Creation date: Feb 5, 2004.
* By: Richard Webster
*/
/**
* This module provides a combinator library for creating abstract SQL queries.
* A query is represented by the type {@link typeConstructor = Query@}.
*
* A new, empty query can be created by calling {@link newQuery@}.
*
* {@code
* qry0 = newQuery;
* @}
*
* To project a column or a SQL expression, use the {@link project@} function.
* There are several variations on this function, such as {@link projectColumn@} and {@link projectWithAliases@}.
*
* {@code
* qry1 = project qry0 [toUntypedExpr countryField, toUntypedExpr orderDateField];
* @}
*
* To add a restriction to the query, use the {@link restrict@} function.
*
* {@code
* qry2 = restrict qry1 (eqExpr countryField (stringConstant "Canada"));
* @}
*
* To add sorting to the query, use the {@link order@} function.
*
* {@code
* qry3 = order qry2 orderDateField True;
* @}
*
* To add a join to the query, use the {@link join@} function.
*
* {@code
* joinInfo = makeJoinInfo (intField custTable "Customer ID") (intField ordersTable "Customer ID") InnerJoin;
* qry4 = join qry3 joinInfo;
* @}
*
* An abstract {@link typeConstructor = Query@} can be converted to a concrete SQL query with the {@link queryText@} function.
* This function requires a {@link typeConstructor = SqlBuilder@} for a specific RDBMS as found in the module {@link module = "Cal.Data.SqlBuilder"@}.
*
* See the module {@link module = "Cal.Test.Data.Sql_Tests"@} for examples of query construction.
*
* There is some support for other types of SQL statements, such as ones to create/delete tables and inserting rows.
* See the {@link Statement@} type.
*
* @author Richard Webster
*/
module Cal.Data.Sql;
import Cal.Core.Prelude using
typeConstructor = Byte, Int, Double, String, Boolean, Char, Integer, JObject, JList, Maybe, Ordering;
dataConstructor = False, True, LT, EQ, GT, Nothing, Just;
typeClass = Eq, Ord, Num, Inputable, Outputable;
function =
append, compare, concat, const, doubleToString, equals, error, fromJust, fst, input,
intToString, isNothing, isEmpty, max, mod, not, output, round, seq, snd, toDouble,
field1, field2, field3, upFrom, upFromTo;
typeConstructor = Long;
function = compose, fromMaybe, isJust, listToMaybe, maybeToList, longToString;
;
import Cal.Collections.List using
function = all, chop, filter, foldLeft, foldLeftStrict, foldRight, head, intersperse,
last, length, list2, map, outputList, outputListWith, reverse, subscript, sum, tail, take,
zip, zip3, zipWith;
function = any, concatMap, deleteFirsts, drop, elemIndex, isElem, mapIndexed, repeat, removeDuplicates,
removeDuplicatesBy, lookupWithDefault, find, groupBy, intersect, isSingletonList, sort;
;
import Cal.Core.String using
function = toLowerCase;
;
import Cal.Core.Debug using
typeClass = Show;
function = show;
;
import Cal.Utilities.TimeZone using
typeConstructor = TimeZone;
;
import Cal.Utilities.Time using
typeConstructor = Time;
;
import Cal.Utilities.PrettyPrinter using
typeConstructor = Document;
function = line, multilineText, nest, text;
;
import Cal.Data.SqlType using
typeConstructor = SqlType;
;
import Cal.Collections.Set using
typeConstructor = Set;
;
import Cal.Collections.Array using
typeConstructor = Array;
;
import Cal.Data.DatabaseMetadata using
typeConstructor = DatabaseReference, TableReference, TableDescription, FieldDescription,
TableConstraint;
dataConstructor = PrimaryKeyConstraint, UniqueConstraint, ForeignKeyConstraint;
function = getTableNameFromReference, makeTableReference;
;
import Cal.Utilities.UniqueIdentifier using
typeConstructor = UniqueIdentifier;
typeClass = UniquelyNamedItem;
function = makeUniqueIdentifierByName;
;
import Cal.Utilities.Range using
typeConstructor = Range;
function =
hasLeftEndpoint, hasRightEndpoint, includesLeftEndpoint,
includesRightEndpoint, leftEndpoint, rightEndpoint;
;
friend Cal.Data.SqlBuilder;
/**
* Associates a table resource with its alias for the query.
*/
data public QueryTable =
/**
* A reference to an actual database table with a given alias for the query.
* @arg tableRef a reference to a database table by name
* @arg tableAlias the alias for the table in the query
*/
private BaseTable
tableRef :: !TableReference
tableAlias :: !String
|
/**
* A nested SELECT query to be treated as a query table with a given alias.
* @arg subquery a database query to be treated as a query table
* @arg tableAlias the alias for the nested SELECT 'table' in the query
*/
private SubQueryTable
subquery :: Query
tableAlias :: !String
|
/**
* A nested SELECT statement to be treated as a query table with a given alias.
* @arg subqueryText the SQL for a SELECT statement to be treated as a query table
* @arg tableAlias the alias for the nested SELECT 'table' in the query
*/
private OpaqueSubQueryTable
subqueryText :: String
tableAlias :: !String
;
/**
* Constructs a query table using the specified table name.
* The table alias will be based on the table name.
* @arg tableName the name of the table to be used in the query
* @return a query table for the specified table using the table name as the alias
*/
makeQueryTable :: String -> QueryTable;
public makeQueryTable !tableName = makeQueryTableWithAlias tableName "";
/**
* Constructs a query table using the specified table name and base alias name.
* If no table alias is specified, the table name will be used as the alias.
* @arg tableName the name of the table to be used in the query
* @arg tableAlias the alias to use for the table in the query;
* An empty alias name indicates that the table name should be used as the alias
* @return a query table for the specified table and alias
*/
makeQueryTableWithAlias :: String -> String -> QueryTable;
public makeQueryTableWithAlias !tableName !tableAlias =
let
fixedAlias :: String;
fixedAlias = if (isEmpty tableAlias) then tableName else tableAlias;
in
BaseTable (makeTableReference tableName) fixedAlias;
/**
* Constructs a subquery table using the specified query and alias.
* This function will remove any ordering from the subquery (unless it is a {@link TopN@}
* query).
* @arg subquery a database query to be treated as a query table
* @arg tableAlias the alias to use for the table in the query;
* If an empty alias is specified, then a default one will be generated.
* @return a query table for the nested SELECT query with the given alias
*/
makeSubQueryTable :: Query -> String -> QueryTable;
public makeSubQueryTable subquery !tableAlias =
let
fixedAlias :: String;
fixedAlias = if (isEmpty tableAlias) then "Q" else tableAlias;
in
SubQueryTable (fixSubqueryOrdering subquery) fixedAlias;
/**
* Constructs a subquery table using the specified query text and alias.
* The query text must be valid when used as a subquery.
* In most cases, this means that it cannot contain an ORDER BY clause.
* @arg subqueryText a SELECT statement text to be treated as a query table
* @arg tableAlias the alias to use for the table in the query;
* If an empty alias is specified, then a default one will be generated.
* @return a query table for the nested SELECT query with the given alias
*/
makeOpaqueSubQueryTable :: String -> String -> QueryTable;
public makeOpaqueSubQueryTable subqueryText !tableAlias =
let
fixedAlias :: String;
fixedAlias = if (isEmpty tableAlias) then "Q" else tableAlias;
in
OpaqueSubQueryTable subqueryText fixedAlias;
/**
* Returns whether the specified table is a subquery table.
* @arg queryTable a query table
* @return True if the table is a subquery (or opaque subquery) table;
* False if the table is a base database table
*/
isSubqueryTable :: QueryTable -> Boolean;
public isSubqueryTable !queryTable =
case queryTable of
(SubQueryTable | OpaqueSubQueryTable) {} -> True;
_ -> False;
;
/**
* Returns the name of the table (without quotes).
* An empty string is returned for the name of a subquery table.
* @arg queryTable a query table
* @return the name of the base database table,
* or an empty string if the query table is based on a subquery
*/
getQueryTableName :: QueryTable -> String;
public getQueryTableName !queryTable =
case queryTable of
BaseTable {tableRef} -> getTableNameFromReference tableRef;
_ -> "";
;
/**
* Returns the table alias for a query table.
* This is a composition of the base alias and the table ID to give a distinct alias for the table.
* @arg queryTable a query table
* @return the table alias for the query table
*/
getQueryTableAlias :: QueryTable -> String;
public getQueryTableAlias !queryTable =
case queryTable of
(BaseTable | SubQueryTable | OpaqueSubQueryTable) {tableAlias} -> tableAlias;
;
/**
* Query tables can be compared for equality.
* This is based only on the table aliases, not the actual table information.
*/
instance Eq QueryTable where
equals = equalsQueryTable;
notEquals = notEqualsQueryTable;
;
equalsQueryTable :: QueryTable -> QueryTable -> Boolean;
private equalsQueryTable table1 table2 =
case table1 of
BaseTable {tableAlias} ->
case table2 of
// TODO: should this compare the tables as well? (This breaks the
// SqlParser code currently.)
BaseTable {tableAlias = tableAlias2} ->
tableAlias == tableAlias2;
// && table == table2;
_ -> False;
;
SubQueryTable {tableAlias} ->
case table2 of
// TODO: should this compare the subqueries as well? (This breaks the
// SqlParser code currently.)
SubQueryTable {tableAlias = tableAlias2} ->
tableAlias == tableAlias2;
// && subQuery == subQuery2;
_ -> False;
;
OpaqueSubQueryTable {tableAlias} ->
case table2 of
OpaqueSubQueryTable {tableAlias = tableAlias2} ->
tableAlias == tableAlias2;
// && subqueryText == subqueryText2;
_ -> False;
;
;
notEqualsQueryTable :: QueryTable -> QueryTable -> Boolean;
private notEqualsQueryTable table1 table2 = not (equalsQueryTable table1 table2);
/**
* Query tables can be shown.
* For subquery tables, the subquery text will not be shown since this may be too verbose.
*/
instance Debug.Show QueryTable where
show = showQueryTable;
;
showQueryTable :: QueryTable -> String;
private showQueryTable table =
case table of
BaseTable {tableRef, tableAlias} ->
if (getTableNameFromReference tableRef == tableAlias) then tableAlias
else (show tableRef) ++ " AS " ++ tableAlias;
(SubQueryTable |
OpaqueSubQueryTable) {tableAlias} -> "<subquery> AS "++ tableAlias;
;
/**
* Options for the query.
*/
data public QueryOption =
/**
* This option indicates that only distinct rows should be returned for the query.
*/
public Distinct
|
/**
* This option indicates that the first N rows should be returned.
* @arg n the number of rows to be returned
* @arg percent if True then the number with be interpretted as a percentage of the full set of rows,
* if False then the number will be interpretted as a number of rows
* @arg withTies if True then any rows that are considered equivalent order-wise to the Nth row will also be included in the results;
* if False then only the first N rows will be returned, even if there are ties
*/
public TopN
n :: !Int
percent :: !Boolean
withTies :: !Boolean
deriving Eq, Show
;
/**
* Returns whether the query option specifies TopN criteria.
* @arg option a query option
* @return True if the query option specifies TopN criteria
*/
isTopNOption :: QueryOption -> Boolean;
public isTopNOption !option =
case option of
TopN {} -> True;
_ -> False;
;
/**
* Information about a join between 2 tables.
*/
data public JoinInfo =
/**
* Information about a join between 2 tables.
* @arg leftTable one of the query tables to be joined
* @arg rightTable the other query table to be joined
* @arg fieldJoinExprs Boolean expressions joining fields from the tables
* @arg joinType the type of join being performed (inner, left outer, right outer, or full outer)
*/
private JoinInfo
leftTable :: !QueryTable
rightTable :: !QueryTable
fieldJoinExprs :: [TypedExpr Boolean]
joinType :: !JoinType
deriving Eq
;
/**
* Creates a join info using the query field info specified.
* The expressions provided must both be {@link QueryField@}s.
* The link expressions will be created to compare each pair of fields using
* {@code '='@}.
* If other link comparison are required, the {@link typeConstructor = JoinInfo@} can be constructed with
* these explicitly.
* @arg leftField an expression for a field in the left table to be joined
* @arg rightField an expression for a field in the right table to be joined
* @arg joinType the type of join being performed (inner, left outer, right outer, or full outer)
* @return join info between the specified table fields
*/
makeJoinInfo :: Eq a => TypedExpr a -> TypedExpr a -> JoinType -> JoinInfo;
public makeJoinInfo !leftField !rightField !joinType =
let
tableFromQueryField fieldExpr =
case (toUntypedExpr fieldExpr) of
QueryField {queryTable} -> queryTable;
_ -> error ("The join fields must be QueryFields");
;
leftTable :: QueryTable;
leftTable = tableFromQueryField leftField;
rightTable :: QueryTable;
rightTable = tableFromQueryField rightField;
fieldJoinExpr :: TypedExpr Boolean;
fieldJoinExpr = eqExpr leftField rightField;
in
JoinInfo leftTable rightTable [fieldJoinExpr] joinType;
/**
* Creates a join info between 2 tables using the specified linking expression.
* @arg leftTable one of the query tables to be joined
* @arg rightTable the other query table to be joined
* @arg linkingExpr a Boolean expression joining fields from the tables
* @arg joinType the type of join being performed (inner, left outer, right outer, or full outer)
*/
makeJoinInfo2 :: QueryTable -> QueryTable -> TypedExpr Boolean -> JoinType -> JoinInfo;
public makeJoinInfo2 !leftTable !rightTable linkingExpr !joinType =
JoinInfo leftTable rightTable [linkingExpr] joinType;
/**
* The types of joins that can be performed between two tables.
*/
data public JoinType =
/**
* The inner join option indicates that rows should be returned where
* the join condition between the table is satisfied.
*/
public InnerJoin
|
/**
* The left outer join option indicates that all rows from the left table should
* be included along with values from the right table where the join condition is satisfied.
*/
public LeftOuterJoin
|
/**
* The right outer join option indicates that all rows from the right table should
* be included along with values from the left table where the join condition is satisfied.
*/
public RightOuterJoin
|
/**
* The full outer join option indicates that all rows satisfying the join condition should be returned
* as well as any unmatched rows from both the left and right tables.
*/
public FullOuterJoin
deriving Eq, Show
;
/**
* A binary tree structure for representing joins.
*/
data public JoinNode =
/**
* A node representing a single query table.
* @arg table a query table
*/
public JoinTable
table :: QueryTable
|
/**
* A node specifying a join between tables in two join trees.
* @arg leftNode one of the join trees to be joined
* @arg rightNode the other join tree to be joined
* @arg linkingExpr a Boolean expression joining tables in the join trees
* @arg joinType the type of join to be performed
*/
public JoinSubtree
leftNode :: JoinNode
rightNode :: JoinNode
linkingExpr :: (TypedExpr Boolean)
joinType :: JoinType
deriving Eq
;
/**
* Join nodes can be shown.
*/
instance Debug.Show JoinNode where
show = showJoinNode;
;
showJoinNode :: JoinNode -> String;
private showJoinNode !node =
case node of
JoinTable joinTable -> show joinTable;
JoinSubtree {leftNode, rightNode} -> "{" ++ (showJoinNode leftNode) ++ " -> " ++ (showJoinNode rightNode) ++ "}";
;
/**
* A named set of joins.
*/
data public JoinSet =
/**
* A named set of joins.
* @arg name the name of the join set
* @arg joins the root join of the join set
*/
private JoinSet
name :: !String
joins :: JoinNode
deriving Show
;
/**
* Construct a join set from a list of JoinInfo values.
* @arg name the name of the join set
* @arg joins the joins to be part of the set
* @return a join set with the specified name
*/
makeJoinSet :: String -> [JoinInfo] -> JoinSet;
public makeJoinSet !name joins =
makeJoinSet2 name (buildJoinTree joins);
/**
* Construct a join set from a JoinNode tree
* @arg name the name of the join set
* @arg joinTree the root join of the join set
* @return a join set with the specified name
*/
makeJoinSet2 :: String -> JoinNode -> JoinSet;
public makeJoinSet2 !name joinTree =
JoinSet name joinTree;
/**
* Returns the join info from the join set.
* @arg joinSet a join set
* @return the root join node of the set
*/
joinSetJoinTree :: JoinSet -> JoinNode;
public joinSetJoinTree !joinSet =
joinSet.JoinSet.joins;
// Implement the UniquelyNamedItem class.
instance UniquelyNamedItem JoinSet where
getDisplayName = joinSetName;
getUniqueIdentifier = joinSetUniqueIdentifier;
;
joinSetName !joinSet = joinSet.JoinSet.name;
joinSetUniqueIdentifier !joinSet = makeUniqueIdentifierByName (joinSetName joinSet);
/**
* A database expression.
*/
data public Expr =
/**
* A reference to a field in a query table.
* @arg fieldName the name of the field
* @arg queryTable the query table
*/
private QueryField
fieldName :: String
queryTable :: QueryTable
|
/**
* A literal value expression.
* @arg constantValue the constant value
*/
private ConstExpr
constantValue :: ConstValue
|
/**
* A parameter placeholder expression.
* @arg parameter the database parameter
*/
private ParameterExpr
parameter :: Parameter
|
/**
* A list of database expressions.
* @arg listValues the list of expressions
*/
private ListExpr
listValues :: [Expr]
|
/**
* A function expression.
* @arg func an identifier for the database function
* @arg arguments the function argument expressions
*/
private FunctionExpr
func :: DbFunction
arguments :: [Expr]
|
/**
* A subquery expression.
* The subquery must have a single projected column.
* @arg subquery the subquery to be treated as a database expression
*/
private SubQueryExpr
subquery :: Query
deriving Eq, Show
;
/**
* Returns {@link True@} if the expression represents a list.
* @arg expr a database expression
* @return {@link True@} if the expression represents a list; {@link False@} otherwise
*/
isListExpr :: Expr -> Boolean;
public isListExpr !expr =
case expr of
ListExpr {} -> True;
_ -> False;
;
/**
* Returns the value expressions from a list expression.
* An exception will be thrown if the expression is not a list expression.
* @arg expr a database expression
* @return the list of value expressions form a list expression
*/
getListFromListExpr :: Expr -> [Expr];
public getListFromListExpr !expr =
case expr of
ListExpr {listValues} -> listValues;
_ -> error "Not a list expression";
;
/**
* Returns {@link True@} if the expression represents a constant containing a string value.
* @arg expr a database expression
* @return {@link True@} if the expression represents a string literal; {@link False@} otherwise
*/
isStringConstExpr :: Expr -> Boolean;
public isStringConstExpr !expr =
case expr of
ConstExpr {constantValue} ->
case constantValue of
StringValue {} -> True;
_ -> False;
;
_ -> False;
;
/**
* Returns the string value from a constant expression.
* An exception will be thrown if the expression is not a string constant expression.
* @arg expr a database expression
* @return the string value from the constant database expression
*/
getStringValueFromConstExpr :: Expr -> String;
public getStringValueFromConstExpr !expr =
case expr of
ConstExpr {constantValue} ->
case constantValue of
StringValue {strValue} -> strValue;
_ -> error "Not a string constant expression";
;
_ -> error "Not a constant expression";
;
/**
* Returns {@link True@} if the expression represents a constant containing a number value.
* @arg expr a database expression
* @return {@link True@} if the expression represents a numeric literal; {@link False@} otherwise
*/
isNumberConstExpr :: Expr -> Boolean;
public isNumberConstExpr !expr =
case expr of
ConstExpr {constantValue} ->
case constantValue of
NumberValue {} -> True;
_ -> False;
;
_ -> False;
;
/**
* Returns the number value from a constant expression.
* An exception will be thrown if the expression is not a numeric constant expression.
* @arg expr a database expression
* @return the numeric value from the constant database expression
*/
getNumberValueFromConstExpr :: Expr -> Double;
public getNumberValueFromConstExpr !expr =
case expr of
ConstExpr {constantValue} ->
case constantValue of
NumberValue {numValue} -> numValue;
_ -> error "Not a number constant expression";
;
_ -> error "Not a constant expression";
;
/**
* Returns {@link True@} if the expression represents a constant containing a boolean value.
* @arg expr a database expression
* @return {@link True@} if the expression represents a Boolean constant; {@link False@} otherwise
*/
isBooleanConstExpr :: Expr -> Boolean;
public isBooleanConstExpr !expr =
case expr of
ConstExpr {constantValue} ->
case constantValue of
BooleanValue {} -> True;
_ -> False;
;
_ -> False;
;
/**
* Returns the boolean value from a constant expression.
* An exception will be thrown if the expression is not a constant Boolean expression.
* @arg expr a database expression
* @return the Boolean value from the constant database expression
*/
getBooleanValueFromConstExpr :: Expr -> Boolean;
public getBooleanValueFromConstExpr !expr =
case expr of
ConstExpr {constantValue} ->
case constantValue of
BooleanValue {boolValue} -> boolValue;
_ -> error "Not a boolean constant expression";
;
_ -> error "Not a constant expression";
;
/**
* Returns {@link True@} if the expression represents a constant containing a time value.
* @arg expr a database expression
* @return {@link True@} if the expression represents a time constant; {@link False@} otherwise
*/
isTimeConstExpr :: Expr -> Boolean;
public isTimeConstExpr !expr =
case expr of
ConstExpr {constantValue} ->
case constantValue of
TimeValue {} -> True;
_ -> False;
;
_ -> False;
;
/**
* Returns the time value from a constant expression.
* An exception will be thrown if the expression is not a time constant expression.
* @arg expr a database expression
* @return the time value from the constant database expression
*/
getTimeValueFromConstExpr :: Expr -> Time;
public getTimeValueFromConstExpr !expr =
case expr of
ConstExpr {constantValue} ->
case constantValue of
TimeValue {timeValue} -> timeValue;
_ -> error "Not a time constant expression";
;
_ -> error "Not a constant expression";
;
/**
* Returns {@link True@} if the expression represents a field in a query table.
* @arg expr a database expression
* @return {@link True@} if the expression represents a field in a query table; {@link False@} otherwise
*/
isQueryFieldExpr :: Expr -> Boolean;
public isQueryFieldExpr !expr =
case expr of
QueryField {} -> True;
_ -> False;
;
/**
* Returns the query table from a query field expression.
* An exception will be thrown if the expression is not a query field expression.
* @arg expr a database expression
* @return the field name from the query field expression
*/
getFieldNameFromQueryFieldExpr :: Expr -> String;
public getFieldNameFromQueryFieldExpr !expr =
case expr of
QueryField {fieldName} -> fieldName;
_ -> error "Not a query field expression";
;
/**
* Returns the field name from a query field expression.
* An exception will be thrown if the expression is not a query field expression.
* @arg expr a database expression
* @return the query table from the query field expression
*/
getQueryTableFromQueryFieldExpr :: Expr -> QueryTable;
public getQueryTableFromQueryFieldExpr !expr =
case expr of
QueryField {queryTable} -> queryTable;
_ -> error "Not a query field expression";
;
/**
* Returns {@link True@} if the expression represents a function (or operator)
* application.
* @arg expr a database expression
* @return {@link True@} if the expression represents a database function expression; {@link False@} otherwise
*/
isFunctionExpr :: Expr -> Boolean;
public isFunctionExpr !expr =
case expr of
FunctionExpr {} -> True;
_ -> False;
;
/**
* Returns the function type from a function expression.
* An exception will be thrown if the expression is not a function expression.
* @arg expr a database expression
* @return the function identifier from the function expression
*/
getFunctionFromFunctionExpr :: Expr -> DbFunction;
public getFunctionFromFunctionExpr !expr =
case expr of
FunctionExpr {func} -> func;
_ -> error "Not a function expression";
;
/**
* Returns the arguments from a function expression.
* An exception will be thrown if the expression is not a function expression.
* @arg expr a database expression
* @return the argument expressions from the function expression
*/
getArgumentsFromFunctionExpr :: Expr -> [Expr];
public getArgumentsFromFunctionExpr !expr =
case expr of
FunctionExpr {arguments} -> arguments;
_ -> error "Not a function expression";
;
/**
* Returns whether the expression represents an operator applied to one or more
* arguments.
* @arg expr a database expression
* @return {@link True@} if the expression represents an operator applied to one or
* more arguments; {@link False@} otherwise
*/
isOperatorExpr :: Expr -> Boolean;
public isOperatorExpr !expr =
case expr of
FunctionExpr {func} -> isOperator func;
_ -> False;
;
/**
* {@code TypedExpr@} wraps an untyped {@link Expr@} and adds information about the expression
* data type.
*/
data public TypedExpr a =
private TypedExpr
untypedExpr :: Expr
;
/**
* Returns the untyped expression from a typed expression.
* @arg typedExpr a typed database expression
* @return the untyped database expression
*/
toUntypedExpr :: TypedExpr a -> Expr;
public toUntypedExpr !typedExpr = typedExpr.TypedExpr.untypedExpr;
/**
* Returns a typed expression from the untyped expression.
* @arg untypedExpr an untyped database expression
* @return the typed database expression
*/
toTypedExpr :: Expr -> TypedExpr a;
public toTypedExpr = TypedExpr;
/**
* Returns the type of the values represented by this typed expression.
* @arg expr a database expression
* @return the type of the database expression
*/
typeOfExpr :: Prelude.Typeable a => TypedExpr a -> Prelude.TypeRep;
public typeOfExpr !expr =
head (Prelude.typeArguments (Prelude.typeOf expr));
// Allow typed expressions to be compared for equality.
instance Eq (TypedExpr a) where
equals = equalsTypedExpr;
notEquals = notEqualsTypedExpr;
;
// Allow typed expressions to be shown.
instance Show (TypedExpr a) where
show = showTypedExpr;
;
showTypedExpr :: TypedExpr a -> String;
showTypedExpr !typedExpr =
show typedExpr.TypedExpr.untypedExpr;
/**
* Checks whether 2 typed expressions are equal.
*/
private equalsTypedExpr !expr1 !expr2 =
(toUntypedExpr expr1) == (toUntypedExpr expr2);
private notEqualsTypedExpr !expr1 !expr2 =
not (equalsTypedExpr expr1 expr2);
/**
* A constant value.
*/
data private ConstValue =
private NullValue
|
private StringValue
strValue::String
|
private NumberValue
numValue::Double
|
private BooleanValue
boolValue::Boolean
|
private TimeValue
timeValue::Time
deriving Eq, Show
;
/**
* Construct a database expression for a string literal.
* @arg strValue the string literal value
* @return the database expression for the string literal
*/
stringConstant :: String -> TypedExpr String;
public stringConstant strValue =
TypedExpr (ConstExpr (StringValue strValue));
/**
* Construct a database expression for a numeric literal.
* @arg numValue the numeric literal value
* @return the database expression for the numeric literal
*/
numericConstant :: Num a => a -> TypedExpr a;
public numericConstant numValue =
TypedExpr (ConstExpr (NumberValue (toDouble numValue)));
/**
* Construct a database expression for a Boolean value.
* @arg boolValue the Boolean value
* @return the database expression for the Boolean value
*/
booleanConstant :: Boolean -> TypedExpr Boolean;
public booleanConstant !boolValue =
if boolValue then trueConstant else falseConstant;
/**
* A database expression for the Boolean value True.
* @return the database expression for the Boolean value True
*/
trueConstant :: TypedExpr Boolean;
public trueConstant =
TypedExpr (ConstExpr (BooleanValue True));
/**
* A database expression for the Boolean value False.
* @return the database expression for the Boolean value False
*/
falseConstant :: TypedExpr Boolean;
public falseConstant =
TypedExpr (ConstExpr (BooleanValue False));
/**
* Construct a database expression for a time literal.
* @arg timeValue the time literal value
* @return the database expression for the time literal
*/
timeConstant :: Time -> TypedExpr Time;
public timeConstant timeValue =
TypedExpr (ConstExpr (TimeValue timeValue));
/**
* A database expression for a null value of some data type.
* @return a database expression for a null value
*/
nullValue :: TypedExpr a;
public nullValue =
TypedExpr (ConstExpr NullValue);
/**
* Tests whether an untyped expression has the constant value of null.
* This does not check whether an expression evaluates to null -- it simply
* checks whether it has the constant value of null.
* @arg expr a database expression
* @return True if the expression has the constant value of null
*/
isNullValue :: Expr -> Boolean;
public isNullValue !expr =
case expr of
ConstExpr {constantValue} ->
case constantValue of
NullValue -> True;
_ -> False;
;
_ -> False;
;
/**
* A database parameter that can be used in place of a value in a SQL statement.
* The parameter values can be bound in when executing the SQL statement.
* This is often used to execute the same statement multiple times with different values.
*/
data private Parameter =
/**
* A parameter without a name.
* The values for unnamed parameters will be bound based on the position of the
* parameter in the SQL statement.
*/
private UnnamedParameter
|
/**
* A parameter with an associated name.
* Note: JDBC 3.0 only supports named parameter for CallableStatement.
*/
private NamedParameter
name :: String
deriving Eq, Show
;
/**
* Returns whether the parameter is a named one.
* @arg param a database parameter
* @return True if the parameter is named one; False if the parameter is unnamed
*/
isUnnamedParameter :: Parameter -> Boolean;
public isUnnamedParameter !param =
case param of
UnnamedParameter -> True;
_ -> False;
;
/**
* Returns the name of a named parameter.
* An error will be thrown if the parameter is unnamed.
* @arg param a database parameter
* @return the name of the named database parameter
*/
getParameterName :: Parameter -> String;
public getParameterName !param =
case param of
NamedParameter {name} -> name;
_ -> error "Only a named parameter has a name!";
;
/**
* Constructs an string expression for a database parameter.
* If Nothing is specified for the name, then the parameter will be an unnamed one.
* @arg maybeParamName an optional name for the parameter
* @return a string database expression for a database parameter
*/
stringParameter :: Maybe String -> TypedExpr String;
public stringParameter !maybeParamName = TypedExpr $ untypedParameter maybeParamName;
/**
* Constructs an integer expression for a database parameter.
* If Nothing is specified for the name, then the parameter will be an unnamed one.
* @arg maybeParamName an optional name for the parameter
* @return a integer database expression for a database parameter
*/
integerParameter :: Maybe String -> TypedExpr Int;
public integerParameter !maybeParamName = TypedExpr $ untypedParameter maybeParamName;
/**
* Constructs an double expression for a database parameter.
* If Nothing is specified for the name, then the parameter will be an unnamed one.
* @arg maybeParamName an optional name for the parameter
* @return a double database expression for a database parameter
*/
doubleParameter :: Maybe String -> TypedExpr Double;
public doubleParameter !maybeParamName = TypedExpr $ untypedParameter maybeParamName;
/**
* Constructs an Boolean expression for a database parameter.
* If Nothing is specified for the name, then the parameter will be an unnamed one.
* @arg maybeParamName an optional name for the parameter
* @return a Boolean database expression for a database parameter
*/
booleanParameter :: Maybe String -> TypedExpr Boolean;
public booleanParameter !maybeParamName = TypedExpr $ untypedParameter maybeParamName;
/**
* Constructs an time expression for a database parameter.
* If Nothing is specified for the name, then the parameter will be an unnamed one.
* @arg maybeParamName an optional name for the parameter
* @return a time database expression for a database parameter
*/
timeParameter :: Maybe String -> TypedExpr Time;
public timeParameter !maybeParamName = TypedExpr $ untypedParameter maybeParamName;
/**
* Constructs an untyped expression for a database parameter.
* If Nothing is specified for the name, then the parameter will be an unnamed one.
* @arg maybeParamName an optional name for the parameter
* @return a database expression for a database parameter
*/
untypedParameter :: Maybe String -> Expr;
private untypedParameter !maybeParamName =
case maybeParamName of
Just paramName -> ParameterExpr $ NamedParameter paramName;
Nothing -> ParameterExpr $ UnnamedParameter;
;
/**
* Constructs an untyped database expression for a list of values.
* @arg listValues a list of database expressions for the list value
* @return an untyped database expression for the list of values
*/
untypedListExpr :: [Expr] -> Expr;
public untypedListExpr listValues = ListExpr listValues;
/**
* Constructs a typed database expression for a list of values.
* This probably doesn't need to be exposed since it can only be used with
* the IN binary operator anyway.
* @arg listValues a list of database expressions for the list value
* @return a typed database expression for the list of values
*/
listExpr :: [TypedExpr a] -> TypedExpr [a];
private listExpr listValues = TypedExpr (ListExpr (map toUntypedExpr listValues));
/**
* Creates a database expression based on a subquery.
* Any ordering will be removed from the subquery (unless it uses {@link TopN@}).
* The subquery must have a single projected column.
* @arg subquery the subquery to be treated as a database expression
* @return a database expression based on the subquery
*/
subQueryExpr :: Query -> Expr;
public subQueryExpr !subquery =
let
// Remove any ordering from the subquery (unless it uses TopN).
fixedSubquery = fixSubqueryOrdering subquery;
in
// Check that there is only 1 return column in the subquery.
// TODO: is there a way to make this typed based on the (one and only) result column in the query?
if (isSingletonList (getProjectedColumns fixedSubquery)) then SubQueryExpr fixedSubquery
else error "Only subqueries returning a single column can be used as field expressions";
/**
* Database expression functions and operators.
*/
data public DbFunction =
// Unary operators.
public OpNot | public OpBitNot | public OpNegate
| public OpIsNull | public OpIsNotNull
| public OpExists
// Binary operators
| public OpEq | public OpLt | public OpLtEq | public OpGt | public OpGtEq | public OpNotEq
| public OpAnd | public OpOr
| public OpLike | public OpIn
| public OpCat
| public OpPlus | public OpMinus | public OpMul | public OpDiv | public OpMod
| public OpBitAnd | public OpBitOr | public OpBitXor
// Other operators
| public OpBetween | public OpCase_Simple | public OpCase_Searched
// Conversion functions
| public ConvertToStringFunction
| public ConvertToIntFunction
| public ConvertToDoubleFunction
| public ConvertToTimeFunction
// Numeric functions
| public AbsFunction | public AcosFunction | public AsinFucntion | public AtanFunction | public Atan2Function
| public CeilingFunction | public CosFunction | public CotFunction | public DegreesFunction | public ExpFunction
| public FloorFunction | public LogFunction | public Log10Function | public ModFunction | public PiFunction
| public PowerFunction | public RadiansFunction | public RandFunction | public RoundFunction | public SignFunction
| public SinFunction | public SqrtFunction | public TanFunction | public TruncateFunction
// String functions
| public AsciiFunction | public CharFunction | public DifferenceFunction
| public InsertFunction| public LcaseFunction | public LeftFunction | public LengthFunction | public LocateFunction
| public LtrimFunction | public RepeatFunction | public ReplaceFunction | public RightFunction | public RtrimFunction
| public SoundexFunction | public SpaceFunction | public SubstringFunction | public UcaseFunction
// System functions
| public DatabaseFunction | public UserFunction
// Null-handling functions
| public IfNullFunction | public NullIfFunction
// DateTime functions
| public DayNameFunction | public DayOfWeekFunction | public DayOfMonthFunction | public DayOfYearFunction | public HourFunction | public MinuteFunction
| public MonthFunction | public MonthNameFunction | public NowFunction | public QuarterFunction | public SecondFunction
| public WeekFunction | public YearFunction
| public DateTimeAddFunction timeInterval :: TimeInterval
| public DateTimeDiffFunction timeInterval :: TimeInterval
// Aggregation functions.
| public AggrCount | public AggrSum | public AggrAvg | public AggrMin | public AggrMax
| public AggrDistinctCount | public AggrDistinctSum | public AggrDistinctAvg
| public AggrCountAll
| public AggrStdDev | public AggrStdDevP | public AggrVar | public AggrVarP
| public AggrOther other :: String
// A function with the specified name.
| public FunctionOther funcName :: String
deriving Eq, Show
;
/**
* Possible time interval values for working with date/time values.
*/
data public TimeInterval =
public YearInterval
| public QuarterInterval
| public MonthInterval
| public DayInterval
| public HourInterval
| public MinuteInterval
| public SecondInterval
deriving Eq, Show
;
/**
* Indicates whether a specified function is an aggregation function or not.
* @arg fn a database function identifier
* @return True if the specified function is an aggregation function
*/
isAggregationFunction :: DbFunction -> Boolean;
public isAggregationFunction !fn =
case fn of
(AggrCount |
AggrSum |
AggrAvg |
AggrMin |
AggrMax |
AggrDistinctCount |
AggrDistinctSum |
AggrDistinctAvg |
AggrCountAll |
AggrStdDev |
AggrStdDevP |
AggrVar |
AggrVarP |
AggrOther) {} -> True;
_ -> False;
;
/**
* Returns the default name of a database function.
*/
defaultFunctionName :: DbFunction -> String;
public defaultFunctionName !func =
case func of
// ConvertFunction -> "CONVERT";
AbsFunction -> "ABS";
AcosFunction -> "ACOS";
AsinFucntion -> "ASIN";
AtanFunction -> "ATAN";
Atan2Function -> "ATAN2";
CeilingFunction -> "CEILING";
CosFunction -> "COS";
CotFunction -> "COT";
DegreesFunction -> "DEGREES";
ExpFunction -> "EXP";
FloorFunction -> "FLOOR";
LogFunction -> "LOG";
Log10Function -> "LOG10";
ModFunction -> "MOD";
PiFunction -> "PI";
PowerFunction -> "POWER";
RadiansFunction -> "RADIANS";
RandFunction -> "RAND";
RoundFunction -> "ROUND";
SignFunction -> "SIGN";
SinFunction -> "SIN";
SqrtFunction -> "SQRT";
TanFunction -> "TAN";
TruncateFunction -> "TRUNCATE";
AsciiFunction -> "ASCII";
CharFunction -> "CHAR";
// TODO: use the OpCat operator instead...
// ConcatFunction -> "CONCAT";
DifferenceFunction -> "DIFFERENCE";
InsertFunction -> "INSERT";
LcaseFunction -> "LCASE";
LeftFunction -> "LEFT";
LengthFunction -> "LENGTH";
LocateFunction -> "LOCATE";
LtrimFunction -> "LTRIM";
RepeatFunction -> "REPEAT";
ReplaceFunction -> "REPLACE";
RightFunction -> "RIGHT";
RtrimFunction -> "RTRIM";
SoundexFunction -> "SOUNDEX";
SpaceFunction -> "SPACE";
SubstringFunction -> "SUBSTRING";
UcaseFunction -> "UCASE";
DatabaseFunction -> "DATABASE";
IfNullFunction -> "IFNULL";
NullIfFunction -> "NULLIF";
UserFunction -> "USER";
DayNameFunction -> "DAYNAME";
DayOfWeekFunction -> "WEEKDAY";
DayOfMonthFunction -> "DAY";
DayOfYearFunction -> "DAYOFYEAR";
HourFunction -> "HOUR";
MinuteFunction -> "MINUTE";
MonthFunction -> "MONTH";
MonthNameFunction -> "MONTHNAME";
NowFunction -> "NOW";
QuarterFunction -> "QUARTER";
SecondFunction -> "SECOND";
// TimestampAddFunction -> "TIMESTAMPADD";
// TimestampDiffFunction -> "TIMESTAMPDIFF";
WeekFunction -> "WEEK";
YearFunction -> "YEAR";
DateTimeAddFunction {} -> "DATEADD";
DateTimeDiffFunction {} -> "DATEDIFF";
AggrCount -> "COUNT";
AggrSum -> "SUM";
AggrAvg -> "AVG";
AggrMin -> "MIN";
AggrMax -> "MAX";
AggrStdDev -> "STDDEV";
AggrStdDevP -> "STDDEVP";
AggrVar -> "VAR";
AggrVarP -> "VARP";
AggrOther {other} -> other;
// Note: the following block of functions are generated specially, but names are included here for
// purposes of outputing to Java.
AggrDistinctCount -> "DISTINCT COUNT";
AggrDistinctSum -> "DISTINCT SUM";
AggrDistinctAvg -> "DISTINCT AVG";
AggrCountAll -> "COUNT ALL";
FunctionOther {funcName} -> funcName;
;
//////////////////////////////////////////////////////////////////////////////////////////////////////
// SQL Functions
/**
* Constructs an untyped database expression for a call to a database function.
* No checking will be done for the number or types of the arguments.
* Where possible the appropriate type-safe function should be called to construct the
* function expression.
* @arg func an identifier for a database function
* @arg arguments a list of database expressions for the function arguments
* @return an untyped database expression for a call to the database function
*/
untypedFunctionExpr :: DbFunction -> [Expr] -> Expr;
public untypedFunctionExpr func arguments = FunctionExpr func arguments;
// Helper functions to construct typed function expressions.
// These should not be exposed.
functionExpr0 :: DbFunction -> TypedExpr a;
private functionExpr0 func = TypedExpr (untypedFunctionExpr func []);
functionExpr1 :: DbFunction -> TypedExpr a -> TypedExpr b;
private functionExpr1 func arg1 = TypedExpr (untypedFunctionExpr func [toUntypedExpr arg1]);
functionExpr2 :: DbFunction -> TypedExpr a -> TypedExpr b -> TypedExpr c;
private functionExpr2 func arg1 arg2 = TypedExpr (untypedFunctionExpr func [toUntypedExpr arg1, toUntypedExpr arg2]);
functionExpr3 :: DbFunction -> TypedExpr a -> TypedExpr b -> TypedExpr c -> TypedExpr d;
private functionExpr3 func arg1 arg2 arg3 = TypedExpr (untypedFunctionExpr func [toUntypedExpr arg1, toUntypedExpr arg2, toUntypedExpr arg3]);
functionExpr4 :: DbFunction -> TypedExpr a -> TypedExpr b -> TypedExpr c -> TypedExpr d -> TypedExpr e;
private functionExpr4 func arg1 arg2 arg3 arg4 = TypedExpr (untypedFunctionExpr func [toUntypedExpr arg1, toUntypedExpr arg2, toUntypedExpr arg3, toUntypedExpr arg4]);
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Unary operator expressions
/**
* Constructs an untyped database expression for the application of a unary operator.
* No checking will be done for the type of the argument.
* Where possible the appropriate type-safe function should be called to construct the
* function expression.
* @arg func an identifier for a unary operator
* @arg argument a database expression to which the unary operator will be applied
* @return an untyped database expression for the application of the unary operator
*/
untypedUnaryExpr :: DbFunction -> Expr -> Expr;
public untypedUnaryExpr func argument = FunctionExpr func [argument];
/**
* Constructs a database expression for the application of the 'not' operator to a Boolean argument.
* @arg boolExpr a Boolean database expression
* @return a Boolean database expression for the application of the 'not' operator to the argument expression
*/
notExpr :: TypedExpr Boolean -> TypedExpr Boolean;
public notExpr = functionExpr1 OpNot;
/**
* Constructs a database expression for the application of the bitwise 'not' operator to an integer argument.
* @arg intExpr an integer database expression
* @return an integer database expression for the application of the bitwise 'not' operator to the argument expression
*/
bitwiseNotExpr :: TypedExpr Int -> TypedExpr Int;
public bitwiseNotExpr = functionExpr1 OpBitNot;
/**
* Constructs a database expression for the negation of the numeric argument expression.
* @arg numExpr a numeric database expression
* @return a numeric database expression for the negation of the argument expression
*/
negateExpr :: Num a => TypedExpr a -> TypedExpr a;
public negateExpr = functionExpr1 OpNegate;
/**
* Constructs a database expression to test whether an argument expression is null.
* @arg expr a database expression
* @return a Boolean database expression to test test whether an argument expression is null
*/
isNullExpr :: TypedExpr a -> TypedExpr Boolean;
public isNullExpr = functionExpr1 OpIsNull;
/**
* Constructs a database expression to test whether an argument expression is non-null.
* @arg expr a database expression
* @return a Boolean database expression to test test whether an argument expression is non-null
*/
isNotNullExpr :: TypedExpr a -> TypedExpr Boolean;
public isNotNullExpr = functionExpr1 OpIsNotNull;
/**
* Constructs a database expression to test whether the argument subquery expression returns any rows.
* @arg subqueryExpr a subquery expressoin
* @return a Boolean database expression to test whether the argument subquery expression returns any rows
*/
existsExpr :: TypedExpr [a] -> TypedExpr Boolean;
public existsExpr subqueryExpr =
// TODO: is there any way to make this only accept subqueries?
functionExpr1 OpExists subqueryExpr;
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Binary operator expressions
/**
* Constructs an untyped database expression for the application of a binary operator.
* No checking will be done for the types of the arguments.
* Where possible the appropriate type-safe function should be called to construct the
* function expression.
* @arg func an identifier for a binary operator
* @arg leftArgument a database expression for the left argument to which the operator will be applied
* @arg rightArgument a database expression for the right argument to which the operator will be applied
* @return an untyped database expression for the application of the binary operator
*/
untypedBinaryExpr :: DbFunction -> Expr -> Expr -> Expr;
public untypedBinaryExpr func leftArgument rightArgument = FunctionExpr func [leftArgument, rightArgument];
/**
* Constructs a database expression which test two operand expressions for equality.
* @arg expr1 a database expression for the first operand
* @arg expr2 a database expression for the second operand
* @return a Boolean database expression to test the operand expressions for equality
*/
eqExpr :: Eq a => TypedExpr a -> TypedExpr a -> TypedExpr Boolean;
public eqExpr = functionExpr2 OpEq;
/**
* Constructs a database expression which tests two operand expressions for inequality.
* @arg expr1 a database expression for the first operand
* @arg expr2 a database expression for the second operand
* @return a Boolean database expression to test the operand expressions for inequality
*/
notEqExpr :: Eq a => TypedExpr a -> TypedExpr a -> TypedExpr Boolean;
public notEqExpr = functionExpr2 OpNotEq;
/**
* Constructs a database expression to test whether the first operand is less than the second.
* @arg expr1 a database expression for the first operand
* @arg expr2 a database expression for the second operand
* @return a Boolean database expression to test whether the first operand is less than the second
*/
ltExpr :: Ord a => TypedExpr a -> TypedExpr a -> TypedExpr Boolean;
public ltExpr = functionExpr2 OpLt;
/**
* Constructs a database expression to test whether the first operand is greater than the second.
* @arg expr1 a database expression for the first operand
* @arg expr2 a database expression for the second operand
* @return a Boolean database expression to test whether the first operand is greater than the second
*/
gtExpr :: Ord a => TypedExpr a -> TypedExpr a -> TypedExpr Boolean;
public gtExpr = functionExpr2 OpGt;
/**
* Constructs a database expression to test whether the first operand is less than or equal to the second.
* @arg expr1 a database expression for the first operand
* @arg expr2 a database expression for the second operand
* @return a Boolean database expression to test whether the first operand is less than or equal to the second
*/
ltEqExpr :: Ord a => TypedExpr a -> TypedExpr a -> TypedExpr Boolean;
public ltEqExpr = functionExpr2 OpLtEq;
/**
* Constructs a database expression to test whether the first operand is greater than or equal to the second.
* @arg expr1 a database expression for the first operand
* @arg expr2 a database expression for the second operand
* @return a Boolean database expression to test whether the first operand is greater than or equal to the second
*/
gtEqExpr :: Ord a => TypedExpr a -> TypedExpr a -> TypedExpr Boolean;
public gtEqExpr = functionExpr2 OpGtEq;
/**
* Constructs a database expression to test whether both of the operand expressions are True.
* @arg b1 a database expression for the first operand
* @arg b2 a database expression for the second operand
* @return a Boolean database expression to test whether both of the operand expressions are True
*/
andExpr :: TypedExpr Boolean -> TypedExpr Boolean -> TypedExpr Boolean;
public andExpr !b1 b2 =
if (b1 == trueConstant) then b2
else if (b2 == trueConstant) then b1
else if (b1 == falseConstant || b2 == falseConstant) then falseConstant
else functionExpr2 OpAnd b1 b2;
/**
* Constructs a database expression to test whether either of the operand expressions are True.
* @arg b1 a database expression for the first operand
* @arg b2 a database expression for the second operand
* @return a Boolean database expression to test whether either of the operand expressions are True
*/
orExpr :: TypedExpr Boolean -> TypedExpr Boolean -> TypedExpr Boolean;
public orExpr b1 b2 =
if (b1 == falseConstant) then b2
else if (b2 == falseConstant) then b1
else if (b1 == trueConstant || b2 == trueConstant) then trueConstant
else functionExpr2 OpOr b1 b2;
/**
* Constructs a database expression to test whether the first string operand expression matches
* the pattern of the second operand expression.
* @arg strExpr a string database expression for the first operand
* @arg patternExpr a string database expression for the pattern against which the first operand will be tested
* @return a Boolean database expression to test whether first string operand expression matches
* the pattern of the second operand expression
*/
likeExpr :: TypedExpr String -> TypedExpr String -> TypedExpr Boolean;
public likeExpr = functionExpr2 OpLike;
/**
* Constructs a database expression to test whether the value of the first operand is equal to the value
* returned by one of the list operand expressions.
* @arg leftExpr a database expression for the first operand
* @arg listValueExprs a database expression for the values against the first operand will be tested
* @return a Boolean database expression to test whether the value of the first operand is one of
* the values returned by the list operand expression
*/
inExpr :: Eq a => TypedExpr a -> [TypedExpr a] -> TypedExpr Boolean;
public inExpr leftExpr listValueExprs = functionExpr2 OpIn leftExpr (listExpr listValueExprs);
/**
* Constructs a database expression to test whether the value of the first operand is one of the values
* returned by the list operand expression.
* @arg leftExpr a database expression for the first operand
* @arg listValuesExpr a database expression for the values against the first operand will be tested
* @return a Boolean database expression to test whether the value of the first operand is one of
* the values returned by the list operand expression
*/
inExpr2 :: Eq a => TypedExpr a -> TypedExpr [a] -> TypedExpr Boolean;
public inExpr2 leftExpr listValuesExpr = functionExpr2 OpIn leftExpr listValuesExpr;
/**
* Constructs a database expression to concatenate two string operand expressions.
* @arg expr1 a string database expression for the first operand
* @arg expr2 a string database expression for the second operand
* @return a string database expression to concatenate the string operand expressions
*/
concatExpr :: TypedExpr String -> TypedExpr String -> TypedExpr String;
public concatExpr = functionExpr2 OpCat;
/**
* A helper function for constructing a numeric binary operator.
* Do not expose this.
*/
numBinaryExpr :: Num a => DbFunction -> TypedExpr a -> TypedExpr a -> TypedExpr a;
private numBinaryExpr = functionExpr2;
/**
* Constructs a database expression to add two numeric operand expressions.
* @arg expr1 a numeric database expression for the first operand
* @arg expr2 a numeric database expression for the second operand
* @return a numeric database expression to add the two numeric operand expressions
*/
public addExpr = numBinaryExpr OpPlus;
/**
* Constructs a database expression to subtract two numeric operand expressions.
* @arg expr1 a numeric database expression for the first operand
* @arg expr2 a numeric database expression for the second operand
* @return a numeric database expression to subtract the two numeric operand expressions
*/
public subtractExpr = numBinaryExpr OpMinus;
/**
* Constructs a database expression to multiply two numeric operand expressions.
* @arg expr1 a numeric database expression for the first operand
* @arg expr2 a numeric database expression for the second operand
* @return a numeric database expression to multiply the two numeric operand expressions
*/
public multiplyExpr = numBinaryExpr OpMul;
/**
* Constructs a database expression to divide two numeric operand expressions.
* @arg expr1 a numeric database expression for the first operand
* @arg expr2 a numeric database expression for the second operand
* @return a numeric database expression to divide the two numeric operand expressions
*/
public divideExpr = numBinaryExpr OpDiv;
/**
* Constructs a database expression to return the modulus of two numeric operand expressions.
* @arg expr1 a numeric database expression for the first operand
* @arg expr2 a numeric database expression for the second operand
* @return a numeric database expression to return the modulus of the two numeric operand expressions
*/
public modulusExpr = numBinaryExpr OpMod;
/**
* A helper function for constructing an integer binary operator.
* Do not expose this.
*/
intBinaryExpr :: DbFunction -> TypedExpr Int -> TypedExpr Int -> TypedExpr Int;
private intBinaryExpr = functionExpr2;
/**
* Constructs a database expression to perform a bitwise 'and' of two numeric operand expressions.
* @arg expr1 an integer database expression for the first operand
* @arg expr2 an integer database expression for the second operand
* @return an integer database expression to perform a bitwise 'and' of two numeric operand expressions
*/
public bitwiseAndExpr = intBinaryExpr OpBitAnd;
/**
* Constructs a database expression to perform a bitwise 'or' of two numeric operand expressions.
* @arg expr1 an integer database expression for the first operand
* @arg expr2 an integer database expression for the second operand
* @return an integer database expression to perform a bitwise 'or' of two numeric operand expressions
*/
public bitwiseOrExpr = intBinaryExpr OpBitOr;
/**
* Constructs a database expression to perform a bitwise 'xor' of two numeric operand expressions.
* @arg expr1 an integer database expression for the first operand
* @arg expr2 an integer database expression for the second operand
* @return an integer database expression to perform a bitwise 'xor' of two numeric operand expressions
*/
public bitwiseXorExpr = intBinaryExpr OpBitXor;
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Other operator expressions
/**
* Constructs a database expression which test whether the value of one operand expression is between
* the value of two other operand expressions.
* @arg leftExpr a database expression to be tested
* @arg lowerExpr a database expression for the lower bound of the value range
* @arg upperExpr a database expression for the upper bound of the value range
* @return a Boolean database expression to test whether the value of one operand expression is between
* the value of two other operand expressions
*/
betweenExpr :: Ord a => TypedExpr a -> TypedExpr a -> TypedExpr a -> TypedExpr Boolean;
public betweenExpr leftExpr lowerExpr upperExpr = functionExpr3 OpBetween leftExpr lowerExpr upperExpr;
/**
* Constructs a database expression for a simple case statement.
* When this expression is evaluated, the result value of the matching WHEN clause will be returned.
* If none of the WHEN values match the case value, then the ELSE value will be returned.
* @arg caseExpr a database expression for the value to be tested
* @arg whenValueAndResults a list of pairs of comparison values and the corresponding result values
* @arg elseValue a database expression for the value to be returned if none of the WHEN clauses match
* @return a database expression for a simple case statement
*/
simpleCaseExpr :: Eq a => TypedExpr a -> [(TypedExpr a, TypedExpr b)] -> Maybe (TypedExpr b) -> TypedExpr b;
public simpleCaseExpr caseExpr whenValueAndResults elseValue =
let
unTypedExprs = toUntypedExpr caseExpr
: concatMap (\pr -> [toUntypedExpr (fst pr), toUntypedExpr (snd pr)]) whenValueAndResults
++ map toUntypedExpr (maybeToList elseValue);
in
TypedExpr (untypedFunctionExpr OpCase_Simple unTypedExprs);
/**
* Construct a database expression for a searched case statement.
* Each WHEN clause will contain a Boolean expression and a result value.
* The result will be the result value for the first WHEN clause with a condition that returns True.
* If none of the WHEN clause expressions return True, then the ELSE value will be returned.
* @arg whenConditionAndResults a list of pairs of Boolean condition expressions and result values
* @arg elseValue a database expression for the value to be returned if none of the WHEN clauses evaluate to True
* @return a database expression for a searched case statement
*/
searchedCaseExpr :: [(TypedExpr Boolean, TypedExpr b)] -> Maybe (TypedExpr b) -> TypedExpr b;
public searchedCaseExpr whenConditionAndResults elseValue =
let
unTypedExprs = concatMap (\pr -> [toUntypedExpr (fst pr), toUntypedExpr (snd pr)]) whenConditionAndResults
++ map toUntypedExpr (maybeToList elseValue);
in
TypedExpr (untypedFunctionExpr OpCase_Searched unTypedExprs);
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Numeric functions
absExpr :: Num a => TypedExpr a -> TypedExpr a;
public absExpr = functionExpr1 AbsFunction;
acosExpr :: TypedExpr Double -> TypedExpr Double;
public acosExpr = functionExpr1 AcosFunction;
asinExpr :: TypedExpr Double -> TypedExpr Double;
public asinExpr = functionExpr1 AsinFucntion;
atanExpr :: TypedExpr Double -> TypedExpr Double;
public atanExpr = functionExpr1 AtanFunction;
atan2Expr :: TypedExpr Double -> TypedExpr Double -> TypedExpr Double;
public atan2Expr = functionExpr2 Atan2Function;
ceilingExpr :: Num a => TypedExpr a -> TypedExpr Int;
public ceilingExpr = functionExpr1 CeilingFunction;
cosExpr :: TypedExpr Double -> TypedExpr Double;
public cosExpr = functionExpr1 CosFunction;
cotExpr :: TypedExpr Double -> TypedExpr Double;
public cotExpr = functionExpr1 CotFunction;
degreesExpr :: TypedExpr Double -> TypedExpr Double;
public degreesExpr = functionExpr1 DegreesFunction;
expExpr :: Num a => TypedExpr a -> TypedExpr Double;
public expExpr = functionExpr1 ExpFunction;
floorExpr :: Num a => TypedExpr a -> TypedExpr Int;
public floorExpr = functionExpr1 FloorFunction;
logExpr :: Num a => TypedExpr a -> TypedExpr Double;
public logExpr = functionExpr1 LogFunction;
log10Expr :: Num a => TypedExpr a -> TypedExpr Double;
public log10Expr = functionExpr1 Log10Function;
modExpr :: TypedExpr Int -> TypedExpr Int -> TypedExpr Int;
public modExpr = functionExpr2 ModFunction;
piExpr :: TypedExpr Double;
public piExpr = functionExpr0 PiFunction;
powerExpr :: (Num a, Num b) => TypedExpr a -> TypedExpr b -> TypedExpr a;
public powerExpr = functionExpr2 PowerFunction;
radiansExpr :: Num a => TypedExpr a -> TypedExpr Double;
public radiansExpr = functionExpr1 RadiansFunction;
randExpr :: TypedExpr Int -> TypedExpr Double;
public randExpr = functionExpr1 RandFunction;
roundExpr :: TypedExpr Double -> TypedExpr Int -> TypedExpr Double;
public roundExpr = functionExpr2 RoundFunction;
signExpr :: Num a => TypedExpr a -> TypedExpr Int;
public signExpr = functionExpr1 SignFunction;
sinExpr :: TypedExpr Double -> TypedExpr Double;
public sinExpr = functionExpr1 SinFunction;
sqrtExpr :: Num a => TypedExpr a -> TypedExpr Double;
public sqrtExpr = functionExpr1 SqrtFunction;
tanExpr :: TypedExpr Double -> TypedExpr Double;
public tanExpr = functionExpr1 TanFunction;
/**
* TODO: is this the correct signature?
*/
truncateExpr :: Num a => TypedExpr a -> TypedExpr Int;
public truncateExpr = functionExpr1 TruncateFunction;
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Conversion functions
/**
* Converts an integer value to a double value.
* The assumption here is that the database will do an implicit conversion
* between these 2 types, so no function will be applied in the generated SQL.
* A function could be used here if necessary.
*/
intToDoubleExpr :: TypedExpr Int -> TypedExpr Double;
public intToDoubleExpr expr = toTypedExpr (toUntypedExpr expr);
/**
* Converts a value to a string value.
*/
convertToStringExpr :: TypedExpr a -> TypedExpr String;
public convertToStringExpr = functionExpr1 ConvertToStringFunction;
/**
* Converts a value to a int value.
*/
convertToIntExpr :: TypedExpr a -> TypedExpr Int;
public convertToIntExpr = functionExpr1 ConvertToIntFunction;
/**
* Converts a value to a double value.
*/
convertToDoubleExpr :: TypedExpr a -> TypedExpr Double;
public convertToDoubleExpr = functionExpr1 ConvertToDoubleFunction;
//////////////////////////////////////////////////////////////////////////////////////////////////////
// String functions
asciiExpr :: TypedExpr String -> TypedExpr Int;
public asciiExpr = functionExpr1 AsciiFunction;
charExpr :: TypedExpr Int -> TypedExpr String;
public charExpr = functionExpr1 CharFunction;
differenceExpr :: TypedExpr String -> TypedExpr String -> TypedExpr Int;
public differenceExpr = functionExpr2 DifferenceFunction;
insertExpr :: TypedExpr String -> TypedExpr Int -> TypedExpr Int -> TypedExpr String -> TypedExpr String;
public insertExpr stringExpr start length insertStr = functionExpr4 InsertFunction stringExpr start length insertStr;
lcaseExpr :: TypedExpr String -> TypedExpr String;
public lcaseExpr = functionExpr1 LcaseFunction;
leftExpr :: TypedExpr String -> TypedExpr Int -> TypedExpr String;
public leftExpr = functionExpr2 LeftFunction;
lengthExpr :: TypedExpr String -> TypedExpr Int;
public lengthExpr = functionExpr1 LengthFunction;
locateExpr :: TypedExpr String -> TypedExpr String -> TypedExpr Int;
public locateExpr searchExpr stringExpr = functionExpr2 LocateFunction searchExpr stringExpr;
locate2Expr :: TypedExpr String -> TypedExpr String -> TypedExpr Int -> TypedExpr Int;
public locate2Expr searchExpr stringExpr start = functionExpr3 LocateFunction searchExpr stringExpr start;
ltrimExpr :: TypedExpr String -> TypedExpr String;
public ltrimExpr = functionExpr1 LtrimFunction;
repeatExpr :: TypedExpr String -> TypedExpr Int -> TypedExpr String;
public repeatExpr stringExpr count = functionExpr2 RepeatFunction stringExpr count;
replaceExpr :: TypedExpr String -> TypedExpr String -> TypedExpr String -> TypedExpr String;
public replaceExpr stringExpr searchStr replacementStr = functionExpr3 ReplaceFunction stringExpr searchStr replacementStr;
rightExpr :: TypedExpr String -> TypedExpr Int -> TypedExpr String;
public rightExpr = functionExpr2 RightFunction;
rtrimExpr :: TypedExpr String -> TypedExpr String;
public rtrimExpr = functionExpr1 RtrimFunction;
soundexExpr :: TypedExpr String -> TypedExpr String;
public soundexExpr = functionExpr1 SoundexFunction;
spaceExpr :: TypedExpr Int -> TypedExpr String;
public spaceExpr nSpaces = functionExpr1 SpaceFunction nSpaces;
substringExpr :: TypedExpr String -> TypedExpr Int -> TypedExpr Int -> TypedExpr String;
public substringExpr stringExpr start length = functionExpr3 SubstringFunction stringExpr start length;
ucaseExpr :: TypedExpr String -> TypedExpr String;
public ucaseExpr = functionExpr1 UcaseFunction;
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Misc functions
databaseExpr :: TypedExpr String;
public databaseExpr = functionExpr0 DatabaseFunction;
ifNullExpr :: TypedExpr a -> TypedExpr a -> TypedExpr a;
public ifNullExpr = functionExpr2 IfNullFunction;
nullIfExpr :: TypedExpr a -> TypedExpr a -> TypedExpr a;
public nullIfExpr = functionExpr2 NullIfFunction;
userExpr :: TypedExpr String;
public userExpr = functionExpr0 UserFunction;
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Date/Time functions
dayNameExpr :: TypedExpr Time -> TypedExpr String;
public dayNameExpr = functionExpr1 DayNameFunction;
dayOfWeekExpr :: TypedExpr Time -> TypedExpr Int;
public dayOfWeekExpr = functionExpr1 DayOfWeekFunction;
dayOfMonthExpr :: TypedExpr Time -> TypedExpr Int;
public dayOfMonthExpr = functionExpr1 DayOfMonthFunction;
dayOfYearExpr :: TypedExpr Time -> TypedExpr Int;
public dayOfYearExpr = functionExpr1 DayOfYearFunction;
hourExpr :: TypedExpr Time -> TypedExpr Int;
public hourExpr = functionExpr1 HourFunction;
minuteExpr :: TypedExpr Time -> TypedExpr Int;
public minuteExpr = functionExpr1 MinuteFunction;
monthExpr :: TypedExpr Time -> TypedExpr Int;
public monthExpr = functionExpr1 MonthFunction;
monthNameExpr :: TypedExpr Time -> TypedExpr String;
public monthNameExpr = functionExpr1 MonthNameFunction;
nowExpr :: TypedExpr Time;
public nowExpr = functionExpr0 NowFunction;
quarterExpr :: TypedExpr Time -> TypedExpr Int;
public quarterExpr = functionExpr1 QuarterFunction;
secondExpr :: TypedExpr Time -> TypedExpr Int;
public secondExpr = functionExpr1 SecondFunction;
// TODO: create helper functions for TimestampAddFunction and
// TimestampDiffFunction...
weekExpr :: TypedExpr Time -> TypedExpr Int;
public weekExpr = functionExpr1 WeekFunction;
yearExpr :: TypedExpr Time -> TypedExpr Int;
public yearExpr = functionExpr1 YearFunction;
dateTimeAddExpr :: TimeInterval -> TypedExpr Int -> TypedExpr Time -> TypedExpr Time;
public dateTimeAddExpr timeInterval = functionExpr2 (DateTimeAddFunction timeInterval);
dateTimeDiffExpr :: TimeInterval -> TypedExpr Time -> TypedExpr Time -> TypedExpr Int;
public dateTimeDiffExpr timeInterval = functionExpr2 (DateTimeDiffFunction timeInterval);
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Aggregate functions
untypedAggregationExpr :: DbFunction -> Expr -> Expr;
public untypedAggregationExpr aggrOp baseExpr =
if (isAggregationFunction aggrOp) then FunctionExpr aggrOp [baseExpr]
else error "The specified function is not an aggregation function.";
countExpr :: TypedExpr a -> TypedExpr Int;
public countExpr = functionExpr1 AggrCount;
sumExpr :: Num a => TypedExpr a -> TypedExpr a;
public sumExpr = functionExpr1 AggrSum;
avgExpr :: Num a => TypedExpr a -> TypedExpr Double;
public avgExpr = functionExpr1 AggrAvg;
minExpr :: Ord a => TypedExpr a -> TypedExpr a;
public minExpr = functionExpr1 AggrMin;
maxExpr :: Ord a => TypedExpr a -> TypedExpr a;
public maxExpr = functionExpr1 AggrMax;
distinctCountExpr :: TypedExpr a -> TypedExpr Int;
public distinctCountExpr = functionExpr1 AggrDistinctCount;
distinctSumExpr :: Num a => TypedExpr a -> TypedExpr a;
public distinctSumExpr = functionExpr1 AggrDistinctSum;
distinctAvgExpr :: Num a => TypedExpr a -> TypedExpr Double;
public distinctAvgExpr = functionExpr1 AggrDistinctAvg;
countAllExpr :: TypedExpr Double;
public countAllExpr = functionExpr0 AggrCountAll;
stdDevExpr :: Num a => TypedExpr a -> TypedExpr Double;
public stdDevExpr = functionExpr1 AggrStdDev;
stdDevPExpr :: Num a => TypedExpr a -> TypedExpr Double;
public stdDevPExpr = functionExpr1 AggrStdDevP;
varianceExpr :: Num a => TypedExpr a -> TypedExpr Double;
public varianceExpr = functionExpr1 AggrVar;
variancePExpr :: Num a => TypedExpr a -> TypedExpr Double;
public variancePExpr = functionExpr1 AggrVarP;
/**
* A database SELECT query.
*/
data public Query =
private Query
options :: [QueryOption]
columns :: [(Expr, String)]
restriction :: (Maybe (TypedExpr Boolean))
orderings :: [(Expr, Boolean)]
joins :: [JoinNode]
groups :: [Expr]
groupRestriction :: (Maybe (TypedExpr Boolean)) |
private Union
query1 :: Query
query2 :: Query
unionAll :: Boolean
deriving Eq, Show;
/**
* Creates a new, empty query.
*/
newQuery :: Query;
public newQuery = Query [] [] Nothing [] [] [] Nothing;
/**
* Combines the 2 queries into a Union query.
*/
unionQuery :: Query -> Query -> Boolean -> Query;
public unionQuery query1 query2 unionAll =
let
query1Cols = getProjectedColumns query1;
query2Cols = getProjectedColumns query2;
// Remove any ordering from the first query.
updatedQuery1 = removeOrdering query1;
in
// Check that the queries have the same number of columns.
if (length query1Cols == length query2Cols) then Union updatedQuery1 query2 unionAll
else error "Both queries in a Union must have the same number of columns";
/**
* Produces a query which returns the intersection of the two specified queries.
* The final result columns will be based on the first query.
* The intersection will be performed on the corresponding columns in each
* query.
* It is ok for the queries to have different numbers of columns.
*/
intersectionQuery :: Query -> Query -> Query;
public intersectionQuery query1 query2 =
let
origAliases = map snd (projectedColumnsWithAliases query1);
// Set a known alias for the colums.
itemColumnAliases = map (\i -> "COL_VALUE" ++ (intToString i)) (upFrom 1);
// Wrap these subqueries in tables.
subQueryTable1 = makeSubQueryTable (setColumnAliases query1 itemColumnAliases) "Q1";
subQueryTable2 = makeSubQueryTable (setColumnAliases query2 itemColumnAliases) "Q2";
nJoinColumns = length (zip (getProjectedColumns query1) (getProjectedColumns query2));
joinInfos = map (\alias -> makeJoinInfo (stringField subQueryTable1 alias) (stringField subQueryTable2 alias) InnerJoin) (take nJoinColumns itemColumnAliases);
resultCols = zip (map (untypedField subQueryTable1) itemColumnAliases) origAliases;
in
join2 (projectWithAliases newQuery resultCols) joinInfos;
/**
* Produces a query which returns the intersection of the two specified queries.
* The final result columns will be based on the first query.
* The intersection will be performed on the specified expressions.
*/
intersectionQuery2 :: Query -> Query -> [(Expr, Expr)] -> Query;
public intersectionQuery2 query1 query2 compareExprs =
let
origAliases = map snd (projectedColumnsWithAliases query1);
compareExprs1 = if (isEmpty compareExprs) then getProjectedColumns query1
else map fst compareExprs;
compareExprs2 = if (isEmpty compareExprs) then getProjectedColumns query2
else map snd compareExprs;
// Ensure that all the comparison expressions are projected from both
// queries.
queryAllColumns1 = projectColumnsIfNecessary query1 compareExprs1;
queryAllColumns2 = projectColumnsIfNecessary query2 compareExprs2;
// Set a known alias for the colums.
itemColumnAliases = map defaultColumnAlias (upFrom 0);
// Wrap these subqueries in tables.
subQueryTable1 = makeSubQueryTable (setColumnAliases queryAllColumns1 itemColumnAliases) "Q1";
subQueryTable2 = makeSubQueryTable (setColumnAliases queryAllColumns2 itemColumnAliases) "Q2";
// Find the join expression for each of the comparison expressions.
lookupJoinExpr origQuery subQueryTable origExpr =
let
indexInOrigQuery = fromJust (elemIndex origExpr (getProjectedColumns origQuery));
alias = defaultColumnAlias indexInOrigQuery;
in
stringField subQueryTable alias;
joinExprs1 = map (lookupJoinExpr queryAllColumns1 subQueryTable1) compareExprs1;
joinExprs2 = map (lookupJoinExpr queryAllColumns2 subQueryTable2) compareExprs2;
joinInfos = zipWith (\e1 e2 -> makeJoinInfo e1 e2 InnerJoin) joinExprs1 joinExprs2;
resultCols = zip (map (untypedField subQueryTable1) itemColumnAliases) origAliases;
in
join2 (projectWithAliases newQuery resultCols) joinInfos;
/**
* This is an alternate version of {@link intersectionQuery@}.
* Instead of creating 2 subqueries and linking them, this function creates a
* subquery for the second query
* and links it directly to the first query (although special handling is needed
* if the first query is uses {@link TopN@}).
* This should give the same results as {@code intersectionQuery@}.
*
* TODO: check which option is most efficient and get rid of the other one.
*/
alternateIntersectionQuery :: Query -> Query -> Query;
public alternateIntersectionQuery query1 query2 =
let
origAliases = map snd (projectedColumnsWithAliases query1);
// Set a known alias for the colums.
itemColumnAliases = map defaultColumnAlias (upFrom 0);
nJoinColumns = length (zip (getProjectedColumns query1) (getProjectedColumns query2));
needToWrapQuery query = isTopNQuery query;
wrapQueryIfNeeded query = if (needToWrapQuery query) then wrapQuery query else query;
updatedQuery1 = wrapQueryIfNeeded query1;
subQueryTable2 = makeSubQueryTable (setColumnAliases query2 itemColumnAliases) "";
joinInfos = zipWith (\query1Col alias -> makeJoinInfo (toTypedExpr query1Col) (stringField subQueryTable2 alias) InnerJoin) (getProjectedColumns updatedQuery1) (take nJoinColumns itemColumnAliases);
in
setColumnAliases (join2 updatedQuery1 joinInfos) origAliases;
/**
* Produces a query which returns the difference of the two specified queries.
* The final result columns will be based on the first query.
* The difference will be performed on the corresponding columns in each query.
* It is ok for the queries to have different numbers of columns.
*/
differenceQuery :: Query -> Query -> Query;
public differenceQuery query1 query2 =
differenceQuery2 query1 query2 (zip (getProjectedColumns query1) (getProjectedColumns query2));
/**
* Produces a query which returns the difference of the two specified queries.
* The final result columns will be based on the first query.
* The difference will be performed on the specified expressions.
*/
differenceQuery2 :: Query -> Query -> [(Expr, Expr)] -> Query;
public differenceQuery2 query1 query2 compareExprs =
let
origAliases = map snd (projectedColumnsWithAliases query1);
compareExprs1 = if (isEmpty compareExprs) then getProjectedColumns query1
else map fst compareExprs;
compareExprs2 = if (isEmpty compareExprs) then getProjectedColumns query2
else map snd compareExprs;
// Ensure that all the comparison expressions are projected from both
// queries.
queryAllColumns1 = projectColumnsIfNecessary query1 compareExprs1;
queryAllColumns2 = projectColumnsIfNecessary query2 compareExprs2;
// Take the first query, and add a restriction of the following form:
// NOT EXISTS (SELECT ... WHERE qry2Field1 <> qry1Field1 AND qry2Field2
// <> qry1Field2 AND ....
needToWrapQuery query = isTopNQuery query;
wrapQueryIfNeeded query = if (needToWrapQuery query) then wrapQuery query else query;
updatedQuery1 = wrapQueryIfNeeded queryAllColumns1;
updatedQuery2 = wrapQueryIfNeeded queryAllColumns2;
// Find the updated expression for each of the comparison expressions.
lookupUpdatedExpr origQuery updatedQuery origExpr =
let
lookupMap = zip (getProjectedColumns origQuery) (getProjectedColumns updatedQuery);
in
fromJust (List.lookup origExpr lookupMap);
updatedCompareExprs1 = map (lookupUpdatedExpr queryAllColumns1 updatedQuery1) compareExprs1;
updatedCompareExprs2 = map (lookupUpdatedExpr queryAllColumns2 updatedQuery2) compareExprs2;
exclusionRestrictions = zipWith (\q2Col q1Col -> toTypedExpr (untypedBinaryExpr OpEq q2Col q1Col)) updatedCompareExprs2 updatedCompareExprs1;
exclusionExpr = notExpr (existsExpr (toTypedExpr (subQueryExpr (restrict2 updatedQuery2 exclusionRestrictions))));
restrictedQuery = restrict updatedQuery1 exclusionExpr;
allResultExpr = getProjectedColumns restrictedQuery;
in
// Set the correct aliases and truncate the projected columns to match
// the original list.
projectWithAliases (removeProjectedColumns restrictedQuery) (zip allResultExpr origAliases);
/**
* Returns a default column alias.
* The column index is the zero-based index of the expression in the query's
* projected columns.
*/
defaultColumnAlias :: Int -> String;
private defaultColumnAlias colIndex = "COL_VALUE" ++ (intToString (colIndex + 1));
/**
* Adds the specified expression to the projected columns, if it isn't already
* projected.
*/
projectColumnIfNecessary :: Query -> Expr -> Query;
private projectColumnIfNecessary !query !expr =
if (isElem expr (getProjectedColumns query)) then query
else project query [expr];
/**
* Adds the specified expressions to the projected columns, if they aren't
* already projected.
*/
projectColumnsIfNecessary :: Query -> [Expr] -> Query;
private projectColumnsIfNecessary query exprs = foldLeftStrict projectColumnIfNecessary query exprs;
/**
* Wraps the query in an outer query which projects the same columns as the
* original.
* The ordering from the original query is also preserved.
*/
wrapQuery :: Query -> Query;
public wrapQuery query = fst (wrapQuery2 query []);
/**
* Wraps the query in an outer query which projects the same columns as the
* original.
* The ordering from the original query is also preserved.
* The wrapped query will be returned along with wrapped versions of the
* specified additional expressions.
*/
wrapQuery2 :: Query -> [Expr] -> (Query, [Expr]);
public wrapQuery2 query additionalExprs =
let
origAliases = projectedColumnAliases query ++ repeat "";
originalOrdering = orderingExpressions query;
origProjectedExprs = getProjectedColumns query;
origOrderingExprs = map fst originalOrdering;
additionalProjectedExprs = deleteFirsts (removeDuplicates (origOrderingExprs ++ additionalExprs)) origProjectedExprs;
itemColumnAliases = map defaultColumnAlias (upFrom 0);
subQueryTable = makeSubQueryTable (setColumnAliases (project query additionalProjectedExprs) itemColumnAliases) "";
allSubqueryProjectedExprs = origProjectedExprs ++ additionalProjectedExprs;
wrappedQueryExprs = map (untypedField subQueryTable) itemColumnAliases;
wrappedQueryProjectedExprs = take (length origProjectedExprs) wrappedQueryExprs;
exprMap = zip allSubqueryProjectedExprs wrappedQueryExprs;
lookupNewExpr origExpr = lookupWithDefault origExpr exprMap (error "Failed to find expression in wrapped query");
// Preverse the ordering from the original query.
newOrdering = map (\pr -> (lookupNewExpr (fst pr), snd pr)) originalOrdering;
wrappedQuery = order2 (projectWithAliases newQuery (zip wrappedQueryProjectedExprs origAliases)) newOrdering;
wrappedAdditionalExprs = map lookupNewExpr additionalExprs;
in
(wrappedQuery, wrappedAdditionalExprs);
/**
* Returns a query field for the specified table.
*/
untypedField :: QueryTable -> String -> Expr;
public untypedField table fieldName = QueryField fieldName table;
stringField :: QueryTable -> String -> TypedExpr String;
public stringField table fieldName = TypedExpr (QueryField fieldName table);
booleanField :: QueryTable -> String -> TypedExpr Boolean;
public booleanField table fieldName = TypedExpr (QueryField fieldName table);
doubleField :: QueryTable -> String -> TypedExpr Double;
public doubleField table fieldName = TypedExpr (QueryField fieldName table);
intField :: QueryTable -> String -> TypedExpr Int;
public intField table fieldName = TypedExpr (QueryField fieldName table);
timeField :: QueryTable -> String -> TypedExpr Time;
public timeField table fieldName = TypedExpr (QueryField fieldName table);
binaryField :: QueryTable -> String -> TypedExpr (Array Byte);
public binaryField table fieldName = TypedExpr (QueryField fieldName table);
/**
* Returns the query options.
* For {@link Union@} queries, this will return the options for the first query.
*/
queryOptions :: Query -> [QueryOption];
public queryOptions query =
case query of
Query {options} -> options;
Union {query1} -> queryOptions query1;
;
/**
* Adds an option to the query.
*/
addOption :: Query -> QueryOption -> Query;
public addOption !query !newOption =
case (removeOption query newOption) of
Query options columns restriction orderings joins groups groupRestriction ->
Query (options ++ [newOption]) columns restriction orderings joins groups groupRestriction;
Union query1 query2 unionAll ->
// Add the option to both queries.
Union (addOption query1 newOption) (addOption query2 newOption) unionAll;
;
/**
* Removes the option of the same type as the specified one (even if the exact
* values don't match).
*/
removeOption :: Query -> QueryOption -> Query;
public removeOption query optionTypeToRemove =
case query of
Query options columns restriction orderings joins groups groupRestriction ->
let
remainingOptions = filter (\o -> not (isSameOptionType optionTypeToRemove o)) options;
in
Query remainingOptions columns restriction orderings joins groups groupRestriction;
Union query1 query2 unionAll ->
// Remove the option from both queries.
Union (removeOption query1 optionTypeToRemove) (removeOption query2 optionTypeToRemove) unionAll;
;
/**
* Returns whether the specified options are of the same type (not necessarily
* the same value).
*/
isSameOptionType :: QueryOption -> QueryOption -> Boolean;
isSameOptionType !option1 !option2 =
case option1 of
Distinct ->
case option2 of
Distinct -> True;
_ -> False;
;
TopN {} ->
case option2 of
TopN {} -> True;
_ -> False;
;
;
/**
* Returns whether a {@link TopN@} option is specified for the query.
*/
isTopNQuery :: Query -> Boolean;
private isTopNQuery !query =
case query of
Query {options} -> any isTopNOption options;
// Check whether either query is a topN query.
Union {query1, query2} -> isTopNQuery query1 || isTopNQuery query2;
;
/**
* Adds the specified expression as a result column in the query.
*/
projectColumn :: Query -> TypedExpr a -> Query;
public projectColumn query newColumn =
project query [toUntypedExpr newColumn];
/**
* Adds the specified expression as a result column in the query.
*/
projectColumnWithAlias :: Query -> TypedExpr a -> String -> Query;
public projectColumnWithAlias query newColumn columnAlias =
projectWithAliases query [(toUntypedExpr newColumn, columnAlias)];
/**
* Adds the specified expressions as result columns in the query.
*/
project :: Query -> [Expr] -> Query;
public project query newColumns =
let
newColumnsAndAliases = zip newColumns (repeat "");
in
projectWithAliases query newColumnsAndAliases;
/**
* Adds the specified expressions as result columns with the corresponding
* aliases in the query.
*
* TODO: don't add the same field multiple times...
*/
projectWithAliases :: Query -> [(Expr, String)] -> Query;
public projectWithAliases query newColumns =
case query of
Query options columns restriction orderings joins groups groupRestriction ->
Query options (columns ++ newColumns) restriction orderings joins groups groupRestriction;
Union query1 query2 unionAll ->
// Product the column in both queries.
Union (projectWithAliases query1 newColumns) (projectWithAliases query2 newColumns) unionAll;
;
/**
* Returns the projected columns for the query.
* For a {@link Union@} query, this will return only the projected columns for the first
* query.
*
* TODO: rename this to projectedColumns...
*/
getProjectedColumns :: Query -> [Expr];
public getProjectedColumns query =
case query of
Query {columns} -> map fst columns;
Union {query1} -> getProjectedColumns query1;
;
/**
* Returns the projected columns for the query.
* For a {@link Union@} query, this will return only the projected columns for the first
* query.
*/
projectedColumnsWithAliases :: Query -> [(Expr, String)];
public projectedColumnsWithAliases query =
case query of
Query {columns} -> columns;
Union {query1} -> projectedColumnsWithAliases query1;
;
/**
* Returns the aliases for the query's projected columns.
*/
projectedColumnAliases :: Query -> [String];
public projectedColumnAliases query = map snd (projectedColumnsWithAliases query);
/**
* Removes all projected columns from the query.
*/
removeProjectedColumns :: Query -> Query;
public removeProjectedColumns query =
case query of
Query options columns restriction orderings joins groups groupRestriction ->
Query options [] restriction orderings joins groups groupRestriction;
Union query1 query2 unionAll ->
// Remove the projected columns from both queries.
Union (removeProjectedColumns query1) (removeProjectedColumns query2) unionAll;
;
/**
* Removes the specified expression from the projected columns list.
* For {@link Union@} queries, the specified expression will be removed from the both
* queries along with the
* corresponding columns in the other query.
*/
removeProjectedColumn :: Query -> Expr -> Query;
public removeProjectedColumn query exprToRemove =
case query of
Query options columns restriction orderings joins groups groupRestriction ->
Query options (filter (\c -> (fst c) != exprToRemove) columns) restriction orderings joins groups groupRestriction;
Union query1 query2 unionAll ->
let
exprIndicesInQuery qry =
case qry of
Query {columns} -> List.findIndices (\c -> fst c == exprToRemove) columns;
Union {query1, query2} -> removeDuplicates (exprIndicesInQuery query1 ++ exprIndicesInQuery query2);
;
// Determine the combined set of indices to be removed.
indicesToRemove = exprIndicesInQuery query;
// Remove the appropriate columns from a query.
removeExprs qry =
case qry of
Query options columns restriction orderings joins groups groupRestriction ->
let
newColumns = map snd (filter (\pr -> not (isElem (fst pr) indicesToRemove)) (zip (upFrom 0) columns));
in
Query options newColumns restriction orderings joins groups groupRestriction;
Union query1 query2 unionAll ->
Union (removeExprs query1) (removeExprs query2) unionAll;
;
in
removeExprs query;
;
/**
* Sets the aliases for the first N projected columns.
* Any other existing aliases will be left untouched.
*/
setColumnAliases :: Query -> [String] -> Query;
public setColumnAliases query newAliases =
case query of
Query options columns restriction orderings joins groups groupRestriction ->
let
oldAliases = map snd columns;
projectedExprs = map fst columns;
finalAliases = newAliases ++ (drop (length newAliases) oldAliases);
newColumns = zip projectedExprs finalAliases;
in
Query options newColumns restriction orderings joins groups groupRestriction;
Union query1 query2 unionAll ->
// Set the column aliases in both queries.
Union (setColumnAliases query1 newAliases) (setColumnAliases query2 newAliases) unionAll;
;
/**
* Adds a restriction on the rows returned by the query.
*/
restrict :: Query -> TypedExpr Boolean -> Query;
public restrict !query !newRestriction =
let
// Returns a list of expressions which are ANDed together, if any.
getAndedExprs :: TypedExpr Boolean -> [TypedExpr Boolean];
getAndedExprs expr =
case (toUntypedExpr) expr of
FunctionExpr {func, arguments} ->
case func of
OpAnd -> concatMap (Prelude.compose getAndedExprs toTypedExpr) arguments;
_ -> [expr];
;
_ -> [expr];
;
restrictHelper :: Query -> TypedExpr Boolean -> Query;
restrictHelper !query !newRestriction =
case query of
Query options columns restriction orderings joins groups groupRestriction ->
let
mergedRestriction =
case restriction of
Nothing -> newRestriction;
Just restrictionValue -> andExpr restrictionValue newRestriction;
;
mergedGroupRestriction =
case groupRestriction of
Nothing -> newRestriction;
JustgroupRestrictionValue -> andExpr groupRestrictionValue newRestriction;
;
in
// Don't change anything if the new criteria is simply
// 'True'.
if (newRestriction == trueConstant) then
query
else if (isGroupRestriction newRestriction) then
Query options columns restriction orderings joins groups (Just mergedGroupRestriction)
else
Query options columns (Just mergedRestriction) orderings joins groups groupRestriction;
Union query1 query2 unionAll ->
// Restrict both queries.
Union (restrict query1 newRestriction) (restrict query2 newRestriction) unionAll;
;
in
// Split up the restriction into separate ANDed expressions and apply
// each individually.
// This is needed in cases where the restriction expression contains
// some record restrictions
// and some group restrictions.
foldLeftStrict restrictHelper query (getAndedExprs newRestriction);
/**
* Adds the specified restrictions on the rows returned by the query.
*/
restrict2 :: Query -> [TypedExpr Boolean] -> Query;
public restrict2 query newRestrictions =
foldLeftStrict restrict query newRestrictions;
/**
* Returns whether the restriction expression applies to groups or record
* values.
*/
isGroupRestriction :: TypedExpr Boolean -> Boolean;
private isGroupRestriction restriction = exprUsesAggregation (toUntypedExpr restriction);
/**
* Returns whether the expression uses aggregate functions.
*
* TODO: is there anything else to look for?
*/
exprUsesAggregation :: Expr -> Boolean;
public exprUsesAggregation expr =
case expr of
(QueryField |
ConstExpr |
SubQueryExpr) {} -> False;
ListExpr {listValues} -> any exprUsesAggregation listValues;
FunctionExpr {func, arguments} -> isAggregationFunction func || (any exprUsesAggregation arguments);
;
/**
* Returns the restriction expressions (including group restrictions) for the
* query.
* For a {@link Union@} query, this only returns the restrictions for the first query.
*/
restrictionExpressions :: Query -> [TypedExpr Boolean];
public restrictionExpressions query =
case query of
Query {restriction, groupRestriction} ->
(Prelude.maybeToList restriction) ++ (Prelude.maybeToList groupRestriction);
Union {query1} ->
restrictionExpressions query1;
;
/**
* Adds sorting on the specified expression.
*/
order :: Ord a => Query -> TypedExpr a -> Boolean -> Query;
public order query sortExpr sortAscending = order2 query [(toUntypedExpr sortExpr, sortAscending)];
/**
* Adds sorting on the specified fields.
*/
order2 :: Query -> [(Expr, Boolean)] -> Query;
public order2 query newOrderings =
case query of
Query options columns restriction orderings joins groups groupRestriction ->
Query options columns restriction (orderings ++ newOrderings) joins groups groupRestriction;
Union query1 query2 unionAll ->
// Add ordering to only the 2nd query.
Union query1 (order2 query2 newOrderings) unionAll;
;
/**
* Returns the ordering info for the query.
*/
orderingExpressions :: Query -> [(Expr, Boolean)];
public orderingExpressions query =
case query of
Query {orderings} -> orderings;
Union {query2} -> orderingExpressions query2;
;
/**
* Removes all ordering from the query.
*/
removeOrdering :: Query -> Query;
public removeOrdering query =
case query of
Query options columns restriction orderings joins groups groupRestriction ->
Query options columns restriction [] joins groups groupRestriction;
Union query1 query2 unionAll ->
// Remove ordering from both queries (even though the first one
// shouldn't have any ordering to begin with).
Union (removeOrdering query1) (removeOrdering query2) unionAll;
;
/**
* Projects the specified expressions from the query, and group and order (ASC) on the expressions.
*/
projectGroupAndOrder :: Query -> [Expr] -> Query;
public projectGroupAndOrder !query !exprs =
order2 (group2 (project query exprs) exprs) (zip exprs $ repeat True);
/**
* Adds a join between 2 tables.
*/
join :: Query -> JoinInfo -> Query;
public join !query newJoinInfo =
case query of
Query options columns restriction orderings joins groups groupRestriction ->
let
newJoinNodes = applyJoinInfo joins newJoinInfo;
in
Query options columns restriction orderings newJoinNodes groups groupRestriction;
Union query1 query2 unionAll ->
// Add the join to both queries.
Union (join query1 newJoinInfo) (join query2 newJoinInfo) unionAll;
;
/**
* Adds the specified joins to the query.
*/
join2 :: Query -> [JoinInfo] -> Query;
public join2 query newJoins =
foldLeft join query newJoins;
/**
* Adds the specified join nodes to the query.
*/
addJoins :: Query -> [JoinNode] -> Query;
public addJoins query newJoins =
setJoins query (joins query ++ newJoins);
/**
* Sets the join information for the query as a list of join node trees.
* This will replace any existing join info.
*/
setJoins :: Query -> [JoinNode] -> Query;
public setJoins query newJoins =
case query of
Query options columns restriction orderings joins groups groupRestriction ->
Query options columns restriction orderings (cleanupJoinNodes newJoins) groups groupRestriction;
Union query1 query2 unionAll ->
// Set the joins in both queries.
Union (setJoins query1 newJoins) (setJoins query2 newJoins) unionAll;
;
/**
* Returns the join information from the query.
* For a {@link Union@} query, the joins from the first query will be returned.
*/
joins :: Query -> [JoinNode];
public joins query =
case query of
Query {joins} -> joins;
Union {query1} -> joins query1;
;
/**
*