diff --git a/src/Database/Esqueleto/Experimental/From.hs b/src/Database/Esqueleto/Experimental/From.hs index 085ff9d..2c0cef8 100644 --- a/src/Database/Esqueleto/Experimental/From.hs +++ b/src/Database/Esqueleto/Experimental/From.hs @@ -52,7 +52,7 @@ instance PersistEntity a => From (Table a) where runFrom e@Table = do let ed = entityDef $ getVal e ident <- newIdentFor (entityDB ed) - let entity = EEntity ident + let entity = unsafeSqlEntity ident pure $ (entity, FromStart ident ed) where getVal :: Table ent -> Proxy ent diff --git a/src/Database/Esqueleto/Experimental/ToAlias.hs b/src/Database/Esqueleto/Experimental/ToAlias.hs index 9621596..4a85143 100644 --- a/src/Database/Esqueleto/Experimental/ToAlias.hs +++ b/src/Database/Esqueleto/Experimental/ToAlias.hs @@ -16,20 +16,22 @@ class ToAlias a where toAlias :: a -> SqlQuery a instance ToAlias (SqlExpr (Value a)) where - toAlias v@(EAliasedValue _ _) = pure v - toAlias v = do - ident <- newIdentFor (DBName "v") - pure $ EAliasedValue ident v + toAlias e@(ERaw m f) + | Just _ <- sqlExprMetaAlias m, not (sqlExprMetaIsReference m) = pure e + | otherwise = do + ident <- newIdentFor (DBName "v") + pure $ ERaw noMeta{sqlExprMetaAlias = Just ident} f instance ToAlias (SqlExpr (Entity a)) where - toAlias v@(EAliasedEntityReference _ _) = pure v - toAlias v@(EAliasedEntity _ _) = pure v - toAlias (EEntity tableIdent) = do + toAlias (ERaw m f) = do ident <- newIdentFor (DBName "v") - pure $ EAliasedEntity ident tableIdent + pure $ ERaw m{sqlExprMetaIsReference = False, sqlExprMetaAlias = Just ident} f instance ToAlias (SqlExpr (Maybe (Entity a))) where - toAlias (EMaybe e) = EMaybe <$> toAlias e + -- FIXME: Code duplication because the compiler doesnt like half final encoding + toAlias (ERaw m f) = do + ident <- newIdentFor (DBName "v") + pure $ ERaw m{sqlExprMetaIsReference = False, sqlExprMetaAlias = Just ident} f instance (ToAlias a, ToAlias b) => ToAlias (a,b) where toAlias (a,b) = (,) <$> toAlias a <*> toAlias b diff --git a/src/Database/Esqueleto/Experimental/ToAliasReference.hs b/src/Database/Esqueleto/Experimental/ToAliasReference.hs index b01bbe6..72ac475 100644 --- a/src/Database/Esqueleto/Experimental/ToAliasReference.hs +++ b/src/Database/Esqueleto/Experimental/ToAliasReference.hs @@ -1,10 +1,11 @@ {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} module Database.Esqueleto.Experimental.ToAliasReference where -import Database.Esqueleto.Experimental.ToAlias +import Data.Coerce import Database.Esqueleto.Internal.Internal hiding (From, from, on) import Database.Esqueleto.Internal.PersistentImport @@ -16,18 +17,21 @@ class ToAliasReference a where toAliasReference :: Ident -> a -> SqlQuery a instance ToAliasReference (SqlExpr (Value a)) where - toAliasReference aliasSource (EAliasedValue aliasIdent _) = pure $ EValueReference aliasSource (\_ -> aliasIdent) - toAliasReference _ v@(ERaw _ _) = toAlias v - toAliasReference _ v@(ECompositeKey _) = toAlias v - toAliasReference s (EValueReference _ b) = pure $ EValueReference s b + toAliasReference aliasSource (ERaw m _) + | Just alias <- sqlExprMetaAlias m = pure $ ERaw m{sqlExprMetaIsReference = True} $ \_ info -> + (useIdent info aliasSource <> "." <> useIdent info alias, []) + toAliasReference _ e = pure e instance ToAliasReference (SqlExpr (Entity a)) where - toAliasReference aliasSource (EAliasedEntity ident _) = pure $ EAliasedEntityReference aliasSource ident - toAliasReference _ e@(EEntity _) = toAlias e - toAliasReference s (EAliasedEntityReference _ b) = pure $ EAliasedEntityReference s b + toAliasReference aliasSource (ERaw m _) + | Just _ <- sqlExprMetaAlias m, False <- sqlExprMetaIsReference m = + pure $ ERaw m{sqlExprMetaIsReference = True} $ \_ info -> + (useIdent info aliasSource, []) + toAliasReference _ e = pure e instance ToAliasReference (SqlExpr (Maybe (Entity a))) where - toAliasReference s (EMaybe e) = EMaybe <$> toAliasReference s e + toAliasReference aliasSource e = + coerce <$> toAliasReference aliasSource (coerce e :: SqlExpr (Entity a)) instance (ToAliasReference a, ToAliasReference b) => ToAliasReference (a, b) where diff --git a/src/Database/Esqueleto/Experimental/ToMaybe.hs b/src/Database/Esqueleto/Experimental/ToMaybe.hs index cc1a0f8..0677bfb 100644 --- a/src/Database/Esqueleto/Experimental/ToMaybe.hs +++ b/src/Database/Esqueleto/Experimental/ToMaybe.hs @@ -21,7 +21,7 @@ instance ToMaybe (SqlExpr (Maybe a)) where instance ToMaybe (SqlExpr (Entity a)) where type ToMaybeT (SqlExpr (Entity a)) = SqlExpr (Maybe (Entity a)) - toMaybe = EMaybe + toMaybe (ERaw f m) = (ERaw f m) instance ToMaybe (SqlExpr (Value a)) where type ToMaybeT (SqlExpr (Value a)) = SqlExpr (Value (Maybe (Nullable a))) diff --git a/src/Database/Esqueleto/Internal/Internal.hs b/src/Database/Esqueleto/Internal/Internal.hs index c5e0937..96c6bce 100644 --- a/src/Database/Esqueleto/Internal/Internal.hs +++ b/src/Database/Esqueleto/Internal/Internal.hs @@ -24,6 +24,7 @@ module Database.Esqueleto.Internal.Internal where import Control.Applicative ((<|>)) +import Data.Coerce (coerce) import Control.Arrow (first, (***)) import Control.Exception (Exception, throw, throwIO) import Control.Monad (MonadPlus(..), guard, void) @@ -83,35 +84,35 @@ fromStart ( PersistEntity a , BackendCompatible SqlBackend (PersistEntityBackend a) ) - => SqlQuery (SqlExpr (PreprocessedFrom (SqlExpr (Entity a)))) + => SqlQuery (PreprocessedFrom (SqlExpr (Entity a))) fromStart = do let ed = entityDef (Proxy :: Proxy a) ident <- newIdentFor (entityDB ed) - let ret = EEntity ident + let ret = unsafeSqlEntity ident f' = FromStart ident ed - return (EPreprocessedFrom ret f') + return (PreprocessedFrom ret f') -- | (Internal) Same as 'fromStart', but entity may be missing. fromStartMaybe :: ( PersistEntity a , BackendCompatible SqlBackend (PersistEntityBackend a) ) - => SqlQuery (SqlExpr (PreprocessedFrom (SqlExpr (Maybe (Entity a))))) + => SqlQuery (PreprocessedFrom (SqlExpr (Maybe (Entity a)))) fromStartMaybe = maybelize <$> fromStart where maybelize - :: SqlExpr (PreprocessedFrom (SqlExpr (Entity a))) - -> SqlExpr (PreprocessedFrom (SqlExpr (Maybe (Entity a)))) - maybelize (EPreprocessedFrom ret f') = EPreprocessedFrom (EMaybe ret) f' + :: PreprocessedFrom (SqlExpr (Entity a)) + -> PreprocessedFrom (SqlExpr (Maybe (Entity a))) + maybelize (PreprocessedFrom e f') = PreprocessedFrom (coerce e) f' -- | (Internal) Do a @JOIN@. fromJoin :: IsJoinKind join - => SqlExpr (PreprocessedFrom a) - -> SqlExpr (PreprocessedFrom b) - -> SqlQuery (SqlExpr (PreprocessedFrom (join a b))) -fromJoin (EPreprocessedFrom lhsRet lhsFrom) - (EPreprocessedFrom rhsRet rhsFrom) = Q $ do + => PreprocessedFrom a + -> PreprocessedFrom b + -> SqlQuery (PreprocessedFrom (join a b)) +fromJoin (PreprocessedFrom lhsRet lhsFrom) + (PreprocessedFrom rhsRet rhsFrom) = Q $ do let ret = smartJoin lhsRet rhsRet from' = FromJoin @@ -119,13 +120,13 @@ fromJoin (EPreprocessedFrom lhsRet lhsFrom) (reifyJoinKind ret) -- JOIN rhsFrom -- RHS Nothing -- ON - return (EPreprocessedFrom ret from') + return (PreprocessedFrom ret from') -- | (Internal) Finish a @JOIN@. fromFinish - :: SqlExpr (PreprocessedFrom a) + :: PreprocessedFrom a -> SqlQuery a -fromFinish (EPreprocessedFrom ret f') = Q $ do +fromFinish (PreprocessedFrom ret f') = Q $ do W.tell mempty { sdFromClause = [f'] } return ret @@ -249,11 +250,22 @@ orderBy exprs = Q $ W.tell mempty { sdOrderByClause = exprs } -- | Ascending order of this field or SqlExpression. asc :: PersistField a => SqlExpr (Value a) -> SqlExpr OrderBy -asc = EOrderBy ASC +asc = orderByExpr " ASC" -- | Descending order of this field or SqlExpression. desc :: PersistField a => SqlExpr (Value a) -> SqlExpr OrderBy -desc = EOrderBy DESC +desc = orderByExpr " DESC" + +orderByExpr :: TLB.Builder -> SqlExpr (Value a) -> SqlExpr OrderBy +orderByExpr orderByType (ERaw m f) + | Just fields <- sqlExprMetaCompositeFields m = + ERaw noMeta $ \_ info -> + let fs = fields info + vals = repeat [] + in uncommas' $ zip (map (<> orderByType) fs) vals + | otherwise = + ERaw noMeta $ \_ info -> + first (<> orderByType) $ f Never info -- | @LIMIT@. Limit the number of returned rows. limit :: Int64 -> SqlQuery () @@ -325,7 +337,7 @@ distinctOn exprs act = Q (W.tell mempty { sdDistinctClause = DistinctOn exprs }) -- -- @since 2.2.4 don :: SqlExpr (Value a) -> SqlExpr DistinctOn -don = EDistinctOn +don = coerce -- | A convenience function that calls both 'distinctOn' and -- 'orderBy'. In other words, @@ -351,15 +363,15 @@ distinctOnOrderBy exprs act = act where toDistinctOn :: SqlExpr OrderBy -> SqlExpr DistinctOn - toDistinctOn (EOrderBy _ f) = EDistinctOn f - toDistinctOn EOrderRandom = - error "We can't select distinct by a random order!" + toDistinctOn (ERaw m f) = ERaw m $ \p info -> + let (b, vals) = f p info + in (TLB.fromLazyText $ head $ TL.splitOn " " $ TLB.toLazyText b, vals) -- | @ORDER BY random()@ clause. -- -- @since 1.3.10 rand :: SqlExpr OrderBy -rand = EOrderRandom +rand = ERaw noMeta $ \_ _ -> ("RANDOM()", []) -- | @HAVING@. -- @@ -526,8 +538,12 @@ subSelectUnsafe = sub SELECT => SqlExpr (Entity val) -> EntityField val typ -> SqlExpr (Value typ) -(EAliasedEntityReference source base) ^. field = - EValueReference source (\_ -> aliasedEntityColumnIdent base fieldDef) +ERaw m f ^. field + | isIdField field = idFieldValue + | Just alias <- sqlExprMetaAlias m = + ERaw noMeta $ \_ info -> + f Never info <> ("." <> useIdent info (aliasedEntityColumnIdent alias fieldDef), []) + | otherwise = ERaw noMeta $ \_ info -> (dot info $ persistFieldDef field, []) where fieldDef = if isIdField field then @@ -535,48 +551,28 @@ subSelectUnsafe = sub SELECT head $ entityKeyFields ed else persistFieldDef field - - ed = entityDef $ getEntityVal (Proxy :: Proxy (SqlExpr (Entity val))) - -e ^. field - | isIdField field = idFieldValue - | otherwise = ERaw Never $ \info -> (dot info $ persistFieldDef field, []) - where idFieldValue = case entityKeyFields ed of idField:[] -> - ERaw Never $ \info -> (dot info idField, []) + ERaw noMeta $ \_ info -> (dot info idField, []) idFields -> - ECompositeKey $ \info -> dot info <$> idFields + let renderedFields info = dot info <$> idFields + in ERaw noMeta{ sqlExprMetaCompositeFields = Just renderedFields} $ + \p info -> (parensM p $ uncommas $ dot info <$> idFields, []) ed = entityDef $ getEntityVal (Proxy :: Proxy (SqlExpr (Entity val))) dot info fieldDef = - useIdent info sourceIdent <> "." <> fieldIdent + sourceIdent info <> "." <> fieldIdent where - sourceIdent = - case e of - EEntity ident -> ident - EAliasedEntity baseI _ -> baseI - EAliasedEntityReference a b -> - error $ unwords - [ "Used (^.) with an EAliasedEntityReference." - , "Please file this as an Esqueleto bug." - , "EAliasedEntityReference", show a, show b - ] - fieldIdent = - case e of - EEntity _ -> fromDBName info (fieldDB fieldDef) - EAliasedEntity baseI _ -> useIdent info $ aliasedEntityColumnIdent baseI fieldDef - EAliasedEntityReference a b -> - error $ unwords - [ "Used (^.) with an EAliasedEntityReference." - , "Please file this as an Esqueleto bug." - , "EAliasedEntityReference", show a, show b - ] - + sourceIdent = fmap fst $ f Never + fieldIdent + | Just baseI <- sqlExprMetaAlias m = + useIdent info $ aliasedEntityColumnIdent baseI fieldDef + | otherwise = + fromDBName info (fieldDB fieldDef) -- | Project an SqlExpression that may be null, guarding against null cases. withNonNull @@ -594,11 +590,11 @@ withNonNull field f = do => SqlExpr (Maybe (Entity val)) -> EntityField val typ -> SqlExpr (Value (Maybe typ)) -EMaybe r ?. field = just (r ^. field) +ERaw m f ?. field = just (ERaw m f ^. field) -- | Lift a constant value from Haskell-land to the query. val :: PersistField typ => typ -> SqlExpr (Value typ) -val v = ERaw Never $ const ("?", [toPersistValue v]) +val v = ERaw noMeta $ \_ _ -> ("?", [toPersistValue v]) -- | @IS NULL@ comparison. -- @@ -624,27 +620,22 @@ val v = ERaw Never $ const ("?", [toPersistValue v]) isNothing :: PersistField typ => SqlExpr (Value (Maybe typ)) -> SqlExpr (Value Bool) isNothing v = case v of - ERaw p f -> - isNullExpr $ first (parensM p) . f - EAliasedValue i _ -> - isNullExpr $ aliasedValueIdentToRawSql i - EValueReference i i' -> - isNullExpr $ valueReferenceToRawSql i i' - ECompositeKey f -> - ERaw Parens $ flip (,) [] . (intersperseB " AND " . map (<> " IS NULL")) . f + ERaw m f -> + case sqlExprMetaCompositeFields m of + Just fields -> + ERaw noMeta $ \p info -> + first (parensM p) . flip (,) [] . (intersperseB " AND " . map (<> " IS NULL")) $ fields info + Nothing -> + ERaw noMeta $ \p info -> + first (parensM p) . isNullExpr $ f Never info where - isNullExpr :: (IdentInfo -> (TLB.Builder, [PersistValue])) -> SqlExpr (Value Bool) - isNullExpr g = ERaw Parens $ first ((<> " IS NULL")) . g + isNullExpr = first ((<> " IS NULL")) -- | Analogous to 'Just', promotes a value of type @typ@ into -- one of type @Maybe typ@. It should hold that @'val' . Just -- === just . 'val'@. just :: SqlExpr (Value typ) -> SqlExpr (Value (Maybe typ)) -just exprVal = case exprVal of - ERaw p f -> ERaw p f - ECompositeKey f -> ECompositeKey f - EAliasedValue i v -> EAliasedValue i (just v) - EValueReference i i' -> EValueReference i i' +just = veryUnsafeCoerceSqlExprValue -- | @NULL@ value. nothing :: SqlExpr (Value (Maybe typ)) @@ -653,23 +644,20 @@ nothing = unsafeSqlValue "NULL" -- | Join nested 'Maybe's in a 'Value' into one. This is useful when -- calling aggregate functions on nullable fields. joinV :: SqlExpr (Value (Maybe (Maybe typ))) -> SqlExpr (Value (Maybe typ)) -joinV exprMM = case exprMM of - ERaw p f -> ERaw p f - ECompositeKey f -> ECompositeKey f - EAliasedValue i v -> EAliasedValue i (joinV v) - EValueReference i i' -> EValueReference i i' +joinV = veryUnsafeCoerceSqlExprValue countHelper :: Num a => TLB.Builder -> TLB.Builder -> SqlExpr (Value typ) -> SqlExpr (Value a) countHelper open close v = case v of - ERaw _ f -> countRawSql f - EAliasedValue i _ -> countRawSql $ aliasedValueIdentToRawSql i - EValueReference i i' -> countRawSql $ valueReferenceToRawSql i i' - ECompositeKey _ -> countRows + ERaw meta f -> + if hasCompositeKeyMeta meta then + countRows + else + countRawSql (f Never) where countRawSql :: (IdentInfo -> (TLB.Builder, [PersistValue])) -> SqlExpr (Value a) - countRawSql x = ERaw Never $ first (\b -> "COUNT" <> open <> parens b <> close) . x + countRawSql x = ERaw noMeta $ \_ -> first (\b -> "COUNT" <> open <> parens b <> close) . x -- | @COUNT(*)@ value. countRows :: Num a => SqlExpr (Value a) @@ -686,19 +674,16 @@ countDistinct :: Num a => SqlExpr (Value typ) -> SqlExpr (Value a) countDistinct = countHelper "(DISTINCT " ")" not_ :: SqlExpr (Value Bool) -> SqlExpr (Value Bool) -not_ v = ERaw Never (\info -> first ("NOT " <>) $ x info) +not_ v = ERaw noMeta $ \p info -> first ("NOT " <>) $ x p info where - x info = + x p info = case v of - ERaw p f -> - let (b, vals) = f info - in (parensM p b, vals) - ECompositeKey _ -> - throw (CompositeKeyErr NotError) - EAliasedValue i _ -> - aliasedValueIdentToRawSql i info - EValueReference i i' -> - valueReferenceToRawSql i i' info + ERaw m f -> + if hasCompositeKeyMeta m then + throw (CompositeKeyErr NotError) + else + let (b, vals) = f Never info + in (parensM p b, vals) (==.) :: PersistField typ => SqlExpr (Value typ) -> SqlExpr (Value typ) -> SqlExpr (Value Bool) (==.) = unsafeSqlBinOpComposite " = " " AND " @@ -895,13 +880,13 @@ castString = veryUnsafeCoerceSqlExprValue -- | Execute a subquery @SELECT@ in an SqlExpression. Returns a -- list of values. subList_select :: PersistField a => SqlQuery (SqlExpr (Value a)) -> SqlExpr (ValueList a) -subList_select = EList . sub_select +subList_select query = ERaw noMeta $ \_ info -> first parens $ toRawSql SELECT info query + -- | Lift a list of constant value from Haskell-land to the query. valList :: PersistField typ => [typ] -> SqlExpr (ValueList typ) -valList [] = EEmptyList -valList vals = EList $ ERaw Parens $ const ( uncommas ("?" <$ vals) - , map toPersistValue vals ) +valList [] = ERaw noMeta $ \_ _ -> ("()", []) +valList vals = ERaw noMeta $ \p -> const (parensM p (uncommas ("?" <$ vals)), map toPersistValue vals ) -- | Same as 'just' but for 'ValueList'. Most of the time you -- won't need it, though, because you can use 'just' from @@ -909,8 +894,7 @@ valList vals = EList $ ERaw Parens $ const ( uncommas ("?" <$ vals) -- -- @since 2.2.12 justList :: SqlExpr (ValueList typ) -> SqlExpr (ValueList (Maybe typ)) -justList EEmptyList = EEmptyList -justList (EList v) = EList (just v) +justList (ERaw m f) = ERaw m f -- | @IN@ operator. For example if you want to select all @Person@s by a list -- of IDs: @@ -932,11 +916,23 @@ justList (EList v) = EList (just v) -- -- Where @personIds@ is of type @[Key Person]@. in_ :: PersistField typ => SqlExpr (Value typ) -> SqlExpr (ValueList typ) -> SqlExpr (Value Bool) -v `in_` e = ifNotEmptyList e False $ unsafeSqlBinOp " IN " v (veryUnsafeCoerceSqlExprValueList e) +(ERaw _ v) `in_` (ERaw _ list) = + ERaw noMeta $ \p info -> + let (b1, vals1) = v Parens info + (b2, vals2) = list Parens info + in + if b2 == "()" then + ("FALSE", []) + else + (b1 <> " IN " <> b2, vals1 <> vals2) -- | @NOT IN@ operator. notIn :: PersistField typ => SqlExpr (Value typ) -> SqlExpr (ValueList typ) -> SqlExpr (Value Bool) -v `notIn` e = ifNotEmptyList e True $ unsafeSqlBinOp " NOT IN " v (veryUnsafeCoerceSqlExprValueList e) +(ERaw _ v) `notIn` (ERaw _ list) = + ERaw noMeta $ \p info -> + let (b1, vals1) = v Parens info + (b2, vals2) = list Parens info + in (b1 <> " NOT IN " <> b2, vals1 <> vals2) -- | @EXISTS@ operator. For example: -- @@ -949,58 +945,52 @@ v `notIn` e = ifNotEmptyList e True $ unsafeSqlBinOp " NOT IN " v (veryUnsafeCo -- return person -- @ exists :: SqlQuery () -> SqlExpr (Value Bool) -exists = unsafeSqlFunction "EXISTS " . existsHelper +exists q = ERaw noMeta $ \p info -> + let ERaw _ f = existsHelper q + (b, vals) = f Never info + in ( parensM p $ "EXISTS " <> b, vals) -- | @NOT EXISTS@ operator. notExists :: SqlQuery () -> SqlExpr (Value Bool) -notExists = unsafeSqlFunction "NOT EXISTS " . existsHelper +notExists q = ERaw noMeta $ \p info -> + let ERaw _ f = existsHelper q + (b, vals) = f Never info + in ( parensM p $ "NOT EXISTS " <> b, vals) -- | @SET@ clause used on @UPDATE@s. Note that while it's not -- a type error to use this function on a @SELECT@, it will -- most certainly result in a runtime error. -set :: PersistEntity val => SqlExpr (Entity val) -> [SqlExpr (Update val)] -> SqlQuery () +set :: PersistEntity val => SqlExpr (Entity val) -> [SqlExpr (Entity val) -> SqlExpr Update] -> SqlQuery () set ent upds = Q $ W.tell mempty { sdSetClause = map apply upds } where - apply (ESet f) = SetClause (f ent) + apply f = SetClause (f ent) -(=.) :: (PersistEntity val, PersistField typ) => EntityField val typ -> SqlExpr (Value typ) -> SqlExpr (Update val) +(=.) :: (PersistEntity val, PersistField typ) => EntityField val typ -> SqlExpr (Value typ) -> (SqlExpr (Entity val) -> SqlExpr Update ) field =. expr = setAux field (const expr) -(+=.) :: (PersistEntity val, PersistField a) => EntityField val a -> SqlExpr (Value a) -> SqlExpr (Update val) +(+=.) :: (PersistEntity val, PersistField a) => EntityField val a -> SqlExpr (Value a) -> (SqlExpr (Entity val) -> SqlExpr Update) field +=. expr = setAux field (\ent -> ent ^. field +. expr) -(-=.) :: (PersistEntity val, PersistField a) => EntityField val a -> SqlExpr (Value a) -> SqlExpr (Update val) +(-=.) :: (PersistEntity val, PersistField a) => EntityField val a -> SqlExpr (Value a) -> (SqlExpr (Entity val) -> SqlExpr Update) field -=. expr = setAux field (\ent -> ent ^. field -. expr) -(*=.) :: (PersistEntity val, PersistField a) => EntityField val a -> SqlExpr (Value a) -> SqlExpr (Update val) +(*=.) :: (PersistEntity val, PersistField a) => EntityField val a -> SqlExpr (Value a) -> (SqlExpr (Entity val) -> SqlExpr Update) field *=. expr = setAux field (\ent -> ent ^. field *. expr) -(/=.) :: (PersistEntity val, PersistField a) => EntityField val a -> SqlExpr (Value a) -> SqlExpr (Update val) +(/=.) :: (PersistEntity val, PersistField a) => EntityField val a -> SqlExpr (Value a) -> (SqlExpr (Entity val) -> SqlExpr Update) field /=. expr = setAux field (\ent -> ent ^. field /. expr) -- | Apply a 'PersistField' constructor to @SqlExpr Value@ arguments. (<#) :: (a -> b) -> SqlExpr (Value a) -> SqlExpr (Insertion b) -(<#) _ (ERaw _ f) = EInsert Proxy f -(<#) _ (ECompositeKey _) = throw (CompositeKeyErr ToInsertionError) -(<#) _ (EAliasedValue i _) = EInsert Proxy $ aliasedValueIdentToRawSql i -(<#) _ (EValueReference i i') = EInsert Proxy $ valueReferenceToRawSql i i' - +(<#) _ (ERaw _ f) = ERaw noMeta f -- | Apply extra @SqlExpr Value@ arguments to a 'PersistField' constructor (<&>) :: SqlExpr (Insertion (a -> b)) -> SqlExpr (Value a) -> SqlExpr (Insertion b) -(EInsert _ f) <&> v = - EInsert Proxy $ \x -> - let (fb, fv) = f x - (gb, gv) = g x - in - (fb <> ", " <> gb, fv ++ gv) - where - g = - case v of - ERaw _ f' -> f' - EAliasedValue i _ -> aliasedValueIdentToRawSql i - EValueReference i i' -> valueReferenceToRawSql i i' - ECompositeKey _ -> throw (CompositeKeyErr CombineInsertionError) +(ERaw _ f) <&> (ERaw _ g) = + ERaw noMeta $ \_ info -> + let (fb, fv) = f Never info + (gb, gv) = g Never info + in (fb <> ", " <> gb, fv ++ gv) -- | @CASE@ statement. For example: -- @@ -1290,18 +1280,15 @@ toUniqueDef uniqueConstructor = uniqueDef renderUpdates :: (BackendCompatible SqlBackend backend) => backend - -> [SqlExpr (Update val)] + -> [SqlExpr (Entity val) -> SqlExpr Update] -> (TLB.Builder, [PersistValue]) renderUpdates conn = uncommas' . concatMap renderUpdate where - mk :: SqlExpr (Value ()) -> [(TLB.Builder, [PersistValue])] - mk (ERaw _ f) = [f info] - mk (ECompositeKey _) = throw (CompositeKeyErr MakeSetError) -- FIXME - mk (EAliasedValue i _) = [aliasedValueIdentToRawSql i info] - mk (EValueReference i i') = [valueReferenceToRawSql i i' info] + mk :: SqlExpr Update -> [(TLB.Builder, [PersistValue])] + mk (ERaw _ f) = [f Never info] - renderUpdate :: SqlExpr (Update val) -> [(TLB.Builder, [PersistValue])] - renderUpdate (ESet f) = mk (f undefined) -- second parameter of f is always unused + renderUpdate :: (SqlExpr (Entity val) -> SqlExpr Update) -> [(TLB.Builder, [PersistValue])] + renderUpdate f = mk (f undefined) -- second parameter of f is always unused info = (projectBackend conn, initialIdentState) -- | Data type that represents an @INNER JOIN@ (see 'LeftOuterJoin' for an example). @@ -1380,9 +1367,6 @@ data OnClauseWithoutMatchingJoinException = instance Exception OnClauseWithoutMatchingJoinException --- | (Internal) Phantom type used to process 'from' (see 'fromStart'). -data PreprocessedFrom a - -- | Phantom type used by 'orderBy', 'asc' and 'desc'. data OrderBy @@ -1391,7 +1375,7 @@ data DistinctOn -- | Phantom type for a @SET@ operation on an entity of the given -- type (see 'set' and '(=.)'). -data Update typ +data Update -- | Phantom type used by 'insertSelect'. data Insertion a @@ -1624,7 +1608,7 @@ instance -- | (Internal) Class that implements the @JOIN@ 'from' magic -- (see 'fromStart'). class FromPreprocess a where - fromPreprocess :: SqlQuery (SqlExpr (PreprocessedFrom a)) + fromPreprocess :: SqlQuery (PreprocessedFrom a) instance (PersistEntity val, BackendCompatible SqlBackend (PersistEntityBackend val)) @@ -1810,7 +1794,7 @@ instance Show FromClause where render' = T.unpack . renderExpr dummy -- | A part of a @SET@ clause. -newtype SetClause = SetClause (SqlExpr (Value ())) +newtype SetClause = SetClause (SqlExpr Update) -- | Collect 'OnClause's on 'FromJoin's. Returns the first -- unmatched 'OnClause's data on error. Returns a list without @@ -2013,98 +1997,75 @@ type IdentInfo = (SqlBackend, IdentState) useIdent :: IdentInfo -> Ident -> TLB.Builder useIdent info (I ident) = fromDBName info $ DBName ident +data SqlExprMeta = SqlExprMeta + { -- A composite key. + -- + -- Persistent uses the same 'PersistList' constructor for both + -- fields which are (homogeneous) lists of values and the + -- (probably heterogeneous) values of a composite primary key. + -- + -- We need to treat composite keys as fields. For example, we + -- have to support using ==., otherwise you wouldn't be able to + -- join. OTOH, lists of values should be treated exactly the + -- same as any other scalar value. + -- + -- In particular, this is valid for persistent via rawSql for + -- an F field that is a list: + -- + -- A.F in ? -- [PersistList [foo, bar]] + -- + -- However, this is not for a composite key entity: + -- + -- A.ID = ? -- [PersistList [foo, bar]] + -- + -- The ID field doesn't exist on the DB for a composite key + -- table, it exists only on the Haskell side. Those variations + -- also don't work: + -- + -- (A.KeyA, A.KeyB) = ? -- [PersistList [foo, bar]] + -- [A.KeyA, A.KeyB] = ? -- [PersistList [foo, bar]] + -- + -- We have to generate: + -- + -- A.KeyA = ? AND A.KeyB = ? -- [foo, bar] + -- + -- Note that the PersistList had to be deconstructed into its + -- components. + -- + -- In order to disambiguate behaviors, this constructor is used + -- /only/ to represent a composite field access. It does not + -- represent a 'PersistList', not even if the 'PersistList' is + -- used in the context of a composite key. That's because it's + -- impossible, e.g., for 'val' to disambiguate between these + -- uses. + sqlExprMetaCompositeFields :: Maybe (IdentInfo -> [TLB.Builder]) + , sqlExprMetaAlias :: Maybe Ident -- Alias ident if this is an aliased value/entity + , sqlExprMetaIsReference :: Bool -- Is this SqlExpr a reference to the selected value/entity (supports subqueries) + } + +noMeta :: SqlExprMeta +noMeta = SqlExprMeta + { sqlExprMetaCompositeFields = Nothing + , sqlExprMetaAlias = Nothing + , sqlExprMetaIsReference = False + } + +hasCompositeKeyMeta :: SqlExprMeta -> Bool +hasCompositeKeyMeta = Maybe.isJust . sqlExprMetaCompositeFields + -- | An expression on the SQL backend. -- -- There are many comments describing the constructors of this -- data type. However, Haddock doesn't like GADTs, so you'll have to read them by hitting \"Source\". -data SqlExpr a where - -- An entity, created by 'from' (cf. 'fromStart'). - EEntity :: Ident -> SqlExpr (Entity val) - -- Base Table - EAliasedEntity :: Ident -> Ident -> SqlExpr (Entity val) - -- Source Base - EAliasedEntityReference :: Ident -> Ident -> SqlExpr (Entity val) - - -- Just a tag stating that something is nullable. - EMaybe :: SqlExpr a -> SqlExpr (Maybe a) - -- Raw expression: states whether parenthesis are needed -- around this expression, and takes information about the SQL -- connection (mainly for escaping names) and returns both an -- string ('TLB.Builder') and a list of values to be -- interpolated by the SQL backend. - ERaw :: NeedParens -> (IdentInfo -> (TLB.Builder, [PersistValue])) -> SqlExpr (Value a) +data SqlExpr a = ERaw SqlExprMeta (NeedParens -> IdentInfo -> (TLB.Builder, [PersistValue])) - - -- A raw expression with an alias - EAliasedValue :: Ident -> SqlExpr (Value a) -> SqlExpr (Value a) - - -- A reference to an aliased field in a table or subquery - EValueReference :: Ident -> (IdentInfo -> Ident) -> SqlExpr (Value a) - - -- A composite key. - -- - -- Persistent uses the same 'PersistList' constructor for both - -- fields which are (homogeneous) lists of values and the - -- (probably heterogeneous) values of a composite primary key. - -- - -- We need to treat composite keys as fields. For example, we - -- have to support using ==., otherwise you wouldn't be able to - -- join. OTOH, lists of values should be treated exactly the - -- same as any other scalar value. - -- - -- In particular, this is valid for persistent via rawSql for - -- an F field that is a list: - -- - -- A.F in ? -- [PersistList [foo, bar]] - -- - -- However, this is not for a composite key entity: - -- - -- A.ID = ? -- [PersistList [foo, bar]] - -- - -- The ID field doesn't exist on the DB for a composite key - -- table, it exists only on the Haskell side. Those variations - -- also don't work: - -- - -- (A.KeyA, A.KeyB) = ? -- [PersistList [foo, bar]] - -- [A.KeyA, A.KeyB] = ? -- [PersistList [foo, bar]] - -- - -- We have to generate: - -- - -- A.KeyA = ? AND A.KeyB = ? -- [foo, bar] - -- - -- Note that the PersistList had to be deconstructed into its - -- components. - -- - -- In order to disambiguate behaviors, this constructor is used - -- /only/ to represent a composite field access. It does not - -- represent a 'PersistList', not even if the 'PersistList' is - -- used in the context of a composite key. That's because it's - -- impossible, e.g., for 'val' to disambiguate between these - -- uses. - ECompositeKey :: (IdentInfo -> [TLB.Builder]) -> SqlExpr (Value a) - - -- 'EList' and 'EEmptyList' are used by list operators. - EList :: SqlExpr (Value a) -> SqlExpr (ValueList a) - EEmptyList :: SqlExpr (ValueList a) - - -- A 'SqlExpr' accepted only by 'orderBy'. - EOrderBy :: OrderByType -> SqlExpr (Value a) -> SqlExpr OrderBy - - EOrderRandom :: SqlExpr OrderBy - - -- A 'SqlExpr' accepted only by 'distinctOn'. - EDistinctOn :: SqlExpr (Value a) -> SqlExpr DistinctOn - - -- A 'SqlExpr' accepted only by 'set'. - ESet :: (SqlExpr (Entity val) -> SqlExpr (Value ())) -> SqlExpr (Update val) - - -- An internal 'SqlExpr' used by the 'from' hack. - EPreprocessedFrom :: a -> FromClause -> SqlExpr (PreprocessedFrom a) - - -- Used by 'insertSelect'. - EInsert :: Proxy a -> (IdentInfo -> (TLB.Builder, [PersistValue])) -> SqlExpr (Insertion a) - EInsertFinal :: PersistEntity a => SqlExpr (Insertion a) -> SqlExpr InsertFinal +-- | Data type to support from hack +data PreprocessedFrom a = PreprocessedFrom a FromClause -- | Phantom type used to mark a @INSERT INTO@ query. data InsertFinal @@ -2131,13 +2092,14 @@ setAux :: (PersistEntity val, PersistField typ) => EntityField val typ -> (SqlExpr (Entity val) -> SqlExpr (Value typ)) - -> SqlExpr (Update val) -setAux field mkVal = ESet $ \ent -> unsafeSqlBinOp " = " name (mkVal ent) - where - name = ERaw Never $ \info -> (fieldName info field, mempty) + -> (SqlExpr (Entity val) -> SqlExpr Update) +setAux field value = \ent -> ERaw noMeta $ \_ info -> + let ERaw _ valueF = value ent + (valueToSet, valueVals) = valueF Parens info + in (fieldName info field <> " = " <> valueToSet, valueVals) sub :: PersistField a => Mode -> SqlQuery (SqlExpr (Value a)) -> SqlExpr (Value a) -sub mode query = ERaw Parens $ \info -> toRawSql mode info query +sub mode query = ERaw noMeta $ \_ info -> first parens $ toRawSql mode info query fromDBName :: IdentInfo -> DBName -> TLB.Builder fromDBName (conn, _) = TLB.fromText . connEscapeName conn @@ -2148,39 +2110,30 @@ existsHelper = sub SELECT . (>> return true) true :: SqlExpr (Value Bool) true = val True -ifNotEmptyList :: SqlExpr (ValueList a) -> Bool -> SqlExpr (Value Bool) -> SqlExpr (Value Bool) -ifNotEmptyList EEmptyList b _ = val b -ifNotEmptyList (EList _) _ x = x - -- | (Internal) Create a case statement. -- -- Since: 2.1.1 unsafeSqlCase :: PersistField a => [(SqlExpr (Value Bool), SqlExpr (Value a))] -> SqlExpr (Value a) -> SqlExpr (Value a) -unsafeSqlCase when v = ERaw Never buildCase +unsafeSqlCase when v = ERaw noMeta buildCase where - buildCase :: IdentInfo -> (TLB.Builder, [PersistValue]) - buildCase info = - let (elseText, elseVals) = valueToSql v info - (whenText, whenVals) = mapWhen when info + buildCase :: NeedParens -> IdentInfo -> (TLB.Builder, [PersistValue]) + buildCase p info = + let (elseText, elseVals) = valueToSql v Parens info + (whenText, whenVals) = mapWhen when Parens info in ( "CASE" <> whenText <> " ELSE " <> elseText <> " END", whenVals <> elseVals) - mapWhen :: [(SqlExpr (Value Bool), SqlExpr (Value a))] -> IdentInfo -> (TLB.Builder, [PersistValue]) - mapWhen [] _ = throw (UnexpectedCaseErr UnsafeSqlCaseError) - mapWhen when' info = foldl (foldHelp info) (mempty, mempty) when' + mapWhen :: [(SqlExpr (Value Bool), SqlExpr (Value a))] -> NeedParens -> IdentInfo -> (TLB.Builder, [PersistValue]) + mapWhen [] _ _ = throw (UnexpectedCaseErr UnsafeSqlCaseError) + mapWhen when' p info = foldl (foldHelp p info) (mempty, mempty) when' - foldHelp :: IdentInfo -> (TLB.Builder, [PersistValue]) -> (SqlExpr (Value Bool), SqlExpr (Value a)) -> (TLB.Builder, [PersistValue]) - foldHelp _ _ (ECompositeKey _, _) = throw (CompositeKeyErr FoldHelpError) - foldHelp _ _ (_, ECompositeKey _) = throw (CompositeKeyErr FoldHelpError) - foldHelp info (b0, vals0) (v1, v2) = - let (b1, vals1) = valueToSql v1 info - (b2, vals2) = valueToSql v2 info + foldHelp :: NeedParens -> IdentInfo -> (TLB.Builder, [PersistValue]) -> (SqlExpr (Value Bool), SqlExpr (Value a)) -> (TLB.Builder, [PersistValue]) + foldHelp p info (b0, vals0) (v1, v2) = + let (b1, vals1) = valueToSql v1 p info + (b2, vals2) = valueToSql v2 p info in ( b0 <> " WHEN " <> b1 <> " THEN " <> b2, vals0 <> vals1 <> vals2 ) - valueToSql :: SqlExpr (Value a) -> IdentInfo -> (TLB.Builder, [PersistValue]) - valueToSql (ERaw p f) = (first (parensM p)) . f - valueToSql (ECompositeKey _) = throw (CompositeKeyErr SqlCaseError) - valueToSql (EAliasedValue i _) = aliasedValueIdentToRawSql i - valueToSql (EValueReference i i') = valueReferenceToRawSql i i' + valueToSql :: SqlExpr (Value a) -> NeedParens -> IdentInfo -> (TLB.Builder, [PersistValue]) + valueToSql (ERaw _ f) p = f p -- | (Internal) Create a custom binary operator. You /should/ -- /not/ use this function directly since its type is very @@ -2195,32 +2148,31 @@ unsafeSqlCase when v = ERaw Never buildCase -- In the example above, we constraint the arguments to be of the -- same type and constraint the result to be a boolean value. unsafeSqlBinOp :: TLB.Builder -> SqlExpr (Value a) -> SqlExpr (Value b) -> SqlExpr (Value c) -unsafeSqlBinOp op (ERaw p1 f1) (ERaw p2 f2) = ERaw Parens f +unsafeSqlBinOp op (ERaw m1 f1) (ERaw m2 f2) + | not (hasCompositeKeyMeta m1 || hasCompositeKeyMeta m2) = ERaw noMeta f where - f info = - let (b1, vals1) = f1 info - (b2, vals2) = f2 info + f p info = + let (b1, vals1) = f1 Parens info + (b2, vals2) = f2 Parens info in - ( parensM p1 b1 <> op <> parensM p2 b2 + ( parensM p (b1 <> op <> b2) , vals1 <> vals2 ) unsafeSqlBinOp op a b = unsafeSqlBinOp op (construct a) (construct b) where construct :: SqlExpr (Value a) -> SqlExpr (Value a) - construct (ERaw p f) = - ERaw (if p == Never then Parens else Never) $ \info -> - let (b1, vals) = f info - build ("?", [PersistList vals']) = - (uncommas $ replicate (length vals') "?", vals') - build expr = expr - in - build (parensM p b1, vals) - construct (ECompositeKey f) = - ERaw Parens $ \info -> (uncommas $ f info, mempty) - construct (EAliasedValue i _) = - ERaw Never $ aliasedValueIdentToRawSql i - construct (EValueReference i i') = - ERaw Never $ valueReferenceToRawSql i i' + construct (ERaw m f) = + case sqlExprMetaCompositeFields m of + Just fields -> + ERaw noMeta $ \_ info -> (parens $ uncommas $ fields info, mempty) + Nothing -> + ERaw noMeta $ \p info -> + let (b1, vals) = f (if p == Never then Parens else Never) info + build ("?", [PersistList vals']) = + (uncommas $ replicate (length vals') "?", vals') + build expr = expr + in + first (parensM p) $ build (b1, vals) {-# INLINE unsafeSqlBinOp #-} -- | Similar to 'unsafeSqlBinOp', but may also be applied to @@ -2248,18 +2200,16 @@ unsafeSqlBinOp op a b = unsafeSqlBinOp op (construct a) (construct b) -- no placeholders and split it on the commas. unsafeSqlBinOpComposite :: TLB.Builder -> TLB.Builder -> SqlExpr (Value a) -> SqlExpr (Value b) -> SqlExpr (Value c) unsafeSqlBinOpComposite op sep a b - | isCompositeKey a || isCompositeKey b = ERaw Parens $ compose (listify a) (listify b) + | isCompositeKey a || isCompositeKey b = ERaw noMeta $ const $ compose (listify a) (listify b) | otherwise = unsafeSqlBinOp op a b where isCompositeKey :: SqlExpr (Value x) -> Bool - isCompositeKey (ECompositeKey _) = True - isCompositeKey _ = False + isCompositeKey (ERaw m _) = hasCompositeKeyMeta m listify :: SqlExpr (Value x) -> IdentInfo -> ([TLB.Builder], [PersistValue]) - listify (ECompositeKey f) = flip (,) [] . f - listify (ERaw _ f) = deconstruct . f - listify (EAliasedValue i _) = deconstruct . (aliasedValueIdentToRawSql i) - listify (EValueReference i i') = deconstruct . (valueReferenceToRawSql i i') + listify (ERaw m f) + | Just f <- sqlExprMetaCompositeFields m = flip (,) [] . f + | otherwise = deconstruct . f Parens deconstruct :: (TLB.Builder, [PersistValue]) -> ([TLB.Builder], [PersistValue]) deconstruct ("?", [PersistList vals]) = (replicate (length vals) "?", vals) @@ -2276,19 +2226,19 @@ unsafeSqlBinOpComposite op sep a b bc = intersperseB sep [x <> op <> y | (x, y) <- zip b1 b2] vc = v1 <> v2 + -- | (Internal) A raw SQL value. The same warning from -- 'unsafeSqlBinOp' applies to this function as well. unsafeSqlValue :: TLB.Builder -> SqlExpr (Value a) -unsafeSqlValue v = ERaw Never $ const (v, mempty) +unsafeSqlValue v = ERaw noMeta $ \_ _ -> (v, mempty) {-# INLINE unsafeSqlValue #-} +unsafeSqlEntity :: PersistEntity ent => Ident -> SqlExpr (Entity ent) +unsafeSqlEntity ident = ERaw noMeta $ \_ info -> + (useIdent info ident, []) + valueToFunctionArg :: IdentInfo -> SqlExpr (Value a) -> (TLB.Builder, [PersistValue]) -valueToFunctionArg info v = - case v of - ERaw _ f -> f info - EAliasedValue i _ -> aliasedValueIdentToRawSql i info - EValueReference i i' -> valueReferenceToRawSql i i' info - ECompositeKey _ -> throw (CompositeKeyErr SqlFunctionError) +valueToFunctionArg info (ERaw _ f) = f Never info -- | (Internal) A raw SQL function. Once again, the same warning -- from 'unsafeSqlBinOp' applies to this function as well. @@ -2296,7 +2246,7 @@ unsafeSqlFunction :: UnsafeSqlFunctionArgument a => TLB.Builder -> a -> SqlExpr (Value b) unsafeSqlFunction name arg = - ERaw Never $ \info -> + ERaw noMeta $ \p info -> let (argsTLB, argsVals) = uncommas' $ map (valueToFunctionArg info) $ toArgList arg in @@ -2310,7 +2260,7 @@ unsafeSqlExtractSubField :: UnsafeSqlFunctionArgument a => TLB.Builder -> a -> SqlExpr (Value b) unsafeSqlExtractSubField subField arg = - ERaw Never $ \info -> + ERaw noMeta $ \_ info -> let (argsTLB, argsVals) = uncommas' $ map (valueToFunctionArg info) $ toArgList arg in @@ -2322,13 +2272,8 @@ unsafeSqlFunctionParens :: UnsafeSqlFunctionArgument a => TLB.Builder -> a -> SqlExpr (Value b) unsafeSqlFunctionParens name arg = - ERaw Never $ \info -> - let valueToFunctionArgParens v = - case v of - ERaw p f -> first (parensM p) (f info) - EAliasedValue i _ -> aliasedValueIdentToRawSql i info - EValueReference i i' -> valueReferenceToRawSql i i' info - ECompositeKey _ -> throw (CompositeKeyErr SqlFunctionError) + ERaw noMeta $ \p info -> + let valueToFunctionArgParens (ERaw _ f) = f Never info (argsTLB, argsVals) = uncommas' $ map valueToFunctionArgParens $ toArgList arg in @@ -2337,16 +2282,7 @@ unsafeSqlFunctionParens name arg = -- | (Internal) An explicit SQL type cast using CAST(value as type). -- See 'unsafeSqlBinOp' for warnings. unsafeSqlCastAs :: T.Text -> SqlExpr (Value a) -> SqlExpr (Value b) -unsafeSqlCastAs t v = ERaw Never ((first (\value -> "CAST" <> parens (value <> " AS " <> TLB.fromText t))) . valueToText) - where - valueToText info = - case v of - (ERaw p f) -> - let (b, vals) = f info - in (parensM p b, vals) - EAliasedValue i _ -> aliasedValueIdentToRawSql i info - EValueReference i i' -> valueReferenceToRawSql i i' info - ECompositeKey _ -> throw (CompositeKeyErr SqlCastAsError) +unsafeSqlCastAs t (ERaw _ f) = ERaw noMeta $ \_ -> ((first (\value -> "CAST" <> parens (value <> " AS " <> TLB.fromText t))) . f Never) -- | (Internal) This class allows 'unsafeSqlFunction' to work with different -- numbers of arguments; specifically it allows providing arguments to a sql @@ -2477,17 +2413,13 @@ instance ( UnsafeSqlFunctionArgument a -- 'SqlExpr (Value b)'. You should /not/ use this function -- unless you know what you're doing! veryUnsafeCoerceSqlExprValue :: SqlExpr (Value a) -> SqlExpr (Value b) -veryUnsafeCoerceSqlExprValue (ERaw p f) = ERaw p f -veryUnsafeCoerceSqlExprValue (ECompositeKey f) = ECompositeKey f -veryUnsafeCoerceSqlExprValue (EAliasedValue i v) = EAliasedValue i (veryUnsafeCoerceSqlExprValue v) -veryUnsafeCoerceSqlExprValue (EValueReference i i') = EValueReference i i' +veryUnsafeCoerceSqlExprValue = coerce -- | (Internal) Coerce a value's type from 'SqlExpr (ValueList -- a)' to 'SqlExpr (Value a)'. Does not work with empty lists. veryUnsafeCoerceSqlExprValueList :: SqlExpr (ValueList a) -> SqlExpr (Value a) -veryUnsafeCoerceSqlExprValueList (EList v) = v -veryUnsafeCoerceSqlExprValueList EEmptyList = throw (UnexpectedCaseErr EmptySqlExprValueList) +veryUnsafeCoerceSqlExprValueList = coerce ---------------------------------------------------------------------- @@ -2857,7 +2789,7 @@ makeSelect info mode_ distinctClause ret = process mode_ first (("SELECT DISTINCT ON (" <>) . (<> ") ")) $ uncommas' (processExpr <$> exprs) where - processExpr (EDistinctOn f) = materializeExpr info f + processExpr e = materializeExpr info (coerce e :: SqlExpr (Value a)) withCols v = v <> sqlSelectCols info ret plain v = (v, []) @@ -2915,38 +2847,22 @@ makeFrom info mode fs = ret fromKind RightOuterJoinKind = " RIGHT OUTER JOIN " fromKind FullOuterJoinKind = " FULL OUTER JOIN " - makeOnClause (ERaw _ f) = first (" ON " <>) (f info) - makeOnClause (ECompositeKey _) = throw (CompositeKeyErr MakeOnClauseError) - makeOnClause (EAliasedValue _ _) = throw (AliasedValueErr MakeOnClauseError) - makeOnClause (EValueReference _ _) = throw (AliasedValueErr MakeOnClauseError) + makeOnClause (ERaw _ f) = first (" ON " <>) (f Never info) mkExc :: SqlExpr (Value Bool) -> OnClauseWithoutMatchingJoinException mkExc (ERaw _ f) = OnClauseWithoutMatchingJoinException $ - TL.unpack $ TLB.toLazyText $ fst (f info) - mkExc (ECompositeKey _) = throw (CompositeKeyErr MakeExcError) - mkExc (EAliasedValue _ _) = throw (AliasedValueErr MakeExcError) - mkExc (EValueReference _ _) = throw (AliasedValueErr MakeExcError) + TL.unpack $ TLB.toLazyText $ fst (f Never info) makeSet :: IdentInfo -> [SetClause] -> (TLB.Builder, [PersistValue]) makeSet _ [] = mempty makeSet info os = first ("\nSET " <>) . uncommas' $ concatMap mk os where - mk (SetClause (ERaw _ f)) = [f info] - mk (SetClause (ECompositeKey _)) = throw (CompositeKeyErr MakeSetError) -- FIXME - mk (SetClause (EAliasedValue i _)) = [aliasedValueIdentToRawSql i info] - mk (SetClause (EValueReference i i')) = [valueReferenceToRawSql i i' info] + mk (SetClause (ERaw _ f)) = [f Never info] makeWhere :: IdentInfo -> WhereClause -> (TLB.Builder, [PersistValue]) -makeWhere _ NoWhere = mempty -makeWhere info (Where v) = first ("\nWHERE " <>) $ x info - where - x = - case v of - ERaw _ f -> f - EAliasedValue i _ -> aliasedValueIdentToRawSql i - EValueReference i i' -> valueReferenceToRawSql i i' - ECompositeKey _ -> throw (CompositeKeyErr MakeWhereError) +makeWhere _ NoWhere = mempty +makeWhere info (Where (ERaw _ f)) = first ("\nWHERE " <>) $ f Never info makeGroupBy :: IdentInfo -> GroupByClause -> (TLB.Builder, [PersistValue]) makeGroupBy _ (GroupBy []) = (mempty, []) @@ -2956,21 +2872,11 @@ makeGroupBy info (GroupBy fields) = first ("\nGROUP BY " <>) build build = uncommas' $ map match fields match :: SomeValue -> (TLB.Builder, [PersistValue]) - match (SomeValue (ERaw _ f)) = f info - match (SomeValue (ECompositeKey f)) = (mconcat $ f info, mempty) - match (SomeValue (EAliasedValue i _)) = aliasedValueIdentToRawSql i info - match (SomeValue (EValueReference i i')) = valueReferenceToRawSql i i' info + match (SomeValue (ERaw _ f)) = f Never info makeHaving :: IdentInfo -> WhereClause -> (TLB.Builder, [PersistValue]) makeHaving _ NoWhere = mempty -makeHaving info (Where v) = first ("\nHAVING " <>) $ x info - where - x = - case v of - ERaw _ f -> f - EAliasedValue i _ -> aliasedValueIdentToRawSql i - EValueReference i i' -> valueReferenceToRawSql i i' - ECompositeKey _ -> throw (CompositeKeyErr MakeHavingError) +makeHaving info (Where (ERaw _ f)) = first ("\nHAVING " <>) $ f Never info -- makeHaving, makeWhere and makeOrderBy makeOrderByNoNewline @@ -2979,19 +2885,7 @@ makeOrderByNoNewline _ [] = mempty makeOrderByNoNewline info os = first ("ORDER BY " <>) . uncommas' $ concatMap mk os where mk :: OrderByClause -> [(TLB.Builder, [PersistValue])] - mk (EOrderBy t (ECompositeKey f)) = - let fs = f info - vals = repeat [] - in zip (map (<> orderByType t) fs) vals - mk (EOrderBy t v) = - let x = - case v of - ERaw p f -> (first (parensM p)) . f - EAliasedValue i _ -> aliasedValueIdentToRawSql i - EValueReference i i' -> valueReferenceToRawSql i i' - ECompositeKey _ -> undefined -- defined above - in [ first (<> orderByType t) $ x info ] - mk EOrderRandom = [first (<> "RANDOM()") mempty] + mk (ERaw _ f) = [f Never info] orderByType ASC = " ASC" orderByType DESC = " DESC" @@ -3002,8 +2896,6 @@ makeOrderBy info is = let (tlb, vals) = makeOrderByNoNewline info is in ("\n" <> tlb, vals) -{-# DEPRECATED EOrderRandom "Since 2.6.0: `rand` ordering function is not uniform across all databases! To avoid accidental partiality it will be removed in the next major version." #-} - makeLimit :: IdentInfo -> LimitClause -> [OrderByClause] -> (TLB.Builder, [PersistValue]) makeLimit (conn, _) (Limit ml mo) orderByClauses = let limitRaw = connLimitOffset conn (v ml, v mo) hasOrderClause "\n" @@ -3061,18 +2953,20 @@ class SqlSelect a r | a -> r, r -> a where -- | @INSERT INTO@ hack. -instance SqlSelect (SqlExpr InsertFinal) InsertFinal where - sqlInsertInto info (EInsertFinal (EInsert p _)) = +instance PersistEntity e => SqlSelect (SqlExpr (Insertion e)) (Insertion e) where + sqlInsertInto info e = let fields = uncommas $ map (fromDBName info . fieldDB) $ entityFields $ - entityDef p + entityDef (proxy e) + proxy :: SqlExpr (Insertion a) -> Proxy a + proxy = const Proxy table = - fromDBName info . entityDB . entityDef $ p + fromDBName info . entityDB . entityDef . proxy in - ("INSERT INTO " <> table <> parens fields <> "\n", []) - sqlSelectCols info (EInsertFinal (EInsert _ f)) = f info + ("INSERT INTO " <> table e <> parens fields <> "\n", []) + sqlSelectCols info (ERaw _ f) = f Never info sqlSelectColCount = const 0 sqlSelectProcessRow = const (Right (throw (UnexpectedCaseErr InsertionFinalError))) @@ -3090,37 +2984,36 @@ unescapedColumnNames ent = -- | You may return an 'Entity' from a 'select' query. instance PersistEntity a => SqlSelect (SqlExpr (Entity a)) (Entity a) where - sqlSelectCols info expr@(EEntity ident) = ret - where - process ed = uncommas $ - map ((name <>) . TLB.fromText) $ - entityColumnNames ed (fst info) - -- 'name' is the biggest difference between 'RawSql' and - -- 'SqlSelect'. We automatically create names for tables - -- (since it's not the user who's writing the FROM - -- clause), while 'rawSql' assumes that it's just the - -- name of the table (which doesn't allow self-joins, for - -- example). - name = useIdent info ident <> "." - ret = let ed = entityDef $ getEntityVal $ return expr - in (process ed, mempty) - sqlSelectCols info expr@(EAliasedEntity aliasIdent tableIdent) = ret - where - process ed = uncommas $ - map ((name <>) . aliasName) $ - unescapedColumnNames ed - aliasName columnName = (fromDBName info columnName) <> " AS " <> aliasedColumnName aliasIdent info (unDBName columnName) - name = useIdent info tableIdent <> "." - ret = let ed = entityDef $ getEntityVal $ return expr - in (process ed, mempty) - sqlSelectCols info expr@(EAliasedEntityReference sourceIdent baseIdent) = ret - where - process ed = uncommas $ - map ((name <>) . aliasedColumnName baseIdent info . unDBName) $ - unescapedColumnNames ed - name = useIdent info sourceIdent <> "." - ret = let ed = entityDef $ getEntityVal $ return expr - in (process ed, mempty) + sqlSelectCols info expr@(ERaw m f) + | Just baseIdent <- sqlExprMetaAlias m, False <- sqlExprMetaIsReference m = + let process ed = uncommas $ + map ((name <>) . aliasName) $ + unescapedColumnNames ed + aliasName columnName = (fromDBName info columnName) <> " AS " <> aliasedColumnName baseIdent info (unDBName columnName) + name = fst (f Never info) <> "." + ed = entityDef $ getEntityVal $ return expr + in (process ed, mempty) + | Just baseIdent <- sqlExprMetaAlias m, True <- sqlExprMetaIsReference m = + let process ed = uncommas $ + map ((name <>) . aliasedColumnName baseIdent info . unDBName) $ + unescapedColumnNames ed + name = fst (f Never info) <> "." + ed = entityDef $ getEntityVal $ return expr + in (process ed, mempty) + | otherwise = + let process ed = uncommas $ + map ((name <>) . TLB.fromText) $ + entityColumnNames ed (fst info) + -- 'name' is the biggest difference between 'RawSql' and + -- 'SqlSelect'. We automatically create names for tables + -- (since it's not the user who's writing the FROM + -- clause), while 'rawSql' assumes that it's just the + -- name of the table (which doesn't allow self-joins, for + -- example). + name = fst (f Never info) <> "." + ed = entityDef $ getEntityVal $ return expr + in (process ed, mempty) + sqlSelectColCount = entityColumnCount . entityDef . getEntityVal sqlSelectProcessRow = parseEntityValues ed where @@ -3131,7 +3024,7 @@ getEntityVal = const Proxy -- | You may return a possibly-@NULL@ 'Entity' from a 'select' query. instance PersistEntity a => SqlSelect (SqlExpr (Maybe (Entity a))) (Maybe (Entity a)) where - sqlSelectCols info (EMaybe ent) = sqlSelectCols info ent + sqlSelectCols info e = sqlSelectCols info (coerce e :: SqlExpr (Entity a)) sqlSelectColCount = sqlSelectColCount . fromEMaybe where fromEMaybe :: Proxy (SqlExpr (Maybe e)) -> Proxy (SqlExpr e) @@ -3151,17 +3044,12 @@ instance PersistField a => SqlSelect (SqlExpr (Value a)) (Value a) where -- | Materialize a @SqlExpr (Value a)@. materializeExpr :: IdentInfo -> SqlExpr (Value a) -> (TLB.Builder, [PersistValue]) -materializeExpr info (ERaw p f) = - let (b, vals) = f info - in (parensM p b, vals) -materializeExpr info (ECompositeKey f) = - let bs = f info - in (uncommas $ map (parensM Parens) bs, []) -materializeExpr info (EAliasedValue ident x) = - let (b, vals) = materializeExpr info x - in (b <> " AS " <> (useIdent info ident), vals) -materializeExpr info (EValueReference sourceIdent columnIdent) = - valueReferenceToRawSql sourceIdent columnIdent info +materializeExpr info (ERaw m f) + | Just fields <- sqlExprMetaCompositeFields m = (uncommas $ fmap parens $ fields info, []) + | Just alias <- sqlExprMetaAlias m + , not (sqlExprMetaIsReference m) = first (<> " AS " <> useIdent info alias) (f Parens info) + | otherwise = f Parens info + -- | You may return tuples (up to 16-tuples) and tuples of tuples -- from a 'select' query. @@ -3676,7 +3564,7 @@ insertSelectCount :: (MonadIO m, PersistEntity a) => SqlQuery (SqlExpr (Insertion a)) -> SqlWriteT m Int64 -insertSelectCount = rawEsqueleto INSERT_INTO . fmap EInsertFinal +insertSelectCount = rawEsqueleto INSERT_INTO -- | Renders an expression into 'Text'. Only useful for creating a textual -- representation of the clauses passed to an "On" clause. @@ -3684,20 +3572,8 @@ insertSelectCount = rawEsqueleto INSERT_INTO . fmap EInsertFinal -- @since 3.2.0 renderExpr :: SqlBackend -> SqlExpr (Value Bool) -> T.Text renderExpr sqlBackend e = case e of - ERaw _ mkBuilderValues -> do - let (builder, _) = mkBuilderValues (sqlBackend, initialIdentState) - in (builderToText builder) - ECompositeKey mkInfo -> - throw - . RenderExprUnexpectedECompositeKey - . builderToText - . mconcat - . mkInfo - $ (sqlBackend, initialIdentState) - EAliasedValue i _ -> - builderToText $ useIdent (sqlBackend, initialIdentState) i - EValueReference i i' -> - let (builder, _) = valueReferenceToRawSql i i' (sqlBackend, initialIdentState) + ERaw _ mkBuilderValues -> + let (builder, _) = mkBuilderValues Never (sqlBackend, initialIdentState) in (builderToText builder) -- | An exception thrown by 'RenderExpr' - it's not designed to handle composite diff --git a/src/Database/Esqueleto/Internal/Sql.hs b/src/Database/Esqueleto/Internal/Sql.hs index 3b92975..2af0009 100644 --- a/src/Database/Esqueleto/Internal/Sql.hs +++ b/src/Database/Esqueleto/Internal/Sql.hs @@ -37,8 +37,8 @@ module Database.Esqueleto.Internal.Sql -- * The guts , unsafeSqlCase , unsafeSqlBinOp - , unsafeSqlBinOpComposite , unsafeSqlValue + , unsafeSqlEntity , unsafeSqlCastAs , unsafeSqlFunction , unsafeSqlExtractSubField diff --git a/src/Database/Esqueleto/PostgreSQL.hs b/src/Database/Esqueleto/PostgreSQL.hs index bf571f0..35a2c43 100644 --- a/src/Database/Esqueleto/PostgreSQL.hs +++ b/src/Database/Esqueleto/PostgreSQL.hs @@ -83,18 +83,18 @@ unsafeSqlAggregateFunction -> a -> [OrderByClause] -> SqlExpr (Value b) -unsafeSqlAggregateFunction name mode args orderByClauses = ERaw Never $ \info -> +unsafeSqlAggregateFunction name mode args orderByClauses = ERaw noMeta $ \_ info -> let (orderTLB, orderVals) = makeOrderByNoNewline info orderByClauses -- Don't add a space if we don't have order by clauses orderTLBSpace = case orderByClauses of - [] -> "" + [] -> "" (_:_) -> " " (argsTLB, argsVals) = - uncommas' $ map (\(ERaw _ f) -> f info) $ toArgList args + uncommas' $ map (\(ERaw _ f) -> f Never info) $ toArgList args aggMode = case mode of - AggModeAll -> "" + AggModeAll -> "" -- ALL is the default, so we don't need to -- specify it AggModeDistinct -> "DISTINCT " @@ -182,7 +182,7 @@ upsert ) => record -- ^ new record to insert - -> [SqlExpr (Update record)] + -> [SqlExpr (Entity record) -> SqlExpr Update] -- ^ updates to perform if the record already exists -> R.ReaderT SqlBackend m (Entity record) -- ^ the record in the database after the operation @@ -200,7 +200,7 @@ upsertBy -- ^ uniqueness constraint to find by -> record -- ^ new record to insert - -> [SqlExpr (Update record)] + -> [SqlExpr (Entity record) -> SqlExpr Update] -- ^ updates to perform if the record already exists -> R.ReaderT SqlBackend m (Entity record) -- ^ the record in the database after the operation @@ -276,7 +276,7 @@ insertSelectWithConflict -- a unique "MyUnique 0", "MyUnique undefined" would work as well. -> SqlQuery (SqlExpr (Insertion val)) -- ^ Insert query. - -> (SqlExpr (Entity val) -> SqlExpr (Entity val) -> [SqlExpr (Update val)]) + -> (SqlExpr (Entity val) -> SqlExpr (Entity val) -> [SqlExpr (Entity val) -> SqlExpr Update]) -- ^ A list of updates to be applied in case of the constraint being -- violated. The expression takes the current and excluded value to produce -- the updates. @@ -292,22 +292,22 @@ insertSelectWithConflictCount . (FinalResult a, KnowResult a ~ Unique val, MonadIO m, PersistEntity val) => a -> SqlQuery (SqlExpr (Insertion val)) - -> (SqlExpr (Entity val) -> SqlExpr (Entity val) -> [SqlExpr (Update val)]) + -> (SqlExpr (Entity val) -> SqlExpr (Entity val) -> [SqlExpr (Entity val) -> SqlExpr Update]) -> SqlWriteT m Int64 insertSelectWithConflictCount unique query conflictQuery = do conn <- R.ask uncurry rawExecuteCount $ combine - (toRawSql INSERT_INTO (conn, initialIdentState) (fmap EInsertFinal query)) + (toRawSql INSERT_INTO (conn, initialIdentState) query) (conflict conn) where proxy :: Proxy val proxy = Proxy updates = conflictQuery entCurrent entExcluded combine (tlb1,vals1) (tlb2,vals2) = (builderToText (tlb1 `mappend` tlb2), vals1 ++ vals2) - entExcluded = EEntity $ I "excluded" + entExcluded = unsafeSqlEntity (I "excluded") tableName = unDBName . entityDB . entityDef - entCurrent = EEntity $ I (tableName proxy) + entCurrent = unsafeSqlEntity (I (tableName proxy)) uniqueDef = toUniqueDef unique constraint = TLB.fromText . unDBName . uniqueDBName $ uniqueDef renderedUpdates :: (BackendCompatible SqlBackend backend) => backend -> (TLB.Builder, [PersistValue]) @@ -355,13 +355,11 @@ filterWhere -> SqlExpr (Value Bool) -- ^ Filter clause -> SqlExpr (Value a) -filterWhere aggExpr clauseExpr = ERaw Never $ \info -> +filterWhere aggExpr clauseExpr = ERaw noMeta $ \_ info -> let (aggBuilder, aggValues) = case aggExpr of - ERaw _ aggF -> aggF info - ECompositeKey _ -> throw $ CompositeKeyErr FilterWhereAggError + ERaw _ aggF -> aggF Never info (clauseBuilder, clauseValues) = case clauseExpr of - ERaw _ clauseF -> clauseF info - ECompositeKey _ -> throw $ CompositeKeyErr FilterWhereClauseError + ERaw _ clauseF -> clauseF Never info in ( aggBuilder <> " FILTER (WHERE " <> clauseBuilder <> ")" , aggValues <> clauseValues ) diff --git a/test/Common/Test.hs b/test/Common/Test.hs index 414908e..8eb157b 100644 --- a/test/Common/Test.hs +++ b/test/Common/Test.hs @@ -1,6 +1,6 @@ {-# LANGUAGE CPP #-} -{-# LANGUAGE DataKinds #-} {-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE EmptyDataDecls #-} @@ -385,7 +385,7 @@ testSubSelect run = do v `shouldBe` [Value 1] describe "subSelectList" $ do - it "is safe on empty databases as well as good databases" $ do + it "is safe on empty databases as well as good databases" $ run $ do let query = from $ \n -> do where_ $ n ^. NumbersInt `in_` do @@ -394,16 +394,15 @@ testSubSelect run = do where_ $ n' ^. NumbersInt >=. val 3 pure (n' ^. NumbersInt) pure n + empty <- select query - empty <- run $ do - select query - - full <- run $ do + full <- do setup select query - empty `shouldBe` [] - full `shouldSatisfy` (not . null) + liftIO $ do + empty `shouldBe` [] + full `shouldSatisfy` (not . null) describe "subSelectMaybe" $ do it "is equivalent to joinV . subSelect" $ do @@ -888,12 +887,14 @@ testSelectSubQuery run = describe "select subquery" $ do l1Deeds <- mapM (\k -> insert' $ Deed k (entityKey l1e)) (map show [1..3 :: Int]) let l1WithDeeds = do d <- l1Deeds pure (l1e, Just d) - ret <- select $ Experimental.from $ do - (lords :& deeds) <- - Experimental.from $ Table @Lord - `LeftOuterJoin` Table @Deed - `Experimental.on` (\(l :& d) -> just (l ^. LordId) ==. d ?. DeedOwnerId) - pure (lords, deeds) + let q = Experimental.from $ do + (lords :& deeds) <- + Experimental.from $ Table @Lord + `LeftOuterJoin` Table @Deed + `Experimental.on` (\(l :& d) -> just (l ^. LordId) ==. d ?. DeedOwnerId) + pure (lords, deeds) + + ret <- select q liftIO $ ret `shouldMatchList` ((l3e, Nothing) : l1WithDeeds) it "lets you order by alias" $ run $ do @@ -1078,17 +1079,6 @@ testSelectWhere run = describe "select where_" $ do ( val $ PointKey 1 2 , val $ PointKey 5 6 ) liftIO $ ret `shouldBe` [()] - it "works when using ECompositeKey constructor" $ run $ do - insert_ $ Point 1 2 "" - ret <- - select $ - from $ \p -> do - where_ $ - p ^. PointId - `between` - ( EI.ECompositeKey $ const ["3", "4"] - , EI.ECompositeKey $ const ["5", "6"] ) - liftIO $ ret `shouldBe` [] it "works with avg_" $ run $ do _ <- insert' p1 @@ -1851,9 +1841,10 @@ testRenderSql run = do (c, expr) <- run $ do conn <- ask let Right c = P.mkEscapeChar conn + let user = EI.unsafeSqlEntity (EI.I "user") + blogPost = EI.unsafeSqlEntity (EI.I "blog_post") pure $ (,) c $ EI.renderExpr conn $ - EI.EEntity (EI.I "user") ^. PersonId - ==. EI.EEntity (EI.I "blog_post") ^. BlogPostAuthorId + user ^. PersonId ==. blogPost ^. BlogPostAuthorId expr `shouldBe` Text.intercalate (Text.singleton c) ["", "user", ".", "id", ""] @@ -1865,23 +1856,6 @@ testRenderSql run = do expr <- run $ ask >>= \c -> pure $ EI.renderExpr c (val (PersonKey 0) ==. val (PersonKey 1)) expr `shouldBe` "? = ?" - describe "EEntity Ident behavior" $ do - let render :: SqlExpr (Entity val) -> Text.Text - render (EI.EEntity (EI.I ident)) = ident - render _ = error "guess we gotta handle this in the test suite now" - it "renders sensibly" $ run $ do - _ <- insert $ Foo 2 - _ <- insert $ Foo 3 - _ <- insert $ Person "hello" Nothing Nothing 3 - results <- select $ - from $ \(a `LeftOuterJoin` b) -> do - on $ a ^. FooName ==. b ^. PersonFavNum - pure (val (render a), val (render b)) - liftIO $ - head results - `shouldBe` - (Value "Foo", Value "Person") - describe "ExprParser" $ do let parse parser = AP.parseOnly (parser '#') describe "parseEscapedChars" $ do