{-# LANGUAGE OverloadedStrings, FlexibleInstances, UndecidableInstances #-} {-# LANGUAGE ScopedTypeVariables, TypeOperators, GADTs, FlexibleContexts #-} {-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving, TypeFamilies, CPP #-} {-# LANGUAGE DataKinds #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- | Selda is not LINQ, but they're definitely related. -- -- Selda is a high-level EDSL for interacting with relational databases. -- All database computations are performed within some monad implementing -- the 'MonadSelda' type class. The 'SeldaT' monad over any @MonadIO@ is the -- only pre-defined instance of @MonadSelda@. -- 'SeldaM' is provided as a convenient short-hand for @SeldaT IO@. -- -- To actually execute a database computation, you need one of the database -- backends: @selda-sqlite@ or @selda-postgresql@. -- -- All Selda functions may throw 'SeldaError' when something goes wrong. -- This includes database connection errors, uniqueness constraint errors, -- etc. -- -- See for a tutorial covering the language -- basics. module Database.Selda ( -- * Running queries MonadSelda, Backend , SeldaError (..), ValidationError , SeldaT, SeldaM , Relational, Only (..), The (..) , Table (tableName), Query, Row, Col, Res, Result , query, queryInto , transaction, withoutForeignKeyEnforcement , newUuid -- * Constructing queries , SqlType (..), SqlRow (..), GSqlRow, SqlEnum (..) , Columns, Same , Order (..) , (:*:)(..) , select, selectValues, from, distinct , restrict, limit , order, ascending, descending , orderRandom , union, unionAll, inner, suchThat -- * Working with selectors , Selector, Coalesce , HasField, FieldType, IsLabel , (!), (?), Assignment ((:=)), with , (+=), (-=), (*=), (||=), (&&=), ($=) -- * Expressions over columns , Set (..), Monoid (..), Semigroup (..) , ID, invalidId, isInvalidId, untyped, fromId, toId , IsUUID (..), UUID', typedUuid, untypedUuid , RowID, invalidRowId, isInvalidRowId, fromRowId, toRowId , (.==), (./=), (.>), (.<), (.>=), (.<=), like , (.&&), (.||), not_ , literal, is, int, float, text, true, false, null_ , roundTo, length_, isNull, ifThenElse, ifNull, matchNull , toUpper, toLower , new, row, only , Mappable (..) -- * Converting between column types , round_, just, fromBool, fromInt, toString -- * Inner queries , Aggr, Aggregates, OuterCols, AggrCols, LeftCols, Inner, SqlOrd , innerJoin, leftJoin , aggregate, groupBy , count, avg, sum_, max_, min_ -- * Modifying tables , insert, insert_, insertWithPK, tryInsert, insertUnless, insertWhen, def , update, update_, upsert , deleteFrom, deleteFrom_ -- * Prepared statements , Preparable, Prepare , prepared -- * Defining schemas , Generic , TableName, ColName, Attr (..), Attribute , ForeignKey (..) , SelectorLike, Group (..), sel , table, tableFieldMod , primary, autoPrimary, weakAutoPrimary , untypedAutoPrimary, weakUntypedAutoPrimary , unique , IndexMethod (..), index, indexUsing -- * Creating and dropping tables , createTable, tryCreateTable , dropTable, tryDropTable -- * Tuple convenience functions , Tup, Head , first, second, third, fourth, fifth -- * Useful re-exports , MonadIO, MonadMask, liftIO , Text, Day, TimeOfDay, UTCTime, UUID ) where import Control.Monad.Catch (MonadMask) import Data.Typeable ( Typeable, eqT, (:~:)(..) ) import Database.Selda.Backend.Internal ( SqlType(..), SeldaM, SeldaT, MonadSelda(Backend), SeldaError(..) ) import Database.Selda.SqlType ( UUID, UUID'(..), ID(..), RowID, SqlEnum(..), invalidRowId, isInvalidRowId, toRowId, fromRowId, typedUuid, toId, fromId, invalidId, isInvalidId ) import Database.Selda.Table.Type ( IndexMethod(..) ) import Database.Selda.Types ( TableName, ColName, Tup, Head, type (:*:)(..), first, second, third, fourth, fifth ) import Database.Selda.Column ( BinOp(Like, Eq, Neq, Gt, Lt, Gte, Lte, And, Or), UnOp(Not, IsNull), Exp(If, InQuery, InList, BinOp, UnOp), UntypedCol(Untyped), Same(..), Row(..), Col(..), Columns, literal, liftC3, liftC ) import Database.Selda.Compile ( Result, Res, compQueryWithFreshScope ) import Database.Selda.FieldSelectors ( IsLabel, HasField, FieldType ) import Database.Selda.Frontend ( MonadIO(..), query, queryInto, insert, tryInsert, upsert, insertUnless, insertWhen, insert_, insertWithPK, update, update_, deleteFrom, deleteFrom_, createTable, tryCreateTable, dropTable, tryDropTable, transaction, withoutForeignKeyEnforcement ) import Database.Selda.Generic ( Generic, gRow, gNew, Relational, def ) import Database.Selda.Inner ( Aggregates, LeftCols, AggrCols, OuterCols, Inner, Aggr, liftAggr, aggr ) import Database.Selda.Prepared ( Prepare, Preparable, prepared ) import Database.Selda.Query ( select, selectValues, union, unionAll, restrict, aggregate, leftJoin, innerJoin, groupBy, limit, order, orderRandom, distinct ) import Database.Selda.Query.Type ( Query ) import Database.Selda.Selectors ( Selector, Assignment(..), Coalesce, (!), (?), ($=), with ) import Database.Selda.SQL ( Order(..) ) import Database.Selda.SqlRow ( GSqlRow, SqlRow(..) ) import Database.Selda.Table ( Table(tableName), ForeignKey(..), Attribute, SelectorLike, Attr(..), Group(..), table, tableFieldMod, primary, index, indexUsing, autoPrimary, weakAutoPrimary, untypedAutoPrimary, weakUntypedAutoPrimary, unique ) import Database.Selda.Table.Validation ( ValidationError ) import Database.Selda.Unsafe ( cast, fun, fun2, operator ) import Data.Proxy ( Proxy(..) ) import Data.String (IsString) import Data.Text (Text) import Data.Time (Day, TimeOfDay, UTCTime) import GHC.Generics (Rep) import qualified GHC.Generics as G (from) import Unsafe.Coerce ( unsafeCoerce ) import System.Random (randomIO) import GHC.TypeLits as TL ( TypeError, ErrorMessage(Text, (:<>:), ShowType, (:$$:)) ) -- | Any column type that can be used with the 'min_' and 'max_' functions. class SqlType a => SqlOrd a instance {-# OVERLAPPABLE #-} (SqlType a, Num a) => SqlOrd a instance SqlOrd RowID instance SqlOrd Text instance SqlOrd Day instance SqlOrd UTCTime instance SqlOrd TimeOfDay instance SqlOrd a => SqlOrd (Maybe a) instance Typeable a => SqlOrd (ID a) -- | Wrapper for single column tables. -- Use this when you need a table with only a single column, with 'table' or -- 'selectValues'. newtype Only a = Only a deriving ( Generic , Show , Read , Eq , Ord , Enum , Num , Integral , Fractional , Real , IsString ) instance SqlType a => SqlRow (Only a) instance (TypeError ( 'TL.Text "'Only " ':<>: 'ShowType a ':<>: 'TL.Text "' is not a proper SQL type." ':$$: 'TL.Text "Use 'the' to access the value of the column." ), Typeable a) => SqlType (Only a) where mkLit = error "unreachable" sqlType = error "unreachable" fromSql = error "unreachable" defaultValue = error "unreachable" -- | Any type which is backed by an UUID. class IsUUID a where uuid :: UUID -> a instance IsUUID UUID where uuid = id instance IsUUID (UUID' a) where uuid = typedUuid -- | Generate a new random UUID using the system's random number generator. -- UUIDs generated this way are (astronomically likely to be) unique, -- but not necessarily unpredictable. -- -- For applications where unpredictability is crucial, take care to use a -- proper cryptographic PRNG to generate your UUIDs. newUuid :: (MonadIO m, IsUUID uuid) => m uuid newUuid = uuid <$> liftIO randomIO -- | Annotation to force the type of a polymorphic label (i.e. @#foo@) to -- be a selector. This is useful, for instance, when defining unique -- constraints: @sel #foo :- unique@. sel :: Selector t a -> Selector t a sel = id -- | Add the given column to the column pointed to by the given selector. (+=) :: (SqlType a, Num (Col s a)) => Selector t a -> Col s a -> Assignment s t s += c = s $= (+ c) infixl 2 += -- | Subtract the given column from the column pointed to by the given selector. (-=) :: (SqlType a, Num (Col s a)) => Selector t a -> Col s a -> Assignment s t s -= c = s $= (\x -> x - c) infixl 2 -= -- | Multiply the column pointed to by the given selector, by the given column. (*=) :: (SqlType a, Num (Col s a)) => Selector t a -> Col s a -> Assignment s t s *= c = s $= (* c) infixl 2 *= -- | Logically @OR@ the column pointed to by the given selector with -- the given column. (||=) :: Selector t Bool -> Col s Bool -> Assignment s t s ||= c = s $= (.|| c) infixl 2 ||= -- | Logically @AND@ the column pointed to by the given selector with -- the given column. (&&=) :: Selector t Bool -> Col s Bool -> Assignment s t s &&= c = s $= (.&& c) infixl 2 &&= class The a where type TheOnly a -- | Extract the value of a row from a singleton table. the :: a -> TheOnly a instance The (Only a) where type TheOnly (Only a) = a the (Only x) = x instance The (Row s (Only a)) where type TheOnly (Row s (Only a)) = Col s a the (Many [Untyped x]) = One (unsafeCoerce x) the (Many _) = error "BUG: non-singleton Only-column" -- | Create a singleton table column from an appropriate value. only :: SqlType a => Col s a -> Row s (Only a) only (One x) = Many [Untyped x] -- | Create a new row with the given fields. -- Any unassigned fields will contain their default values. new :: forall s a. Relational a => [Assignment s a] -> Row s a new fields = Many (gNew (Proxy :: Proxy (Rep a))) `with` fields -- | Create a new row from the given value. -- This can be useful when you want to update all or most of a row: -- -- > update users (#uid `is` user_id) -- > (\old -> row user_info `with` [...]) row :: forall s a. Relational a => a -> Row s a row x = Many (gRow (G.from x)) -- | Convenient shorthand for @fmap (! sel) q@. -- The following two queries are quivalent: -- -- > q1 = name `from` select people -- > q2 = do -- > person <- select people -- > return (person ! name) from :: (Typeable t, SqlType a) => Selector t a -> Query s (Row s t) -> Query s (Col s a) from s q = (! s) <$> q infixr 7 `from` -- | Explicitly create an inner query. Equivalent to @innerJoin (const true)@. -- -- Sometimes it's handy, for performance -- reasons and otherwise, to perform a subquery and restrict only that query -- before adding the result of the query to the result set, instead of first -- adding the query to the result set and restricting the whole result set -- afterwards. inner :: (Columns a, Columns (OuterCols a)) => Query (Inner s) a -> Query s (OuterCols a) inner = innerJoin (const true) -- | Create and filter an inner query, before adding it to the current result -- set. -- -- @q `suchThat` p@ is generally more efficient than -- @select q >>= \x -> restrict (p x) >> pure x@. suchThat :: (Columns a, Columns (OuterCols a)) => Query (Inner s) a -> (a -> Col (Inner s) Bool) -> Query s (OuterCols a) suchThat q p = inner $ do x <- q restrict (p x) return x infixr 7 `suchThat` -- | Comparisons over columns. -- Note that when comparing nullable (i.e. @Maybe@) columns, SQL @NULL@ -- semantics are used. This means that comparing to a @NULL@ field will remove -- the row in question from the current set. -- To test for @NULL@, use 'isNull' instead of @.== literal Nothing@. (.==), (./=) :: (Same s t, SqlType a) => Col s a -> Col t a -> Col s Bool (.>), (.<), (.>=), (.<=) :: (Same s t, SqlOrd a) => Col s a -> Col t a -> Col s Bool (.==) = liftC2 $ BinOp Eq (./=) = liftC2 $ BinOp Neq (.>) = liftC2 $ BinOp Gt (.<) = liftC2 $ BinOp Lt (.>=) = liftC2 $ BinOp Gte (.<=) = liftC2 $ BinOp Lte infixl 4 .== infixl 4 ./= infixl 4 .> infixl 4 .< infixl 4 .>= infixl 4 .<= -- | Is the given column null? isNull :: SqlType a => Col s (Maybe a) -> Col s Bool isNull = liftC $ UnOp IsNull -- | Applies the given function to the given nullable column where it isn't null, -- and returns the given default value where it is. -- -- This is the Selda equivalent of 'maybe'. matchNull :: (SqlType a, SqlType b, Same s t) => Col s b -> (Col s a -> Col s b) -> Col t (Maybe a) -> Col s b matchNull nullvalue f x = ifThenElse (isNull x) nullvalue (f (cast x)) -- | If the second value is Nothing, return the first value. Otherwise return -- the second value. ifNull :: (Same s t, SqlType a) => Col s a -> Col t (Maybe a) -> Col s a ifNull nullvalue x = ifThenElse (isNull x) nullvalue (cast x) -- | Any container type which can be mapped over. -- Sort of like 'Functor', if you squint a bit. class Mappable f where type Container f a (.<$>) :: (SqlType a, SqlType b) => (Col s a -> Col s b) -> f s (Container f a) -> f s (Container f b) infixl 4 .<$> instance Mappable Aggr where type Container Aggr a = a (.<$>) = liftAggr instance Mappable Col where type Container Col a = Maybe a f .<$> mx = cast (f (cast mx)) -- | Any container type for which we can check object membership. class Set set where -- | Is the given column contained in the given set? isIn :: (Same s t, SqlType a) => Col s a -> set (Col t a) -> Col s Bool infixl 4 `isIn` instance Set [] where isIn _ [] = false isIn (One x) xs = One $ InList x [c | One c <- xs] instance Set (Query s) where isIn (One x) = One . InQuery x . snd . compQueryWithFreshScope (.&&), (.||) :: Same s t => Col s Bool -> Col t Bool -> Col s Bool (.&&) = liftC2 $ BinOp And (.||) = liftC2 $ BinOp Or infixr 3 .&& infixr 2 .|| -- | Ordering for 'order'. ascending, descending :: Order ascending = Asc descending = Desc -- | Lift a non-nullable column to a nullable one. -- Useful for creating expressions over optional columns: -- -- > data Person = Person {name :: Text, age :: Int, pet :: Maybe Text} -- > deriving Generic -- > instance SqlRow Person -- > -- > people :: Table Person -- > people = table "people" [] -- > -- > peopleWithCats = do -- > person <- select people -- > restrict (person ! #pet .== just "cat") -- > return (person ! #name) just :: SqlType a => Col s a -> Col s (Maybe a) just = cast -- | Returns 'true' if the given field in the given row is equal to the given -- literal. is :: forall r s c. SqlType c => Selector r c -> c -> Row s r -> Col s Bool is s x r = r ! s .== (literal x :: Col s c) -- | SQL NULL, at any type you like. null_ :: SqlType a => Col s (Maybe a) null_ = literal Nothing -- | Specialization of 'literal' for integers. int :: Int -> Col s Int int = literal -- | Specialization of 'literal' for doubles. float :: Double -> Col s Double float = literal -- | Specialization of 'literal' for text. text :: Text -> Col s Text text = literal -- | True and false boolean literals. true, false :: Col s Bool true = literal True false = literal False -- | The SQL @LIKE@ operator; matches strings with @%@ wildcards. -- For instance: -- -- > "%gon" `like` "dragon" .== true like :: Same s t => Col s Text -> Col t Text -> Col s Bool like = liftC2 $ BinOp Like infixl 4 `like` -- | The number of non-null values in the given column. count :: SqlType a => Col s a -> Aggr s Int count = aggr "COUNT" -- | The average of all values in the given column. avg :: (SqlType a, Num a) => Col s a -> Aggr s (Maybe a) avg = aggr "AVG" -- | The greatest value in the given column. Texts are compared lexically. max_ :: SqlOrd a => Col s a -> Aggr s (Maybe a) max_ = aggr "MAX" -- | The smallest value in the given column. Texts are compared lexically. min_ :: SqlOrd a => Col s a -> Aggr s (Maybe a) min_ = aggr "MIN" -- | Sum all values in the given column. sum_ :: forall a b s. (SqlType a, SqlType b, Num a, Num b) => Col s a -> Aggr s b sum_ = liftAggr (ifNull (0::Col s b) . cast) . aggr "SUM" -- | Round a value to the nearest integer. Equivalent to @roundTo 0@. round_ :: forall s a. (SqlType a, Num a) => Col s Double -> Col s a round_ = case eqT :: Maybe (a :~: Double) of Just Refl -> fun "ROUND" _ -> cast . fun "ROUND" -- | Round a column to the given number of decimals places. roundTo :: Col s Int -> Col s Double -> Col s Double roundTo = flip $ fun2 "ROUND" -- | Calculate the length of a string column. length_ :: Col s Text -> Col s Int length_ = fun "LENGTH" -- | Boolean negation. not_ :: Col s Bool -> Col s Bool not_ = liftC $ UnOp Not -- | Convert a boolean column to any numeric type. fromBool :: (SqlType a, Num a) => Col s Bool -> Col s a fromBool = cast -- | Convert an integer column to any numeric type. fromInt :: (SqlType a, Num a) => Col s Int -> Col s a fromInt = cast -- | Convert any SQL type to a string. toString :: SqlType a => Col s a -> Col s Text toString = cast -- | Convert the given string to uppercase. toUpper :: Col s Text -> Col s Text toUpper = fun "UPPER" -- | Convert the given string to lowercase. toLower :: Col s Text -> Col s Text toLower = fun "LOWER" instance Semigroup (Col s Text) where (<>) = operator "||" instance Monoid (Col s Text) where mempty = "" -- | Perform a conditional on a column ifThenElse :: (Same s t, Same t u, SqlType a) => Col s Bool -> Col t a -> Col u a -> Col s a ifThenElse = liftC3 If