Merge pull request #9 from foxhound-systems/final-expr

Convert SqlExpr to a final encoding
This commit is contained in:
Ben Levy 2021-02-05 11:39:21 -06:00 committed by GitHub
commit e3ae687309
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 358 additions and 504 deletions

View File

@ -52,7 +52,7 @@ instance PersistEntity a => From (Table a) where
runFrom e@Table = do runFrom e@Table = do
let ed = entityDef $ getVal e let ed = entityDef $ getVal e
ident <- newIdentFor (entityDB ed) ident <- newIdentFor (entityDB ed)
let entity = EEntity ident let entity = unsafeSqlEntity ident
pure $ (entity, FromStart ident ed) pure $ (entity, FromStart ident ed)
where where
getVal :: Table ent -> Proxy ent getVal :: Table ent -> Proxy ent

View File

@ -16,20 +16,22 @@ class ToAlias a where
toAlias :: a -> SqlQuery a toAlias :: a -> SqlQuery a
instance ToAlias (SqlExpr (Value a)) where instance ToAlias (SqlExpr (Value a)) where
toAlias v@(EAliasedValue _ _) = pure v toAlias e@(ERaw m f)
toAlias v = do | Just _ <- sqlExprMetaAlias m, not (sqlExprMetaIsReference m) = pure e
ident <- newIdentFor (DBName "v") | otherwise = do
pure $ EAliasedValue ident v ident <- newIdentFor (DBName "v")
pure $ ERaw noMeta{sqlExprMetaAlias = Just ident} f
instance ToAlias (SqlExpr (Entity a)) where instance ToAlias (SqlExpr (Entity a)) where
toAlias v@(EAliasedEntityReference _ _) = pure v toAlias (ERaw m f) = do
toAlias v@(EAliasedEntity _ _) = pure v
toAlias (EEntity tableIdent) = do
ident <- newIdentFor (DBName "v") ident <- newIdentFor (DBName "v")
pure $ EAliasedEntity ident tableIdent pure $ ERaw m{sqlExprMetaIsReference = False, sqlExprMetaAlias = Just ident} f
instance ToAlias (SqlExpr (Maybe (Entity a))) where 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 instance (ToAlias a, ToAlias b) => ToAlias (a,b) where
toAlias (a,b) = (,) <$> toAlias a <*> toAlias b toAlias (a,b) = (,) <$> toAlias a <*> toAlias b

View File

@ -1,10 +1,11 @@
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
module Database.Esqueleto.Experimental.ToAliasReference module Database.Esqueleto.Experimental.ToAliasReference
where where
import Database.Esqueleto.Experimental.ToAlias import Data.Coerce
import Database.Esqueleto.Internal.Internal hiding (From, from, on) import Database.Esqueleto.Internal.Internal hiding (From, from, on)
import Database.Esqueleto.Internal.PersistentImport import Database.Esqueleto.Internal.PersistentImport
@ -16,18 +17,21 @@ class ToAliasReference a where
toAliasReference :: Ident -> a -> SqlQuery a toAliasReference :: Ident -> a -> SqlQuery a
instance ToAliasReference (SqlExpr (Value a)) where instance ToAliasReference (SqlExpr (Value a)) where
toAliasReference aliasSource (EAliasedValue aliasIdent _) = pure $ EValueReference aliasSource (\_ -> aliasIdent) toAliasReference aliasSource (ERaw m _)
toAliasReference _ v@(ERaw _ _) = toAlias v | Just alias <- sqlExprMetaAlias m = pure $ ERaw m{sqlExprMetaIsReference = True} $ \_ info ->
toAliasReference _ v@(ECompositeKey _) = toAlias v (useIdent info aliasSource <> "." <> useIdent info alias, [])
toAliasReference s (EValueReference _ b) = pure $ EValueReference s b toAliasReference _ e = pure e
instance ToAliasReference (SqlExpr (Entity a)) where instance ToAliasReference (SqlExpr (Entity a)) where
toAliasReference aliasSource (EAliasedEntity ident _) = pure $ EAliasedEntityReference aliasSource ident toAliasReference aliasSource (ERaw m _)
toAliasReference _ e@(EEntity _) = toAlias e | Just _ <- sqlExprMetaAlias m, False <- sqlExprMetaIsReference m =
toAliasReference s (EAliasedEntityReference _ b) = pure $ EAliasedEntityReference s b pure $ ERaw m{sqlExprMetaIsReference = True} $ \_ info ->
(useIdent info aliasSource, [])
toAliasReference _ e = pure e
instance ToAliasReference (SqlExpr (Maybe (Entity a))) where 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 instance (ToAliasReference a, ToAliasReference b) => ToAliasReference (a, b) where

