selda 0.3.2.0 → 0.3.3.0
raw patch · 17 files changed
+310/−139 lines, 17 files
Files
- ChangeLog.md +7/−0
- README.md +0/−1
- selda.cabal +6/−2
- src/Database/Selda.hs +35/−11
- src/Database/Selda/Backend.hs +4/−3
- src/Database/Selda/Backend/Internal.hs +3/−2
- src/Database/Selda/Frontend.hs +8/−4
- src/Database/Selda/Generic.hs +1/−3
- src/Database/Selda/Migrations.hs +6/−6
- src/Database/Selda/Query.hs +1/−2
- src/Database/Selda/Selectors.hs +19/−66
- src/Database/Selda/Selectors/FieldSelectors.hs +104/−0
- src/Database/Selda/Selectors/MakeSelectors.hs +92/−0
- src/Database/Selda/SqlRow.hs +14/−1
- src/Database/Selda/SqlType.hs +8/−3
- src/Database/Selda/Table.hs +1/−34
- src/Database/Selda/Validation.hs +1/−1
ChangeLog.md view
@@ -1,6 +1,13 @@ # Revision history for Selda +## 0.3.3.0 -- 2018-09-01++* Ad hoc selectors using OverloadedLabels.+* Shorter build times.+* Minor API updates and simplifications.++ ## 0.3.2.0 -- 2018-08-07 * Some aggregates are now nullable.
README.md view
@@ -145,4 +145,3 @@ * Streaming * MySQL/MariaDB backend. * MSSQL backend.-* Automatically sanity check changelog, versions and date before release.
selda.cabal view
@@ -1,5 +1,5 @@ name: selda-version: 0.3.2.0+version: 0.3.3.0 synopsis: Multi-backend, high-level EDSL for interacting with SQL databases. description: This package provides an EDSL for writing portable, type-safe, high-level database code. Its feature set includes querying and modifying databases,@@ -47,6 +47,7 @@ Database.Selda Database.Selda.Backend Database.Selda.Migrations+ Database.Selda.SqlType Database.Selda.Unsafe Database.Selda.Validation other-modules:@@ -62,17 +63,20 @@ Database.Selda.Query Database.Selda.Query.Type Database.Selda.Selectors+ Database.Selda.Selectors.MakeSelectors Database.Selda.SQL Database.Selda.SQL.Print Database.Selda.SQL.Print.Config Database.Selda.SqlRow- Database.Selda.SqlType Database.Selda.Table Database.Selda.Table.Compile Database.Selda.Table.Type Database.Selda.Table.Validation Database.Selda.Transform Database.Selda.Types+ if impl(ghc >= 7.11)+ other-modules:+ Database.Selda.Selectors.FieldSelectors other-extensions: OverloadedStrings GADTs
src/Database/Selda.hs view
@@ -28,9 +28,8 @@ , Table, Query, Row, Col, Res, Result , query, queryInto , transaction, setLocalCache, withoutForeignKeyEnforcement+ -- * Constructing queries- , Selector, Source, Selected, (!), Assignment ((:=)), with- , (+=), (-=), (*=), (||=), (&&=), ($=) , SqlType (..), SqlRow (..), SqlEnum (..) , Columns , Order (..)@@ -40,52 +39,71 @@ , order, ascending, descending , orderRandom , inner, suchThat++ -- * Working with selectors+ , Selector, Coalesce+#if MIN_VERSION_base(4, 9, 0)+ , HasField, FieldType, IsLabel+#endif+ , (!), (?), Assignment ((:=)), with+ , (+=), (-=), (*=), (||=), (&&=), ($=)+ -- * Expressions over columns , Set (..)- , ID, invalidId, isInvalidId, untyped, toId+ , ID, invalidId, isInvalidId, untyped, fromId, toId , RowID, invalidRowId, isInvalidRowId, fromRowId, toRowId , (.==), (./=), (.>), (.<), (.>=), (.<=), like , (.&&), (.||), not_- , literal, int, float, text, true, false, null_+ , literal, is, int, float, text, true, false, null_ , roundTo, length_, isNull, ifThenElse, ifNull, matchNull , new, 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- , Selectors, ForeignKey (..)+ , Selectors, GSelectors, ForeignKey (..) , table, tableFieldMod, tableWithSelectors, selectors , primary, autoPrimary, untypedAutoPrimary, unique , IndexMethod (..), index, indexUsing+ -- * Creating and dropping tables , createTable, tryCreateTable , dropTable, tryDropTable+ -- * Compiling and inspecting queries , OnError (..) , compile , compileCreateTable, compileDropTable , compileInsert, compileUpdate+ -- * Tuple convenience functions , Tup, Head , first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth+ -- * Useful re-exports- , MonadIO, liftIO+ , MonadIO, MonadMask, liftIO , Text, Day, TimeOfDay, UTCTime ) where+import Control.Monad.Catch (MonadMask) import Data.Typeable (Typeable) import Database.Selda.Backend import Database.Selda.Column@@ -97,9 +115,9 @@ import Database.Selda.Query import Database.Selda.Query.Type import Database.Selda.Selectors+import Database.Selda.Selectors.MakeSelectors import Database.Selda.SQL hiding (distinct) import Database.Selda.SqlRow-import Database.Selda.SqlType import Database.Selda.Table import Database.Selda.Table.Compile import Database.Selda.Table.Validation@@ -114,6 +132,7 @@ import Unsafe.Coerce #if MIN_VERSION_base(4, 9, 0)+import Database.Selda.Selectors.FieldSelectors import GHC.TypeLits as TL #endif @@ -215,10 +234,10 @@ -- > q2 = do -- > person <- select people -- > return (person ! name)-from :: (Typeable a, SqlType b)- => Selector (Source a) b- -> Query s (Row s a)- -> Query s (Col s (Selected a b))+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` @@ -343,6 +362,11 @@ -- > return (name ! sName) 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 :: SqlType c => Selector r c -> c -> Row s r -> Col s Bool+is s x r = r ! s .== literal x -- | SQL NULL, at any type you like. null_ :: SqlType a => Col s (Maybe a)
src/Database/Selda/Backend.hs view
@@ -3,22 +3,23 @@ module Database.Selda.Backend ( MonadSelda (..), SeldaT, SeldaM, SeldaError (..) , StmtID, BackendID (..), QueryRunner, SeldaBackend (..), SeldaConnection- , SqlType (..), SqlValue (..), SqlTypeRep (..)+ , SqlValue (..) , IndexMethod (..)- , Param (..), Lit (..), ColAttr (..)+ , Param (..), ColAttr (..) , PPConfig (..), defPPConfig , TableName, ColName, ColumnInfo (..) , columnInfo, fromColInfo , mkTableName, mkColName, fromTableName, fromColName, rawTableName , newConnection, allStmts, seldaBackend , runSeldaT, seldaClose- , sqlDateTimeFormat, sqlDateFormat, sqlTimeFormat+ , module SqlType ) where import Control.Monad import Control.Monad.Catch import Control.Monad.IO.Class import Data.IORef import Database.Selda.Backend.Internal+import Database.Selda.SqlType as SqlType import Database.Selda.Table (IndexMethod (..)) import Database.Selda.Types
src/Database/Selda/Backend/Internal.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE GeneralizedNewtypeDeriving, DefaultSignatures #-} -- | Internal backend API. module Database.Selda.Backend.Internal ( StmtID, BackendID (..)@@ -218,7 +218,7 @@ -- 'wrapTransaction' flush the entire cache and disable caching when -- invoked. If you want to use Selda's built-in caching mechanism, you will -- need to implement these operations yourself.-class (MonadIO m, MonadMask m) => MonadSelda m where+class MonadIO m => MonadSelda m where -- | Get the connection in use by the computation. seldaConnection :: m SeldaConnection @@ -245,6 +245,7 @@ -> m () -- ^ Signal transaction rollback to SQL backend. -> m a -- ^ Transaction to perform. -> m a+ default wrapTransaction :: MonadMask m => m () -> m () -> m a -> m a wrapTransaction commit rollback act = do bracketOnError (pure ()) (const rollback)
src/Database/Selda/Frontend.hs view
@@ -92,7 +92,7 @@ -- -- Like 'insert', if even one of the inserted rows would cause a constraint -- violation, the whole insert operation fails.-tryInsert :: (MonadSelda m, Relational a) => Table a -> [a] -> m Bool+tryInsert :: (MonadSelda m, MonadCatch m, Relational a) => Table a -> [a] -> m Bool tryInsert tbl row = do mres <- try $ insert tbl row case mres of@@ -241,7 +241,7 @@ -- will be rolled back and the exception re-thrown, even if the exception -- is caught and handled within the transaction. transaction :: MonadSelda m => m a -> m a-transaction m =+transaction m = do wrapTransaction (void $ exec "COMMIT" []) (void $ exec "ROLLBACK" []) $ do exec "BEGIN TRANSACTION" [] *> m @@ -253,7 +253,7 @@ -- Use with extreme caution, preferably only for migrations. -- -- On the PostgreSQL backend, at least PostgreSQL 9.6 is required.-withoutForeignKeyEnforcement :: MonadSelda m => m a -> m a+withoutForeignKeyEnforcement :: (MonadSelda m, MonadMask m) => m a -> m a withoutForeignKeyEnforcement m = do b <- seldaBackend bracket_ (liftIO $ disableForeignKeys b True)@@ -309,4 +309,8 @@ exec :: MonadSelda m => Text -> [Param] -> m Int exec q ps = do backend <- seldaBackend- fmap fst . liftIO $ runStmt backend q ps+ liftIO $ execIO backend q ps++-- | Like 'exec', but in 'IO'.+execIO :: SeldaBackend -> Text -> [Param] -> IO Int+execIO backend q ps = fmap fst $ runStmt backend q ps
src/Database/Selda/Generic.hs view
@@ -19,17 +19,16 @@ #if MIN_VERSION_base(4, 9, 0) import qualified GHC.TypeLits as TL import qualified GHC.Generics as G ((:+:)(..))+import qualified Database.Selda.Column as C (Col) #endif import Control.Exception (Exception (..), try, throw) import System.IO.Unsafe import Database.Selda.Types-import Database.Selda.Selectors import Database.Selda.SqlType import Database.Selda.SqlRow (SqlRow) import Database.Selda.Table.Type import Database.Selda.SQL (Param (..)) import Database.Selda.Exp (Exp (Col, Lit), UntypedCol (..))-import qualified Database.Selda.Column as C (Col) #if !MIN_VERSION_base(4, 11, 0) import Data.Monoid #endif@@ -45,7 +44,6 @@ ( Generic a , SqlRow a , GRelation (Rep a)- , GSelectors a (Rep a) ) -- | Extract all insert parameters from a generic value.
src/Database/Selda/Migrations.hs view
@@ -39,7 +39,7 @@ -- -- The migration is performed as a migration, ensuring that either the entire -- migration passes, or none of it does.-migrate :: (MonadSelda m, Relational a, Relational b)+migrate :: (MonadSelda m, MonadMask m, Relational a, Relational b) => Table a -- ^ Table to migrate from. -> Table b -- ^ Table to migrate to. -> (Row () a -> Row () b)@@ -49,20 +49,20 @@ -- | Like 'migrate', but allows the column upgrade to access -- the entire database.-migrateM :: (MonadSelda m, Relational a, Relational b)+migrateM :: (MonadSelda m, MonadMask m, Relational a, Relational b) => Table a -> Table b -> (Row s a -> Query s (Row s b)) -> m () migrateM t1 t2 upg = migrateAll True [Migration t1 t2 upg] -wrap :: MonadSelda m => Bool -> m a -> m a+wrap :: (MonadSelda m, MonadMask m) => Bool -> m a -> m a wrap enforceFKs | enforceFKs = transaction | otherwise = withoutForeignKeyEnforcement -- | Perform all given migrations as a single transaction.-migrateAll :: MonadSelda m+migrateAll :: (MonadSelda m, MonadMask m) => Bool -- ^ Enforce foreign keys during migration? -> MigrationStep -- ^ Migration step to perform. -> m ()@@ -82,7 +82,7 @@ -- indexed columns are not taken into account. Two columns @c1@ and @c2@ are -- considered to be identical if @c1@ is indexed with index method @foo@ and -- @c2@ is indexed with index method @bar@.-autoMigrate :: MonadSelda m+autoMigrate :: (MonadSelda m, MonadMask m) => Bool -- ^ Enforce foreign keys during migration? -> [MigrationStep] -- ^ Migration steps to perform. -> m ()@@ -110,7 +110,7 @@ -- | Workhorse for migration. -- Is NOT performed as a transaction, so exported functions need to -- properly wrap calls this function.-migrateInternal :: (MonadSelda m, Relational a, Relational b)+migrateInternal :: (MonadSelda m, MonadThrow m, Relational a, Relational b) => Table a -> Table b -> (Row s a -> Query s (Row s b))
src/Database/Selda/Query.hs view
@@ -11,8 +11,7 @@ import Database.Selda.Inner import Database.Selda.Query.Type import Database.Selda.SQL as SQL-import Database.Selda.SqlType (SqlType, Lit (LNull), SqlTypeRep (..))-import Database.Selda.SqlRow (SqlRow (nestedCols))+import Database.Selda.SqlType (SqlType) import Database.Selda.Table import Database.Selda.Transform import Control.Monad.State.Strict
src/Database/Selda/Selectors.hs view
@@ -1,49 +1,40 @@-{-# LANGUAGE ScopedTypeVariables, TypeFamilies, MultiParamTypeClasses #-}-{-# LANGUAGE TypeOperators, UndecidableInstances, FlexibleInstances #-}-{-# LANGUAGE FlexibleContexts, RankNTypes, AllowAmbiguousTypes, GADTs #-}-{-# LANGUAGE DeriveGeneric, CPP #-}+{-# LANGUAGE TypeFamilies, GADTs, CPP #-} module Database.Selda.Selectors- ( Assignment ((:=)), Selected, Selector, Source, Selectors, GSelectors- , (!), with, ($=)- , selectorsFor, selectorIndex, unsafeSelector+ ( Assignment ((:=)), Selector, Coalesce+ , (!), (?), with, ($=)+ , selectorIndex, unsafeSelector ) where-import Control.Monad.State.Strict import Database.Selda.SqlRow (SqlRow) import Database.Selda.SqlType-import Database.Selda.Types import Database.Selda.Column import Data.List (foldl')-import Data.Proxy-import GHC.Generics hiding (Selector, (:*:))-import qualified GHC.Generics as G import Unsafe.Coerce --- | The result of a '(!)' operation.--- If either the source row, the column to extract,--- or both, is nullable, the result is also nullable.-type family Selected a b where- Selected (Maybe a) (Maybe b) = Maybe b- Selected (Maybe a) b = Maybe b- Selected a b = b---- | The source type of a '(!)' operation.-type family Source a where- Source (Maybe a) = a- Source a = a+-- | Coalesce nested nullable column into a single level of nesting.+type family Coalesce a where+ Coalesce (Maybe (Maybe a)) = Coalesce (Maybe a)+ Coalesce a = a -- | A selector indicating the nth (zero-based) column of a table. -- -- Will cause errors in queries during compilation, execution, or both, -- unless handled with extreme care. You really shouldn't use it at all.-unsafeSelector :: SqlRow a => Int -> Selector a b+unsafeSelector :: (SqlRow a, SqlType b) => Int -> Selector a b unsafeSelector = Selector -- | Extract the given column from the given row.--- Extracting a value from a nullable column will yield a nullable value.--- In other words, this operator is null-coalescing.-(!) :: SqlType b => Row s a -> Selector (Source a) b -> Col s (Selected a b)+(!) :: SqlType a => Row s t -> Selector t a -> Col s a (Many xs) ! (Selector i) = case xs !! i of Untyped x -> One (unsafeCoerce x)+infixl 9 ! +-- | Extract the given column from the given nullable row.+-- Nullable rows usually result from left joins.+-- If a nullable column is extracted from a nullable row, the resulting+-- nested @Maybe@s will be squashed into a single level of nesting.+(?) :: SqlType a => Row s (Maybe t) -> Selector t a -> Col s (Coalesce (Maybe a))+Many xs ? Selector i = case xs !! i of Untyped x -> One (unsafeCoerce x)+infixl 9 ?+ upd :: Row s a -> Assignment s a -> Row s a upd (Many xs) (Selector i := (One x')) = case splitAt i xs of@@ -84,41 +75,3 @@ -- 'with' functions to get and set values on rows, or to specify -- foreign keys. newtype Selector t a = Selector {selectorIndex :: Int}---- | Generate selectors for the given type.-selectorsFor :: forall r. GSelectors r (Rep r) => Proxy r -> Selectors r-selectorsFor = flip evalState 0 . mkSel (Proxy :: Proxy (Rep r))---- | An inductive tuple of selectors for the given relation.-type Selectors r = Sels r (Rep r)--type family Sels t f where- Sels t ((a G.:*: b) G.:*: c) = Sels t (a G.:*: (b G.:*: c))- Sels t (a G.:*: b) = Sels t a :*: Sels t b- Sels t (M1 x y f) = Sels t f- Sels t (K1 i a) = Selector t a---- | Any table type that can have selectors generated.-class GSelectors t (f :: * -> *) where- mkSel :: Proxy f -> Proxy t -> State Int (Sels t f)--instance SqlType a => GSelectors t (K1 i a) where- mkSel _ _ = Selector <$> state (\n -> (n, n+1))--instance (GSelectors t f, Sels t f ~ Sels t (M1 x y f)) =>- GSelectors t (M1 x y f) where- mkSel _ = mkSel (Proxy :: Proxy f)--instance GSelectors t (a G.:*: (b G.:*: c)) =>- GSelectors t ((a G.:*: b) G.:*: c) where- mkSel _ = mkSel (Proxy :: Proxy (a G.:*: (b G.:*: c)))--instance {-# OVERLAPPABLE #-}- ( GSelectors t a- , GSelectors t b- , Sels t (a G.:*: b) ~ (Sels t a :*: Sels t b)- ) => GSelectors t (a G.:*: b) where- mkSel _ p = do- x <- mkSel (Proxy :: Proxy a) p- xs <- mkSel (Proxy :: Proxy b) p- return (x :*: xs)
+ src/Database/Selda/Selectors/FieldSelectors.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE DataKinds, KindSignatures, TypeOperators, PolyKinds #-}+{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables, AllowAmbiguousTypes, TypeFamilies #-}+{-# LANGUAGE UndecidableInstances, ConstraintKinds, UndecidableSuperClasses #-}+{-# LANGUAGE TypeApplications, CPP #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- | Create Selda selectors from plain record field selectors.+-- Requires the @OverloadedLabels@ language extension.+module Database.Selda.Selectors.FieldSelectors+ (FieldType, HasField, IsLabel+ ) where+import Database.Selda.Generic (Relational)+import Database.Selda.Selectors as S+import Database.Selda.SqlType (SqlType)+import Data.Kind (Constraint)+import GHC.Generics+import GHC.TypeLits+import GHC.OverloadedLabels++-- | Get the next nested type.+type family GetFieldType (f :: * -> *) :: * where+ GetFieldType (M1 c i f) = GetFieldType f+ GetFieldType (K1 i a) = a++-- | Get the type of the field @name@ from the generic representation @a@,+-- returning the default value @b@ if the field does not exist.+type family GFieldType (a :: * -> *) (b :: *) (name :: Symbol) :: * where+ GFieldType (M1 S ('MetaSel ('Just name) su ss ds) f) b name = GetFieldType f+ GFieldType (M1 c i a) b name = GFieldType a b name+ GFieldType (a :*: b) c name = GFieldType a (GFieldType b c name) name+ GFieldType a b name = b++-- | The type of the @name@ field, in the record type @t@.+type FieldType name t = GFieldType (Rep t) (NoSuchSelector t name) name++type family NonError (t :: k) :: Constraint where+ NonError (NoSuchSelector t s) = TypeError+ ( 'Text "Row type '" ':<>: 'ShowType t ':<>:+ 'Text "' has no selector " ':<>: 'ShowType s ':<>: 'Text "."+ )+ NonError t = ()++-- | Internal representation of the "no such selector" error message.+data NoSuchSelector (t :: *) (s :: Symbol)++-- | Any table type @t@, which has a field named @name@.+class ( Relational t+ , SqlType (FieldType name t)+ , GRSel name (Rep t)+ , NonError (FieldType name t)) =>+ HasField (name :: Symbol) t++instance ( Relational t+ , SqlType (FieldType name t)+ , GRSel name (Rep t)+ , NonError (FieldType name t)) =>+ HasField (name :: Symbol) t++instance (Relational t, HasField name t, FieldType name t ~ a) =>+ IsLabel name (S.Selector t a) where+#if MIN_VERSION_base(4, 10, 0)+ fromLabel = field @name @t+#else+ fromLabel _ = field @name @t+#endif++-- | Create a selector from a record selector and a type application.+--+-- For example:+-- > data Foo = Foo+-- > { foo :: Int+-- > , bar :: Text+-- > } deriving Generic+-- > instance SqlRow Foo+-- >+-- > fooTable :: Table Foo+-- > fooTable = table "foo"+-- >+-- > getAllBars :: Query s (Col s Text)+-- > getAllBars = do+-- > t <- select fooTable+-- > return (t ! field @"bar")+field :: forall name t.+ (Relational t, HasField name t)+ => S.Selector t (FieldType name t)+field =+ case gSel @name @(Rep t) 0 of+ Left n -> unsafeSelector n+ _ -> error "unreachable"++class GRSel (s :: Symbol) (f :: * -> *) where+ gSel :: Int -> Either Int Int++instance GRSel name (M1 S ('MetaSel ('Just name) su ss ds) f) where+ gSel = Left++instance {-# OVERLAPPABLE #-} GRSel name f => GRSel name (M1 i s f) where+ gSel = gSel @name @f++instance (GRSel name a, GRSel name b) => GRSel name (a :*: b) where+ gSel n = gSel @name @a n >>= gSel @name @b . succ++instance GRSel name (K1 i a) where+ gSel = Right
+ src/Database/Selda/Selectors/MakeSelectors.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE ScopedTypeVariables, TypeOperators, KindSignatures #-}+{-# LANGUAGE TypeFamilies, FlexibleInstances, MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleContexts, UndecidableInstances #-}+-- | Utilities for creating selectors for non-record types.+-- Importing this module may significantly increase compilation times.+module Database.Selda.Selectors.MakeSelectors+ ( Selectors, GSelectors+ , selectors, tableWithSelectors+ ) where+import Control.Monad.State.Strict+import Data.Proxy+import GHC.Generics hiding (Selector, (:*:))+import qualified GHC.Generics as G+import Database.Selda.Generic (Relational)+import Database.Selda.Selectors+import Database.Selda.SqlRow+import Database.Selda.SqlType+import Database.Selda.Table+import Database.Selda.Types++-- | Generate selector functions for the given table.+-- Selectors can be used to access the fields of a query result tuple, avoiding+-- the need to pattern match on the entire tuple.+--+-- > tbl :: Table (Int, Text)+-- > tbl = table "foo" []+-- > (tblBar :*: tblBaz) = selectors tbl+-- >+-- > q :: Query s Text+-- > q = do+-- > row <- select tbl+-- > return (row ! tblBaz)+selectors :: forall a. (Relational a, GSelectors a (Rep a))+ => Table a+ -> Selectors a+selectors _ = selectorsFor (Proxy :: Proxy a)++-- | A pair of the table with the given name and columns, and all its selectors.+-- For example:+--+-- > tbl :: Table (Int, Text)+-- > (tbl, tblBar :*: tblBaz)+-- > = tableWithSelectors "foo" []+-- >+-- > q :: Query s Text+-- > q = tblBaz `from` select tbl+tableWithSelectors :: forall a. (Relational a, GSelectors a (Rep a))+ => TableName+ -> [Attr a]+ -> (Table a, Selectors a)+tableWithSelectors name cs = (t, s)+ where+ t = table name cs+ s = selectors t++-- | Generate selectors for the given type.+selectorsFor :: forall r. GSelectors r (Rep r) => Proxy r -> Selectors r+selectorsFor = flip evalState 0 . mkSel (Proxy :: Proxy (Rep r))++-- | An inductive tuple of selectors for the given relation.+type Selectors r = Sels r (Rep r)++type family Sels t f where+ Sels t ((a G.:*: b) G.:*: c) = Sels t (a G.:*: (b G.:*: c))+ Sels t (a G.:*: b) = Sels t a :*: Sels t b+ Sels t (M1 x y f) = Sels t f+ Sels t (K1 i a) = Selector t a++-- | Any table type that can have selectors generated.+class GSelectors t (f :: * -> *) where+ mkSel :: Proxy f -> Proxy t -> State Int (Sels t f)++instance (SqlRow t, SqlType a) => GSelectors t (K1 i a) where+ mkSel _ _ = unsafeSelector <$> state (\n -> (n, n+1))++instance (GSelectors t f, Sels t f ~ Sels t (M1 x y f)) =>+ GSelectors t (M1 x y f) where+ mkSel _ = mkSel (Proxy :: Proxy f)++instance GSelectors t (a G.:*: (b G.:*: c)) =>+ GSelectors t ((a G.:*: b) G.:*: c) where+ mkSel _ = mkSel (Proxy :: Proxy (a G.:*: (b G.:*: c)))++instance {-# OVERLAPPABLE #-}+ ( GSelectors t a+ , GSelectors t b+ , Sels t (a G.:*: b) ~ (Sels t a :*: Sels t b)+ ) => GSelectors t (a G.:*: b) where+ mkSel _ p = do+ x <- mkSel (Proxy :: Proxy a) p+ xs <- mkSel (Proxy :: Proxy b) p+ return (x :*: xs)
src/Database/Selda/SqlRow.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE GeneralizedNewtypeDeriving, DataKinds #-} {-# LANGUAGE UndecidableInstances, FlexibleInstances, FlexibleContexts #-} {-# LANGUAGE TypeOperators, DefaultSignatures, ScopedTypeVariables, CPP #-} #if MIN_VERSION_base(4, 10, 0)@@ -12,6 +12,9 @@ import Database.Selda.SqlType import Data.Typeable import GHC.Generics+#if MIN_VERSION_base(4, 9, 0)+import qualified GHC.TypeLits as TL+#endif newtype ResultReader a = R (State [SqlValue] a) deriving (Functor, Applicative, Monad)@@ -51,6 +54,16 @@ gNextResult = liftM2 (:*:) gNextResult gNextResult gNestedCols _ = gNestedCols (Proxy :: Proxy a) + gNestedCols (Proxy :: Proxy b) +#if MIN_VERSION_base(4, 9, 0)+instance+ (TL.TypeError+ ( 'TL.Text "Selda currently does not support creating tables from sum types."+ 'TL.:$$:+ 'TL.Text "Restrict your table type to a single data constructor."+ )) => GSqlRow (a :+: b) where+ gNextResult = error "unreachable"+ gNestedCols = error "unreachable"+#endif -- * Various instances instance SqlRow a => SqlRow (Maybe a) where
src/Database/Selda/SqlType.hs view
@@ -1,11 +1,11 @@ {-# LANGUAGE GADTs, OverloadedStrings, ScopedTypeVariables, FlexibleInstances #-} {-# LANGUAGE UndecidableInstances, DefaultSignatures #-}--- | Types representable in Selda's subset of SQL.+-- | Types representable as columns in Selda's subset of SQL. module Database.Selda.SqlType ( SqlType (..), SqlEnum (..)- , Lit (..), RowID, ID (..), SqlValue (..), SqlTypeRep (..)+ , Lit (..), RowID, ID, SqlValue (..), SqlTypeRep (..) , invalidRowId, isInvalidRowId, toRowId, fromRowId- , toId, invalidId, isInvalidId+ , fromId, toId, invalidId, isInvalidId, untyped , compLit, litType , sqlDateTimeFormat, sqlDateFormat, sqlTimeFormat ) where@@ -216,6 +216,11 @@ -- Use with caution, preferably only when reading user input. toId :: Int -> ID a toId = ID . toRowId++-- | Create a typed row identifier from an integer.+-- Use with caution, preferably only when reading user input.+fromId :: ID a -> Int+fromId (ID i) = fromRowId i -- | A typed row identifier which is guaranteed to not match any row in any -- table.
src/Database/Selda/Table.hs view
@@ -7,7 +7,7 @@ ( Attr (..), Table (..), Attribute , ColInfo (..), ColAttr (..), IndexMethod (..) , ForeignKey (..)- , table, tableFieldMod, tableWithSelectors, selectors+ , table, tableFieldMod -- , tableWithSelectors, selectors , primary, autoPrimary, untypedAutoPrimary, unique , index, indexUsing , tableExpr@@ -108,39 +108,6 @@ -- | Remove duplicate attributes. tidy :: ColInfo -> ColInfo tidy ci = ci {colAttrs = snub $ colAttrs ci}---- | A pair of the table with the given name and columns, and all its selectors.--- For example:------ > tbl :: Table (Int, Text)--- > (tbl, tblBar :*: tblBaz)--- > = tableWithSelectors "foo" []--- >--- > q :: Query s Text--- > q = tblBaz `from` select tbl-tableWithSelectors :: forall a. Relational a- => TableName- -> [Attr a]- -> (Table a, Selectors a)-tableWithSelectors name cs = (t, s)- where- t = table name cs- s = selectors t---- | Generate selector functions for the given table.--- Selectors can be used to access the fields of a query result tuple, avoiding--- the need to pattern match on the entire tuple.------ > tbl :: Table (Int, Text)--- > tbl = table "foo" []--- > (tblBar :*: tblBaz) = selectors tbl--- >--- > q :: Query s Text--- > q = do--- > row <- select tbl--- > return (row ! tblBaz)-selectors :: forall a. Relational a => Table a -> Selectors a-selectors _ = selectorsFor (Proxy :: Proxy a) -- | Some attribute that may be set on a column of type @c@, in a table of -- type @t@.
src/Database/Selda/Validation.hs view
@@ -26,7 +26,7 @@ -- database. -- Throws a 'ValidationError' if the schema does not validate, or if -- inconsistencies were found.-validateTable :: MonadSelda m => Table a -> m ()+validateTable :: (MonadSelda m, MonadThrow m) => Table a -> m () validateTable t = do validateSchema t diffs <- diffTable t