packages feed

selda-0.5.2.1: src/Database/Selda.hs

{-# 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 <https://valderman.github.io/selda/tutorial> 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