View File

@ -21,7 +21,7 @@ instance ToMaybe (SqlExpr (Maybe a)) where
instance ToMaybe (SqlExpr (Entity a)) where instance ToMaybe (SqlExpr (Entity a)) where
type ToMaybeT (SqlExpr (Entity a)) = SqlExpr (Maybe (Entity a)) type ToMaybeT (SqlExpr (Entity a)) = SqlExpr (Maybe (Entity a))
toMaybe = EMaybe toMaybe (ERaw f m) = (ERaw f m)
instance ToMaybe (SqlExpr (Value a)) where instance ToMaybe (SqlExpr (Value a)) where
type ToMaybeT (SqlExpr (Value a)) = SqlExpr (Value (Maybe (Nullable a))) type ToMaybeT (SqlExpr (Value a)) = SqlExpr (Value (Maybe (Nullable a)))

File diff suppressed because it is too large Load Diff

View File

@ -37,8 +37,8 @@ module Database.Esqueleto.Internal.Sql
-- * The guts -- * The guts
, unsafeSqlCase , unsafeSqlCase
, unsafeSqlBinOp , unsafeSqlBinOp
, unsafeSqlBinOpComposite
, unsafeSqlValue , unsafeSqlValue
, unsafeSqlEntity
, unsafeSqlCastAs , unsafeSqlCastAs
, unsafeSqlFunction , unsafeSqlFunction
, unsafeSqlExtractSubField , unsafeSqlExtractSubField

View File

