preql-0.4: src/Preql/QuasiQuoter/Syntax/Syntax.hs
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE DuplicateRecordFields #-}
-- | Description: Syntax tree for SQL
module Preql.QuasiQuoter.Syntax.Syntax where
import Preql.QuasiQuoter.Syntax.Name
import Data.Data
import Data.List.NonEmpty (NonEmpty, nonEmpty)
import Data.String (IsString(..))
import Data.Text (Text)
import Data.Word (Word)
import GHC.Generics
import Instances.TH.Lift ()
import Language.Haskell.TH.Syntax (Lift(..))
import qualified Data.Text as T
-- FIXME rename to Constant?
data Literal = I !Word | F !Double | T !Text | B !Bool | Null
deriving (Show, Eq, Generic, Typeable, Data, Lift)
data Statement = QI !Insert | QD !Delete | QU !Update | QS !SelectStmt
deriving (Show, Eq, Generic, Typeable, Data, Lift)
-- | Queries of the form @INSERT INTO table (columns) VALUES (values);@
-- Limitations:
-- * single row
-- * no @ON CONFLICT@
data Insert = Insert
{ table :: !Name
, columns :: NonEmpty Name
, values :: NonEmpty Expr -- TODO enforce matched lengths?
} deriving (Show, Eq, Generic, Typeable, Data, Lift)
-- | Queries of the form @DELETE FROM table WHERE conditions@.
data Delete = Delete
{ table :: !Name
, conditions :: Maybe Expr
} deriving (Show, Eq, Generic, Typeable, Data, Lift)
data Setting = Setting !Name !Expr
deriving (Show, Eq, Generic, Typeable, Data, Lift)
-- | Queries of the form @UPDATE table SET settings WHERE conditions@. Where each
-- @Setting name literal@ is like SQL @name = literal@.
data Update = Update
{ table :: !Name
, settings :: NonEmpty Setting
, conditions :: Maybe Expr
} deriving (Show, Eq, Generic, Typeable, Data, Lift)
-- TODO prevent multiple SelectOptions on the same query
-- If each constructor takes SelectOptions, we can ditch S and the empty SelectOptions becomes valid
data SelectStmt
= SelectValues (NonEmpty (NonEmpty Expr))
| Simple Select
| S SelectStmt SelectOptions
| Set SetOp AllOrDistinct SelectStmt SelectStmt
deriving (Show, Eq, Generic, Typeable, Data, Lift)
data Select = Select
{ distinct :: Maybe DistinctClause
, targetList :: [ResTarget]
, from :: [TableRef]
, whereClause :: Maybe Expr
, groupBy :: [Expr] -- TODO more accurate type than Expr?
, having :: Maybe Expr
, window :: [WindowDef]
-- TODO remaining fields
} deriving (Show, Eq, Generic, Typeable, Data, Lift)
data SelectOptions = SelectOptions
{ sortBy :: [SortBy]
, offset :: Maybe Expr
, limit :: Maybe Expr
, locking :: [Locking]
, withClause :: Maybe WithClause
} deriving (Show, Eq, Generic, Typeable, Data, Lift)
-- This is really for writing tests, but put it here for faster type check errors
select :: Select
select = Select
{ distinct = Nothing
, targetList = []
, from = []
, whereClause = Nothing
, groupBy = []
, having = Nothing
, window = []
}
selectOptions :: SelectOptions
selectOptions = SelectOptions
{ sortBy = []
, offset = Nothing
, limit = Nothing
, locking = []
, withClause = Nothing
}
data TableRef
= J JoinedTable
| As JoinedTable Alias
| SubSelect SelectStmt Alias
deriving (Show, Eq, Generic, Typeable, Data, Lift)
data JoinedTable
= Table Name
| Join JoinType JoinQual TableRef TableRef
| CrossJoin TableRef TableRef
deriving (Show, Eq, Generic, Typeable, Data, Lift)
data Alias = Alias
{ aliasName :: Name
, columnNames :: [ Name ]
} deriving (Show, Eq, Generic, Typeable, Data, Lift)
data JoinType = Inner | LeftJoin | RightJoin | Full
deriving (Show, Eq, Generic, Typeable, Data, Lift, Enum, Bounded)
data JoinQual = Using [Name] | On Expr | Natural
deriving (Show, Eq, Generic, Typeable, Data, Lift)
data DistinctClause = DistinctAll | DistinctOn (NonEmpty Expr)
deriving (Show, Eq, Generic, Typeable, Data, Lift)
data SetOp = Union | Intersect | Except
deriving (Show, Eq, Generic, Typeable, Data, Lift, Enum, Bounded)
data AllOrDistinct = All | Distinct
deriving (Show, Eq, Generic, Typeable, Data, Lift, Enum, Bounded)
data ResTarget = Star | Column Expr (Maybe Name)
deriving (Show, Eq, Generic, Typeable, Data, Lift)
data WindowDef = WindowDef Name WindowSpec
deriving (Show, Eq, Generic, Typeable, Data, Lift)
data Over = WindowName Name | Window WindowSpec
deriving (Show, Eq, Generic, Typeable, Data, Lift)
data WindowSpec = WindowSpec
{ refName :: Maybe Name
, partitionClause :: [Expr]
, orderClause :: [SortBy ]
-- , frameOptions :: _ -- FIXME implement
} deriving (Show, Eq, Generic, Typeable, Data, Lift)
noWindow :: Over
noWindow = Window (WindowSpec Nothing [] [])
data SortBy = SortBy
{ column :: Expr
, direction :: SortOrderOrUsing
, nulls :: NullsOrder
} deriving (Show, Eq, Generic, Typeable, Data, Lift)
data SortOrderOrUsing = SortOrder SortOrder | SortUsing BinOp
deriving (Show, Eq, Generic, Typeable, Data, Lift)
data SortOrder = Ascending | Descending | DefaultSortOrder
deriving (Show, Eq, Generic, Typeable, Data, Lift, Enum, Bounded)
data NullsOrder = NullsFirst | NullsLast | NullsOrderDefault
deriving (Show, Eq, Generic, Typeable, Data, Lift, Enum, Bounded)
data Locking = Locking
{ strength :: LockingStrength
, tables :: [Name]
, wait :: LockWait
} deriving (Show, Eq, Generic, Data, Lift)
data LockingStrength
= ForUpdate | ForNoKeyUpdate | ForShare | ForKeyShare
deriving (Show, Eq, Enum, Bounded, Data, Lift, Generic)
data LockWait = LockWaitError | LockWaitSkip | LockWaitBlock
deriving (Show, Eq, Enum, Bounded, Data, Lift, Generic)
data WithClause = With
{ commonTables :: [ CTE ]
, recursive :: Recursive
}
deriving (Show, Eq, Generic, Typeable, Data, Lift)
data Recursive = Recursive | NotRecursive
deriving (Show, Eq, Enum, Bounded, Data, Lift, Generic)
data Materialized = Materialized | NotMaterialized | MaterializeDefault
deriving (Show, Eq, Enum, Bounded, Data, Lift, Generic)
data CTE = CommonTableExpr
{ name :: Name
, aliases :: [Name]
, materialized :: Materialized
, query :: Statement
}
deriving (Show, Eq, Generic, Typeable, Data, Lift)
data Expr = Lit !Literal | CRef Name
| NumberedParam !Word
| HaskellParam !Text
| BinOp !BinOp !Expr !Expr
| Unary !UnaryOp !Expr
| Indirection Expr (NonEmpty Indirection)
| SelectExpr SelectStmt
| L LikeE
| Fun FunctionApplication
| Cas Case
deriving (Show, Eq, Generic, Typeable, Data, Lift)
type Indirection = Name -- FIXME
data BinOp = Mul | Div | Add | Sub | Exponent | Mod
| Eq | LT | LTE | GT | GTE | NEq
| IsDistinctFrom | IsNotDistinctFrom
| And | Or
deriving (Show, Eq, Generic, Typeable, Data, Lift, Bounded, Enum)
data UnaryOp = Negate | Not | IsNull | NotNull
deriving (Show, Eq, Generic, Typeable, Data, Lift, Bounded, Enum)
data LikeOp = Like | ILike | Similar -- TODO add ~ !~ ~* !~*
deriving (Show, Eq, Generic, Typeable, Data, Lift, Bounded, Enum)
data LikeE = LikeE
{ op :: LikeOp
, string :: Expr
, likePattern :: Expr
, escape :: Maybe Expr
, invert :: Bool
} deriving (Show, Eq, Generic, Typeable, Data, Lift)
like :: LikeOp -> Expr -> Expr -> LikeE
like op string likePattern =
LikeE { op, string, likePattern, escape = Nothing, invert = False }
data FunctionApplication = FApp
{ name :: Name
, indirection :: [Indirection]
, arguments :: FunctionArguments
, withinGroup :: [SortBy] -- not allowed if sortBy in arguments isn't empty
, filterClause :: Maybe Expr
, over :: Over
} deriving (Show, Eq, Generic, Typeable, Data, Lift)
fapp :: (Name, [Indirection]) -> FunctionArguments -> FunctionApplication
fapp (name, indirection) args = FApp
{ name, indirection
, arguments = args
, withinGroup = []
, filterClause = Nothing
, over = noWindow
}
fapp1 :: Name -> [Expr] -> FunctionApplication
fapp1 fName args = fapp (fName, []) args' where
args' = case nonEmpty args of
Nothing -> NoArgs
Just ne -> Args (ArgsList (fmap E ne) [] False)
setSortBy :: FunctionApplication -> [SortBy] -> FunctionApplication
setSortBy f@FApp{ arguments } sorts = case arguments of
Args args -> f { arguments = Args args { sortBy = sorts } }
_ -> f
data FunctionArguments = StarArg | NoArgs | Args ArgsList
deriving (Show, Eq, Generic, Typeable, Data, Lift)
data ArgsList = ArgsList
{ arguments :: NonEmpty Argument
, sortBy :: [SortBy]
, distinct :: Bool
}
deriving (Show, Eq, Generic, Typeable, Data, Lift)
argsList :: NonEmpty Argument -> ArgsList
argsList args = ArgsList args [] False
data Argument = E Expr | Named Name Expr
deriving (Show, Eq, Generic, Typeable, Data, Lift)
data Case = Case
{ whenClause :: [(Expr, Expr)] -- (condition, then)
, implicitArg :: Maybe Expr
, elseClause :: Maybe Expr
} deriving (Show, Eq, Generic, Data, Lift)