@@ -1,64 +1,65 @@
1{-# language DuplicateRecordFields #-}
1{-# language DuplicateRecordFields #-}
2+{-# language FlexibleContexts #-}
2{-# language GADTs #-}
3{-# language GADTs #-}
3{-# language NamedFieldPuns #-}
4{-# language NamedFieldPuns #-}
4−{-# language ScopedTypeVariables #-}
5+{-# language RecordWildCards #-}
5{-# language StandaloneKindSignatures #-}
6{-# language StandaloneKindSignatures #-}
6−{-# language TypeApplications #-}
7+{-# language StrictData #-}
8module Rel8.Statement.Insert
9module Rel8.Statement.Insert
9 ( Insert(..)
10 ( Insert(..)
16import Control.Exception ( throwIO )
18import Control.Exception ( throwIO )
17−import Data.List.NonEmpty ( NonEmpty( (:|) ) )
19+import Data.Foldable ( toList )
18import Data.Kind ( Type )
20import Data.Kind ( Type )
19import Prelude
21import Prelude
22import Hasql.Connection ( Connection )
24import Hasql.Connection ( Connection )
23−import qualified Hasql.Decoders as Hasql
24import qualified Hasql.Encoders as Hasql
25import qualified Hasql.Encoders as Hasql
25import qualified Hasql.Session as Hasql
26import qualified Hasql.Session as Hasql
26import qualified Hasql.Statement as Hasql
27import qualified Hasql.Statement as Hasql
29−import qualified Opaleye.Internal.Manipulation as Opaleye
30+import qualified Opaleye.Internal.HaskellDB.Sql.Print as Opaleye
30−import qualified Opaleye.Manipulation as Opaleye
31+
33+import Text.PrettyPrint ( Doc, (<+>), ($$), parens, text )
36+import Rel8.Query ( Query )
33−import Rel8.Schema.Name ( Selects )
37+import Rel8.Schema.Name ( Name, Selects, ppColumn )
34−import Rel8.Schema.Table ( TableSchema )
38+import Rel8.Schema.Table ( TableSchema(..), ppTable )
35−import Rel8.Statement.Returning ( Returning( Projection, NumberOfRowsAffected ) )
39+import Rel8.Statement.OnConflict ( OnConflict, ppOnConflict )
36−import Rel8.Table.Cols ( fromCols, toCols )
40+import Rel8.Statement.Returning
42+ , decodeReturning, emptyReturning, ppReturning
44+import Rel8.Statement.Select ( ppSelect )
37−import Rel8.Table.Opaleye ( castTable, table, unpackspec )
45+import Rel8.Table ( Table )
38−import Rel8.Table.Serialize ( Serializable, parse )
46+import Rel8.Table.Name ( showNames )
41import qualified Data.Text as Text ( pack )
49import qualified Data.Text as Text ( pack )
42import Data.Text.Encoding ( encodeUtf8 )
50import Data.Text.Encoding ( encodeUtf8 )
45−-- | @OnConflict@ allows you to add an @ON CONFLICT@ clause to an @INSERT@
47−type OnConflict :: Type
49− = Abort -- ^ @ON CONFLICT ABORT@
50− | DoNothing -- ^ @ON CONFLICT DO NOTHING@
53-- | The constituent parts of a SQL @INSERT@ statement.
53-- | The constituent parts of a SQL @INSERT@ statement.
54type Insert :: Type -> Type
54type Insert :: Type -> Type
55data Insert a where
55data Insert a where
56 Insert :: Selects names exprs =>
56 Insert :: Selects names exprs =>
57 { into :: TableSchema names
57 { into :: TableSchema names
58 -- ^ Which table to insert into.
58 -- ^ Which table to insert into.
59− , rows :: [exprs]
59+ , rows :: Query exprs
60− -- ^ The rows to insert.
60+ -- ^ The rows to insert. This can be an arbitrary query — use
61+ -- 'Rel8.values' insert a static list of rows.
61− , onConflict :: OnConflict
62+ , onConflict :: OnConflict names
62 -- ^ What to do if the inserted rows conflict with data already in the
63 -- ^ What to do if the inserted rows conflict with data already in the
64 , returning :: Returning names a
65 , returning :: Returning names a
@@ -67,52 +68,32 @@ data Insert a where
67 -> Insert a
68 -> Insert a
70−-- | Run an @INSERT@ statement
71+ppInsert :: Insert a -> Maybe Doc
71−insert :: Connection -> Insert a -> IO a
72+ppInsert Insert {..} = do
72−insert c Insert {into, rows, onConflict, returning} =
73+ rows' <- ppSelect rows
73− case (rows, returning) of
74+ pure $ text "INSERT INTO" <+> ppInto into
74− ([], NumberOfRowsAffected) -> pure 0
75+ $$ rows'
75− ([], Projection _) -> pure []
76+ $$ ppOnConflict into onConflict
77+ $$ ppReturning into returning
77− (x:xs, NumberOfRowsAffected) -> Hasql.run session c >>= either throwIO pure
79− session = Hasql.statement () statement
80− statement = Hasql.Statement bytes params decode prepare
81− bytes = encodeUtf8 $ Text.pack sql
82− params = Hasql.noParams
83− decode = Hasql.rowsAffected
85− sql = Opaleye.arrangeInsertManySql into' rows' onConflict'
87− into' = table $ toCols <$> into
88− rows' = toCols <$> x :| xs
80+ppInto :: Table Name a => TableSchema a -> Doc
81+ppInto table@TableSchema {columns} =
83+ parens (Opaleye.commaV ppColumn (toList (showNames columns)))
86+-- | Run an 'Insert' statement.
87+insert :: Connection -> Insert a -> IO a
88+insert connection i@Insert {returning} =
89+ case show <$> ppInsert i of
90+ Nothing -> pure (emptyReturning returning)
90− (x:xs, Projection project) -> Hasql.run session c >>= either throwIO pure
92+ Hasql.run session connection >>= either throwIO pure
92 session = Hasql.statement () statement
94 session = Hasql.statement () statement
93 statement = Hasql.Statement bytes params decode prepare
95 statement = Hasql.Statement bytes params decode prepare
94 bytes = encodeUtf8 $ Text.pack sql
96 bytes = encodeUtf8 $ Text.pack sql
95 params = Hasql.noParams
97 params = Hasql.noParams
96− decode = decoder project
98+ decode = decodeReturning returning
97 prepare = False
99 prepare = False
99− Opaleye.arrangeInsertManyReturningSql
106− into' = table $ toCols <$> into
107− rows' = toCols <$> x :| xs
108− project' = castTable . toCols . project . fromCols
113− DoNothing -> Just Opaleye.DoNothing
116− decoder :: forall exprs projection a. Serializable projection a
117− => (exprs -> projection) -> Hasql.Result [a]
118− decoder _ = Hasql.rowList (parse @projection @a)