@ -83,18 +83,18 @@ unsafeSqlAggregateFunction
-> a -> a
-> [OrderByClause] -> [OrderByClause]
-> SqlExpr (Value b) -> 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 let (orderTLB, orderVals) = makeOrderByNoNewline info orderByClauses
-- Don't add a space if we don't have order by clauses -- Don't add a space if we don't have order by clauses
orderTLBSpace = orderTLBSpace =
case orderByClauses of case orderByClauses of
[] -> "" [] -> ""
(_:_) -> " " (_:_) -> " "
(argsTLB, argsVals) = (argsTLB, argsVals) =
uncommas' $ map (\(ERaw _ f) -> f info) $ toArgList args uncommas' $ map (\(ERaw _ f) -> f Never info) $ toArgList args
aggMode = aggMode =
case mode of case mode of
AggModeAll -> "" AggModeAll -> ""
-- ALL is the default, so we don't need to -- ALL is the default, so we don't need to
-- specify it -- specify it
AggModeDistinct -> "DISTINCT " AggModeDistinct -> "DISTINCT "
@ -182,7 +182,7 @@ upsert
) )
=> record => record
-- ^ new record to insert -- ^ new record to insert
-> [SqlExpr (Update record)] -> [SqlExpr (Entity record) -> SqlExpr Update]
-- ^ updates to perform if the record already exists -- ^ updates to perform if the record already exists
-> R.ReaderT SqlBackend m (Entity record) -> R.ReaderT SqlBackend m (Entity record)
-- ^ the record in the database after the operation -- ^ the record in the database after the operation
@ -200,7 +200,7 @@ upsertBy
-- ^ uniqueness constraint to find by -- ^ uniqueness constraint to find by
-> record -> record
-- ^ new record to insert -- ^ new record to insert
-> [SqlExpr (Update record)] -> [SqlExpr (Entity record) -> SqlExpr Update]
-- ^ updates to perform if the record already exists -- ^ updates to perform if the record already exists
-> R.ReaderT SqlBackend m (Entity record) -> R.ReaderT SqlBackend m (Entity record)
-- ^ the record in the database after the operation -- ^ the record in the database after the operation
@ -276,7 +276,7 @@ insertSelectWithConflict
-- a unique "MyUnique 0", "MyUnique undefined" would work as well. -- a unique "MyUnique 0", "MyUnique undefined" would work as well.
-> SqlQuery (SqlExpr (Insertion val)) -> SqlQuery (SqlExpr (Insertion val))
-- ^ Insert query. -- ^ 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 -- ^ A list of updates to be applied in case of the constraint being
-- violated. The expression takes the current and excluded value to produce -- violated. The expression takes the current and excluded value to produce
-- the updates. -- the updates.
@ -292,22 +292,22 @@ insertSelectWithConflictCount
. (FinalResult a, KnowResult a ~ Unique val, MonadIO m, PersistEntity val) . (FinalResult a, KnowResult a ~ Unique val, MonadIO m, PersistEntity val)
=> a => a
-> SqlQuery (SqlExpr (Insertion val)) -> 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 -> SqlWriteT m Int64
insertSelectWithConflictCount unique query conflictQuery = do insertSelectWithConflictCount unique query conflictQuery = do
conn <- R.ask conn <- R.ask
uncurry rawExecuteCount $ uncurry rawExecuteCount $
combine combine
(toRawSql INSERT_INTO (conn, initialIdentState) (fmap EInsertFinal query)) (toRawSql INSERT_INTO (conn, initialIdentState) query)
(conflict conn) (conflict conn)
where where
proxy :: Proxy val proxy :: Proxy val
proxy = Proxy proxy = Proxy
updates = conflictQuery entCurrent entExcluded updates = conflictQuery entCurrent entExcluded
combine (tlb1,vals1) (tlb2,vals2) = (builderToText (tlb1 `mappend` tlb2), vals1 ++ vals2) combine (tlb1,vals1) (tlb2,vals2) = (builderToText (tlb1 `mappend` tlb2), vals1 ++ vals2)
entExcluded = EEntity $ I "excluded" entExcluded = unsafeSqlEntity (I "excluded")
tableName = unDBName . entityDB . entityDef tableName = unDBName . entityDB . entityDef
entCurrent = EEntity $ I (tableName proxy) entCurrent = unsafeSqlEntity (I (tableName proxy))
uniqueDef = toUniqueDef unique uniqueDef = toUniqueDef unique
constraint = TLB.fromText . unDBName . uniqueDBName $ uniqueDef constraint = TLB.fromText . unDBName . uniqueDBName $ uniqueDef
renderedUpdates :: (BackendCompatible SqlBackend backend) => backend -> (TLB.Builder, [PersistValue]) renderedUpdates :: (BackendCompatible SqlBackend backend) => backend -> (TLB.Builder, [PersistValue])
@ -355,13 +355,11 @@ filterWhere
-> SqlExpr (Value Bool) -> SqlExpr (Value Bool)
-- ^ Filter clause -- ^ Filter clause
-> SqlExpr (Value a) -> SqlExpr (Value a)
filterWhere aggExpr clauseExpr = ERaw Never $ \info -> filterWhere aggExpr clauseExpr = ERaw noMeta $ \_ info ->
let (aggBuilder, aggValues) = case aggExpr of let (aggBuilder, aggValues) = case aggExpr of
ERaw _ aggF -> aggF info ERaw _ aggF -> aggF Never info
ECompositeKey _ -> throw $ CompositeKeyErr FilterWhereAggError
(clauseBuilder, clauseValues) = case clauseExpr of (clauseBuilder, clauseValues) = case clauseExpr of
ERaw _ clauseF -> clauseF info ERaw _ clauseF -> clauseF Never info
ECompositeKey _ -> throw $ CompositeKeyErr FilterWhereClauseError
in ( aggBuilder <> " FILTER (WHERE " <> clauseBuilder <> ")" in ( aggBuilder <> " FILTER (WHERE " <> clauseBuilder <> ")"
, aggValues <> clauseValues , aggValues <> clauseValues
) )

