selda 0.3.1.0 → 0.3.2.0
raw patch · 8 files changed
+63/−44 lines, 8 files
Files
- ChangeLog.md +7/−0
- selda.cabal +1/−1
- src/Database/Selda.hs +29/−5
- src/Database/Selda/Column.hs +0/−17
- src/Database/Selda/Inner.hs +4/−2
- src/Database/Selda/Query.hs +7/−4
- src/Database/Selda/Table.hs +14/−14
- src/Database/Selda/Unsafe.hs +1/−1
ChangeLog.md view
@@ -1,6 +1,13 @@ # Revision history for Selda +## 0.3.2.0 -- 2018-08-07++* Some aggregates are now nullable.+* sum_ on an empty table doesn't crash anymore.+* Aggregating over an empty selectValues doesn't crash anymore.++ ## 0.3.1.0 -- 2018-08-06 * Minor API fix when defining table attributes.
selda.cabal view
@@ -1,5 +1,5 @@ name: selda-version: 0.3.1.0+version: 0.3.2.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,
src/Database/Selda.hs view
@@ -47,8 +47,9 @@ , (.==), (./=), (.>), (.<), (.>=), (.<=), like , (.&&), (.||), not_ , literal, int, float, text, true, false, null_- , roundTo, length_, isNull, ifThenElse, matchNull+ , roundTo, length_, isNull, ifThenElse, ifNull, matchNull , new, only+ , Mappable (..) -- * Converting between column types , round_, just, fromBool, fromInt, toString -- * Inner queries@@ -278,6 +279,29 @@ -> 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 :: SqlType a => Col s a -> Col s (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?@@ -354,20 +378,20 @@ count = aggr "COUNT" -- | The average of all values in the given column.-avg :: (SqlType a, Num a) => Col s a -> Aggr s a+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 a+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 a+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_ = castAggr . aggr "SUM"+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
src/Database/Selda/Column.hs view
@@ -81,27 +81,10 @@ abs = liftC $ UnOp Abs signum = liftC $ UnOp Sgn -instance {-# OVERLAPPING #-} (SqlType a, Num a) => Num (Col s (Maybe a)) where- fromInteger = literal . Just . fromInteger- (+) = liftC2 $ BinOp Add- (-) = liftC2 $ BinOp Sub- (*) = liftC2 $ BinOp Mul- negate = liftC $ UnOp Neg- abs = liftC $ UnOp Abs- signum = liftC $ UnOp Sgn- instance Fractional (Col s Double) where fromRational = literal . fromRational (/) = liftC2 $ BinOp Div -instance Fractional (Col s (Maybe Double)) where- fromRational = literal . Just . fromRational- (/) = liftC2 $ BinOp Div- instance Fractional (Col s Int) where fromRational = literal . (truncate :: Double -> Int) . fromRational- (/) = liftC2 $ BinOp Div--instance Fractional (Col s (Maybe Int)) where- fromRational = literal . Just . (truncate :: Double -> Int) . fromRational (/) = liftC2 $ BinOp Div
src/Database/Selda/Inner.hs view
@@ -19,8 +19,10 @@ -- converted into a non-aggregate column. newtype Aggr s a = Aggr {unAggr :: Exp SQL a} -liftAggr :: (Exp SQL a -> Exp SQL b) -> Aggr s a -> Aggr s b-liftAggr f = Aggr . f . unAggr+-- | Lift a function over columns to aggregates.+liftAggr :: (Col s a -> Col s b) -> Aggr s a -> Aggr s b+liftAggr f = Aggr . unOne . f . One . unAggr+ where unOne (One x) = x -- | Denotes an inner query. -- For aggregation, treating sequencing as the cartesian product of queries
src/Database/Selda/Query.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleContexts, OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts, OverloadedStrings, ScopedTypeVariables #-} -- | Query monad and primitive operations. module Database.Selda.Query ( select, selectValues, Database.Selda.Query.distinct@@ -11,10 +11,13 @@ import Database.Selda.Inner import Database.Selda.Query.Type import Database.Selda.SQL as SQL-import Database.Selda.SqlType (SqlType)+import Database.Selda.SqlType (SqlType, Lit (LNull), SqlTypeRep (..))+import Database.Selda.SqlRow (SqlRow (nestedCols)) import Database.Selda.Table import Database.Selda.Transform import Control.Monad.State.Strict+import Data.Proxy+import GHC.Generics (Rep) import Unsafe.Coerce -- | Query the given table.@@ -27,11 +30,11 @@ -- | Query an ad hoc table of type @a@. Each element in the given list represents -- one row in the ad hoc table.-selectValues :: Relational a => [a] -> Query s (Row s a)+selectValues :: forall s a. Relational a => [a] -> Query s (Row s a) selectValues [] = Query $ do st <- get put $ st {sources = sqlFrom [] EmptyTable : sources st}- return $ Many (repeat (Untyped $ Col "NULL"))+ return $ Many (gNew (Proxy :: Proxy (Rep a))) selectValues (row:rows) = Query $ do names <- mapM (const freshName) firstrow let rns = [Named n (Col n) | n <- names]
src/Database/Selda/Table.hs view
@@ -39,7 +39,7 @@ -- For example: -- -- > data Person = Person--- > { id :: Int+-- > { id :: ID Person -- > , name :: Text -- > , age :: Int -- > , pet :: Maybe Text@@ -47,13 +47,13 @@ -- > deriving Generic -- > -- > people :: Table Person--- > people = table "people" [name :- autoPrimary]+-- > people = table "people" [pId :- autoPrimary]+-- > pId :*: pName :*: pAge :*: pPet = selectors people ----- This example will create a table with the column types--- @Int :*: Text :*: Int :*: Maybe Text@, where the first field is--- an auto-incrementing primary key.+-- This will result in a table of @Person@s, with an auto-incrementing primary+-- key. ----- If the given type is not a record type, the column names will be+-- If the given type does not have record selectors, the column names will be -- @col_1@, @col_2@, etc. table :: forall a. Relational a => TableName@@ -112,14 +112,12 @@ -- | A pair of the table with the given name and columns, and all its selectors. -- For example: ----- > tbl :: Table (Int :*: Text)+-- > tbl :: Table (Int, Text) -- > (tbl, tblBar :*: tblBaz)--- > = tableWithSelectors "foo"--- > $ required "bar"--- > :*: required "baz"+-- > = tableWithSelectors "foo" [] -- > -- > q :: Query s Text--- > q = tblBaz <$> select tbl+-- > q = tblBaz `from` select tbl tableWithSelectors :: forall a. Relational a => TableName -> [Attr a]@@ -133,12 +131,14 @@ -- 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" $ required "bar" :*: required "baz"+-- > tbl :: Table (Int, Text)+-- > tbl = table "foo" [] -- > (tblBar :*: tblBaz) = selectors tbl -- > -- > q :: Query s Text--- > q = tblBaz <$> select tbl+-- > q = do+-- > row <- select tbl+-- > return (row ! tblBaz) selectors :: forall a. Relational a => Table a -> Selectors a selectors _ = selectorsFor (Proxy :: Proxy a)
src/Database/Selda/Unsafe.hs view
@@ -23,7 +23,7 @@ -- | Cast an aggregate to another type, using whichever coercion semantics -- are used by the underlying SQL implementation. castAggr :: forall s a b. SqlType b => Aggr s a -> Aggr s b-castAggr = liftAggr $ Cast (sqlType (Proxy :: Proxy b))+castAggr = liftAggr cast -- | A unary operation. Note that the provided function name is spliced -- directly into the resulting SQL query. Thus, this function should ONLY