View File

@ -1,6 +1,6 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE EmptyDataDecls #-} {-# LANGUAGE EmptyDataDecls #-}
@ -385,7 +385,7 @@ testSubSelect run = do
v `shouldBe` [Value 1] v `shouldBe` [Value 1]
describe "subSelectList" $ do 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 = let query =
from $ \n -> do from $ \n -> do
where_ $ n ^. NumbersInt `in_` do where_ $ n ^. NumbersInt `in_` do
@ -394,16 +394,15 @@ testSubSelect run = do
where_ $ n' ^. NumbersInt >=. val 3 where_ $ n' ^. NumbersInt >=. val 3
pure (n' ^. NumbersInt) pure (n' ^. NumbersInt)
pure n pure n
empty <- select query
empty <- run $ do full <- do
select query
full <- run $ do
setup setup
select query select query
empty `shouldBe` [] liftIO $ do
full `shouldSatisfy` (not . null) empty `shouldBe` []
full `shouldSatisfy` (not . null)
describe "subSelectMaybe" $ do describe "subSelectMaybe" $ do
it "is equivalent to joinV . subSelect" $ 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]) l1Deeds <- mapM (\k -> insert' $ Deed k (entityKey l1e)) (map show [1..3 :: Int])
let l1WithDeeds = do d <- l1Deeds let l1WithDeeds = do d <- l1Deeds
pure (l1e, Just d) pure (l1e, Just d)
ret <- select $ Experimental.from $ do let q = Experimental.from $ do
(lords :& deeds) <- (lords :& deeds) <-
Experimental.from $ Table @Lord Experimental.from $ Table @Lord
`LeftOuterJoin` Table @Deed `LeftOuterJoin` Table @Deed
`Experimental.on` (\(l :& d) -> just (l ^. LordId) ==. d ?. DeedOwnerId) `Experimental.on` (\(l :& d) -> just (l ^. LordId) ==. d ?. DeedOwnerId)
pure (lords, deeds) pure (lords, deeds)
ret <- select q
liftIO $ ret `shouldMatchList` ((l3e, Nothing) : l1WithDeeds) liftIO $ ret `shouldMatchList` ((l3e, Nothing) : l1WithDeeds)
it "lets you order by alias" $ run $ do it "lets you order by alias" $ run $ do
@ -1078,17 +1079,6 @@ testSelectWhere run = describe "select where_" $ do
( val $ PointKey 1 2 ( val $ PointKey 1 2
, val $ PointKey 5 6 ) , val $ PointKey 5 6 )
liftIO $ ret `shouldBe` [()] 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 it "works with avg_" $ run $ do
_ <- insert' p1 _ <- insert' p1
@ -1851,9 +1841,10 @@ testRenderSql run = do
(c, expr) <- run $ do (c, expr) <- run $ do
conn <- ask conn <- ask
let Right c = P.mkEscapeChar conn 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 $ pure $ (,) c $ EI.renderExpr conn $
EI.EEntity (EI.I "user") ^. PersonId user ^. PersonId ==. blogPost ^. BlogPostAuthorId
==. EI.EEntity (EI.I "blog_post") ^. BlogPostAuthorId
expr expr
`shouldBe` `shouldBe`
Text.intercalate (Text.singleton c) ["", "user", ".", "id", ""] 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 <- run $ ask >>= \c -> pure $ EI.renderExpr c (val (PersonKey 0) ==. val (PersonKey 1))
expr `shouldBe` "? = ?" 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 describe "ExprParser" $ do
let parse parser = AP.parseOnly (parser '#') let parse parser = AP.parseOnly (parser '#')
describe "parseEscapedChars" $ do describe "parseEscapedChars" $ do