From aac8fb6c51e5f2c28f384af4fab4e485fba7732b Mon Sep 17 00:00:00 2001 From: Teo Camarasu Date: Tue, 25 Aug 2026 18:47:55 +0100 Subject: [PATCH] wip: bump bounds --- rel8-internal/rel8-internal.cabal | 6 +- rel8-internal/src/Rel8/Internal/Statement.hs | 25 ++++ .../src/Rel8/Internal/Statement/Prepared.hs | 110 ++++++++++++++---- .../src/Rel8/Internal/Statement/Run.hs | 9 +- .../src/Rel8/Internal/Statement/View.hs | 10 +- .../src/Rel8/Internal/Type/Composite.hs | 20 +++- rel8-internal/src/Rel8/Internal/Type/Enum.hs | 26 ++++- rel8/rel8.cabal | 2 +- rel8/src/Rel8.hs | 7 +- rel8/tests/Main.hs | 55 ++++++--- 10 files changed, 216 insertions(+), 54 deletions(-) diff --git a/rel8-internal/rel8-internal.cabal b/rel8-internal/rel8-internal.cabal index e21d751c..45d5feec 100644 --- a/rel8-internal/rel8-internal.cabal +++ b/rel8-internal/rel8-internal.cabal @@ -21,16 +21,16 @@ library , attoparsec , base >= 4.16 && < 4.23 , base16 >= 1.0 - , base-compat >= 0.11 && < 0.15 + , base-compat >= 0.11 && < 0.16 , bytestring , case-insensitive , comonad , containers , contravariant - , hasql >= 1.8 && < 1.10 + , hasql >= 1.8 && < 1.10.4 , iproute ^>= 1.7 , opaleye ^>= 0.10.2.1 - , postgresql-binary ^>= 0.14.2 + , postgresql-binary ^>= 0.15.0 , pretty , profunctors , product-profunctors diff --git a/rel8-internal/src/Rel8/Internal/Statement.hs b/rel8-internal/src/Rel8/Internal/Statement.hs index 32e3f3e8..a1118b7d 100644 --- a/rel8-internal/src/Rel8/Internal/Statement.hs +++ b/rel8-internal/src/Rel8/Internal/Statement.hs @@ -10,12 +10,14 @@ {-# language StandaloneKindSignatures #-} {-# language TypeAbstractions #-} {-# language TypeApplications #-} +{-# language CPP #-} module Rel8.Internal.Statement ( Statement , statementReturning , statementNoReturning , ppDecodeStatement + , encodeDoc ) where @@ -30,6 +32,9 @@ import Data.Monoid (Endo (Endo)) import Data.String (fromString) import Prelude +-- bytestring +import Data.ByteString (ByteString) + -- hasql import qualified Hasql.Decoders as Hasql @@ -67,12 +72,17 @@ import Rel8.Internal.Table.Serialize (parse) import Data.Functor.Apply (Apply, WrappedApplicative (..)) import Data.Functor.Bind (Bind, (>>-)) +-- text +import qualified Data.Text as Text +import Data.Text.Encoding (encodeUtf8) + -- transformers import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.State.Strict (State, evalState) import Control.Monad.Trans.Writer.CPS (WriterT, runWriterT, tell) + type Binding :: Type data Binding = Binding { relation :: !String @@ -286,6 +296,20 @@ ppDecodeStatement ppSelect rows (Statement m) = evalState go Opaleye.start doc <- ppWith bindings' <$> ppSelect query pure (doc, Hasql.rowVector (parse @exprs @a)) +-- | Encode a document into the text type that Hasql expects +#if MIN_VERSION_hasql(1,10,0) +encodeDoc :: Doc -> Text.Text +#else +encodeDoc :: Doc -> ByteString +#endif +encodeDoc doc = bytes + where + bytes = +#if !MIN_VERSION_hasql(1,10,0) + encodeUtf8 $ +#endif + Text.pack sql + sql = show doc ppWith :: [Binding] -> Doc -> Doc ppWith bindings after = pre $$ after @@ -311,3 +335,4 @@ ppAlias Binding {relation, columns = mcolumns} = case mcolumns of unsnoc :: [a] -> Maybe ([a], a) unsnoc = foldr (\x -> Just . maybe ([], x) (\(~(a, b)) -> (x : a, b))) Nothing + diff --git a/rel8-internal/src/Rel8/Internal/Statement/Prepared.hs b/rel8-internal/src/Rel8/Internal/Statement/Prepared.hs index 848614e2..40d73eae 100644 --- a/rel8-internal/src/Rel8/Internal/Statement/Prepared.hs +++ b/rel8-internal/src/Rel8/Internal/Statement/Prepared.hs @@ -5,10 +5,18 @@ {-# language NamedFieldPuns #-} {-# language ScopedTypeVariables #-} {-# language TypeApplications #-} +{-# language CPP #-} +{-# language ScopedTypeVariables #-} +{-# language DataKinds #-} -module Rel8.Internal.Statement.Prepared ( - input, - prepared, +module Rel8.Internal.Statement.Prepared + ( input + , preparedRun_ + , preparedRunN + , preparedRun1 + , preparedRunMaybe + , preparedRun + , preparedRunVector ) where -- base @@ -16,6 +24,7 @@ import Data.Functor.Const (Const (Const), getConst) import Data.Functor.Contravariant (contramap, (>$<)) import Data.Functor.Identity (runIdentity) import Prelude +import Data.Int (Int64) -- hasql import qualified Hasql.Encoders as Hasql @@ -25,6 +34,7 @@ import qualified Hasql.Statement as Hasql import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye -- rel8 +import Rel8.Internal.Query (Query) import Rel8.Internal.Expr (Expr) import Rel8.Internal.Expr.Opaleye (fromPrimExpr, scastExpr) import Rel8.Internal.Schema.HTable (hfield, hspecs, htabulateA) @@ -35,29 +45,24 @@ import Rel8.Internal.Table (Table, fromColumns, toResult) import Rel8.Internal.Table.Serialize (Serializable) import Rel8.Internal.Type.Encoder (binary) import Rel8.Internal.Type.Information (encode) +import Rel8.Internal.Statement.Rows (Rows (..)) +import Rel8.Internal.Statement (Statement, ppDecodeStatement, encodeDoc) +import Rel8.Internal.Statement.Select (ppSelect) + +-- text +import qualified Data.Text as Text -- transformers import Control.Monad.Trans.State.Strict (evalState, state) +-- vector +import Data.Vector (Vector) -{-| Given a 'Rel8.run' function that converts a 'Statement' to a -'Hasql.Statement', return a 'Rel8.run'-like function which instead takes a -/parameterized/ 'Statement' and converts it to a /preparable/ -'Hasql.Statement'. - -The parameters @i@ are sent to the database directly via PostgreSQL's binary -format. For large amounts of data this can be significantly more efficient -than embedding the values in the statement with 'Rel8.Internal.lit'. --} -prepared :: forall a b i o. - Serializable a i => - (Statement b -> Hasql.Statement () o) -> - (a -> Statement b) -> - Hasql.Statement i o -prepared run mkStatement = Hasql.Statement sql (encoder @a) decode True +makePreparedRun :: forall a exprs params i. (Serializable params i) => Rows exprs a -> (params -> Statement exprs) -> Hasql.Statement i a +makePreparedRun rows statement = Hasql.preparable bytes (encoder @params) decode where - Hasql.Statement sql _ decode _ = run $ mkStatement input - + bytes = encodeDoc doc + (doc, decode) = ppDecodeStatement ppSelect rows (statement input) encoder :: forall a i. Serializable a i => Hasql.Params i encoder = @@ -85,3 +90,68 @@ input = Spec {info} -> scastExpr info $ fromPrimExpr $ Opaleye.ConstExpr $ Opaleye.OtherLit $ '$' : show n + +-- | Convert a 'Statement' to a prepared runnable 'Hasql.Statement', disregarding the +-- results of that statement (if any). +-- +-- @ +-- preparedRun_ :: (Serializable params i) => (params -> Rel8.'Statement' exprs) -> Hasql.'Hasql.Statement' i () +-- @ +preparedRun_ :: (Serializable params i) => (params -> Statement exprs) -> Hasql.Statement i () +preparedRun_ = makePreparedRun Void + + +-- | Convert a 'Statement' to a prepared runnable 'Hasql.Statement', returning the +-- number of rows affected by that statement (for 'Rel8.insert's, +-- 'Rel8.update's or Rel8.delete's with 'Rel8.NoReturning'). +-- +-- @ +-- preparedRunN :: (Serializable params i) => (params -> Rel8.'Statement' ()) -> Hasql.'Hasql.Statement' i Int64 +-- @ +preparedRunN :: (Serializable params i) => (params -> Statement ()) -> Hasql.Statement i Int64 +preparedRunN = makePreparedRun RowsAffected + + +-- | Convert a 'Statement' to a prepared runnable 'Hasql.Statement', processing the +-- result of the statement as a single row. If the statement returns a number +-- of rows other than 1, a preparedRuntime exception is thrown. +-- +-- @ +-- preparedRun1 ::(Serializable params i, Serializable exprs a) => (params -> Rel8.'Statement' (Query exprs)) -> Hasql.'Hasql.Statement' i a +-- @ +preparedRun1 ::(Serializable params i, Serializable exprs a) => (params -> Statement (Query exprs)) -> Hasql.Statement i a +preparedRun1 = makePreparedRun Single + + +-- | Convert a 'Statement' to a prepared runnable 'Hasql.Statement', processing the +-- result of the statement as 'Maybe' a single row. If the statement returns +-- a number of rows other than 0 or 1, a preparedRuntime exception is thrown. +-- +-- @ +-- preparedRunMaybe :: (Serializable params i, Serializable exprs a) => (params -> Rel8.'Statement' (Query exprs)) -> Hasql.'Hasql.Statement' i (Maybe a) +-- @ +preparedRunMaybe :: (Serializable params i, Serializable exprs a) + => (params -> Statement (Query exprs)) -> Hasql.Statement i (Maybe a) +preparedRunMaybe = makePreparedRun Maybe + + +-- | Convert a 'Statement' to a prepared runnable 'Hasql.Statement', processing the +-- result of the statement as a list of rows. +-- +-- @ +-- preparedRun :: (Serializable params i, Serializable exprs a) => (params -> Rel8.'Statement' (Query exprs)) -> Hasql.'Hasql.Statement' i [a] +-- @ +preparedRun :: (Serializable params i, Serializable exprs a) + => (params -> Statement (Query exprs)) -> Hasql.Statement i [a] +preparedRun = makePreparedRun List + + +-- | Convert a 'Statement' to a prepared runnable 'Hasql.Statement', processing the +-- result of the statement as a 'Vector' of rows. +-- +-- @ +-- preparedRunVector :: (Serializable params i, Serializable exprs a) => (params -> Rel8.'Statement' (Query exprs)) -> Hasql.'Hasql.Statement' i (Vector a) +-- @ +preparedRunVector :: (Serializable params i, Serializable exprs a) + => (params -> Statement (Query exprs)) -> Hasql.Statement i (Vector a) +preparedRunVector = makePreparedRun Vector diff --git a/rel8-internal/src/Rel8/Internal/Statement/Run.hs b/rel8-internal/src/Rel8/Internal/Statement/Run.hs index a9328b0a..eb06ecea 100644 --- a/rel8-internal/src/Rel8/Internal/Statement/Run.hs +++ b/rel8-internal/src/Rel8/Internal/Statement/Run.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} module Rel8.Internal.Statement.Run ( run_ , runN @@ -18,7 +19,7 @@ import qualified Hasql.Statement as Hasql -- rel8 import Rel8.Internal.Query (Query) -import Rel8.Internal.Statement (Statement, ppDecodeStatement) +import Rel8.Internal.Statement (Statement, ppDecodeStatement, encodeDoc) import Rel8.Internal.Statement.Rows (Rows (..)) import Rel8.Internal.Statement.Select (ppSelect) import Rel8.Internal.Table.Serialize (Serializable) @@ -32,12 +33,10 @@ import Data.Vector (Vector) makeRun :: Rows exprs a -> Statement exprs -> Hasql.Statement () a -makeRun rows statement = Hasql.Statement bytes params decode prepare +makeRun rows statement = Hasql.unpreparable bytes params decode where - bytes = encodeUtf8 $ Text.pack sql + bytes = encodeDoc doc params = Hasql.noParams - prepare = False - sql = show doc (doc, decode) = ppDecodeStatement ppSelect rows statement diff --git a/rel8-internal/src/Rel8/Internal/Statement/View.hs b/rel8-internal/src/Rel8/Internal/Statement/View.hs index 38525bf5..f48ead57 100644 --- a/rel8-internal/src/Rel8/Internal/Statement/View.hs +++ b/rel8-internal/src/Rel8/Internal/Statement/View.hs @@ -1,5 +1,6 @@ {-# language FlexibleContexts #-} {-# language MonoLocalBinds #-} +{-# language CPP #-} module Rel8.Internal.Statement.View ( createView @@ -62,12 +63,15 @@ createOrReplaceView = createViewGeneric :: Selects names exprs => CreateView -> TableSchema names -> Query exprs -> Hasql.Statement () () createViewGeneric replace schema query = - Hasql.Statement bytes params decode prepare + Hasql.unpreparable bytes params decode where - bytes = encodeUtf8 (Text.pack sql) + bytes = +#if !MIN_VERSION_hasql(1,10,0) + encodeUtf8 $ +#endif + Text.pack sql params = Hasql.noParams decode = Hasql.noResult - prepare = False sql = show doc doc = ppCreateView schema query replace diff --git a/rel8-internal/src/Rel8/Internal/Type/Composite.hs b/rel8-internal/src/Rel8/Internal/Type/Composite.hs index d819302f..3f96ee82 100644 --- a/rel8-internal/src/Rel8/Internal/Type/Composite.hs +++ b/rel8-internal/src/Rel8/Internal/Type/Composite.hs @@ -13,6 +13,8 @@ {-# language UndecidableInstances #-} {-# language UndecidableSuperClasses #-} {-# language ViewPatterns #-} +{-# language CPP #-} +{-# language OverloadedRecordDot #-} module Rel8.Internal.Type.Composite ( Composite( Composite ) @@ -56,6 +58,7 @@ import Rel8.Internal.Schema.HTable ( HTable, hfield, hspecs, htabulate, htabulat import Rel8.Internal.Schema.Name ( Name( Name ) ) import Rel8.Internal.Schema.Null ( Nullity( Null, NotNull ) ) import Rel8.Internal.Schema.QualifiedName (QualifiedName) +import qualified Rel8.Internal.Schema.QualifiedName import Rel8.Internal.Schema.Result ( Result ) import Rel8.Internal.Schema.Spec ( Spec( Spec, nullity, info ) ) import Rel8.Internal.Table ( fromColumns, toColumns, fromResult, toResult ) @@ -79,6 +82,9 @@ import Rel8.Internal.Type.Parser (parse) -- semigroupoids import Data.Functor.Apply ( WrappedApplicative(..) ) +-- text +import qualified Data.Text as Text + -- transformers import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.State.Strict (StateT (StateT), runStateT) @@ -101,12 +107,22 @@ instance DBComposite a => DBType (Composite a) where typeInformation = TypeInformation { decode = Decoder - { binary = Decoders.composite (Composite . fromResult @_ @(HKD a Expr) <$> decoder) + { binary = Decoders.composite +#if MIN_VERSION_hasql(1,10,0) + (Text.pack <$> (compositeTypeName @a).schema) + (Text.pack (compositeTypeName @a).name) +#endif + (Composite . fromResult @_ @(HKD a Expr) <$> decoder) , text = fmap (Composite . fromResult @_ @(HKD a Expr)) . parser } , encode = Encoder - { binary = Encoders.composite (toResult @_ @(HKD a Expr) . unComposite >$< encoder) + { binary = Encoders.composite +#if MIN_VERSION_hasql(1,10,0) + (Text.pack <$> (compositeTypeName @a).schema) + (Text.pack (compositeTypeName @a).name) +#endif + (toResult @_ @(HKD a Expr) . unComposite >$< encoder) , text = builder . toResult @_ @(HKD a Expr) . unComposite , quote = quoter . litHTable . toResult @_ @(HKD a Expr) . unComposite } diff --git a/rel8-internal/src/Rel8/Internal/Type/Enum.hs b/rel8-internal/src/Rel8/Internal/Type/Enum.hs index 0eee176a..51aade13 100644 --- a/rel8-internal/src/Rel8/Internal/Type/Enum.hs +++ b/rel8-internal/src/Rel8/Internal/Type/Enum.hs @@ -12,6 +12,8 @@ {-# language TypeFamilies #-} {-# language TypeOperators #-} {-# language UndecidableInstances #-} +{-# language CPP #-} +{-# language OverloadedRecordDot #-} module Rel8.Internal.Type.Enum ( Enum( Enum ) @@ -41,7 +43,7 @@ import qualified Hasql.Encoders as Encoders import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye -- rel8 -import Rel8.Internal.Schema.QualifiedName (QualifiedName) +import Rel8.Internal.Schema.QualifiedName (QualifiedName(schema, name)) import Rel8.Internal.Type ( DBType, typeInformation ) import Rel8.Internal.Type.Decoder (Decoder (..)) import Rel8.Internal.Type.Encoder (Encoder (..)) @@ -76,9 +78,18 @@ instance DBEnum a => DBType (Enum a) where { encode = let toText (Enum a) = pack $ enumValue a +#if MIN_VERSION_hasql(1,10,0) + schemaName = pack <$> (enumTypeName @a).schema + enumName = pack (enumTypeName @a).name +#endif in Encoder - { binary = Encoders.enum toText + { binary = Encoders.enum +#if MIN_VERSION_hasql(1,10,0) + schemaName + enumName +#endif + toText , text = encodeUtf8Builder . toText , quote = Opaleye.ConstExpr . @@ -90,9 +101,18 @@ instance DBEnum a => DBType (Enum a) where let mapping = (pack . enumValue &&& Enum) <$> enumerate unrecognised = Left "enum: unrecognised value" +#if MIN_VERSION_hasql(1,10,0) + schemaName = pack <$> (enumTypeName @a).schema + enumName = pack (enumTypeName @a).name +#endif in Decoder - { binary = Decoders.enum (`lookup` mapping) + { binary = Decoders.enum +#if MIN_VERSION_hasql(1,10,0) + schemaName + enumName +#endif + (`lookup` mapping) , text = maybe unrecognised pure . (`lookup` mapping) . decodeUtf8 } , delimiter = ',' diff --git a/rel8/rel8.cabal b/rel8/rel8.cabal index 54d15473..782ed3af 100644 --- a/rel8/rel8.cabal +++ b/rel8/rel8.cabal @@ -66,7 +66,7 @@ test-suite tests , containers , hasql , hasql-transaction - , hedgehog >= 1.0 && < 1.6 + , hedgehog >= 1.0 && < 1.8 , mmorph , iproute , rel8 diff --git a/rel8/src/Rel8.hs b/rel8/src/Rel8.hs index cf83d20e..e91b14c5 100644 --- a/rel8/src/Rel8.hs +++ b/rel8/src/Rel8.hs @@ -352,7 +352,12 @@ module Rel8 , run1 , runMaybe , runVector - , prepared + , preparedRun + , preparedRun_ + , preparedRunN + , preparedRun1 + , preparedRunMaybe + , preparedRunVector -- ** @SELECT@ , select diff --git a/rel8/tests/Main.hs b/rel8/tests/Main.hs index 88fc656f..7eaca16f 100644 --- a/rel8/tests/Main.hs +++ b/rel8/tests/Main.hs @@ -27,7 +27,7 @@ import qualified Data.Aeson.KeyMap as Aeson.KeyMap -- base import Control.Applicative ( empty, liftA2, liftA3 ) -import Control.Exception ( bracket, throwIO ) +import Control.Exception ( Exception, bracket, throwIO ) import Control.Monad ((>=>)) import Data.Bifunctor ( bimap ) import Data.Fixed (Fixed (MkFixed)) @@ -55,12 +55,19 @@ import Data.Containers.ListUtils ( nubOrdOn ) import qualified Data.Map.Strict as Map -- hasql -import Hasql.Connection ( Connection, ConnectionError, acquire, release ) -#if MIN_VERSION_hasql(1,9,0) +import Hasql.Connection ( Connection, acquire, release, use ) +import Hasql.Errors ( SessionError, ConnectionError ) +#if MIN_VERSION_hasql(1,10,0) +import qualified Hasql.Connection.Settings as Hasql.Connection.Setting +#elif MIN_VERSION_hasql(1,9,0) import qualified Hasql.Connection.Setting import qualified Hasql.Connection.Setting.Connection #endif -import Hasql.Session ( sql, run ) +#if MIN_VERSION_hasql(1,10,0) +import Hasql.Session ( script ) +#else +import Hasql.Session ( sql ) +#endif -- hasql-transaction import Hasql.Transaction ( Transaction, condemn, statement ) @@ -168,12 +175,12 @@ tests = db <- TmpPostgres.start >>= either throwIO return bracket (either (error . show) return =<< acquireFromConnectionString (TmpPostgres.toConnectionString db)) release \conn -> void do - flip run conn do - sql "CREATE EXTENSION citext" - sql "CREATE TABLE test_table ( column1 text not null, column2 bool not null )" - sql "CREATE TABLE unique_table ( \"key\" text not null unique, \"value\" text not null )" - sql "CREATE SEQUENCE test_seq" - sql "CREATE TYPE composite AS (\"bool\" bool, \"char\" text, \"array\" int4[])" + use conn do + hasqlSql "CREATE EXTENSION citext" + hasqlSql "CREATE TABLE test_table ( column1 text not null, column2 bool not null )" + hasqlSql "CREATE TABLE unique_table ( \"key\" text not null unique, \"value\" text not null )" + hasqlSql "CREATE SEQUENCE test_seq" + hasqlSql "CREATE TYPE composite AS (\"bool\" bool, \"char\" text, \"array\" int4[])" return db @@ -181,11 +188,18 @@ tests = connect :: TmpPostgres.DB -> IO Connection +#if MIN_VERSION_hasql(1,10,0) +connect = acquireFromConnectionString . TmpPostgres.toConnectionString >=> either (fail . show) pure +#else connect = acquireFromConnectionString . TmpPostgres.toConnectionString >=> either (maybe empty (fail . unpack . decodeUtf8)) pure +#endif acquireFromConnectionString :: ByteString -> IO (Either ConnectionError Connection) acquireFromConnectionString connectionString = -#if MIN_VERSION_hasql(1,9,0) +#if MIN_VERSION_hasql(1,10,0) + acquire + (Hasql.Connection.Setting.connectionString . decodeUtf8 $ connectionString) +#elif MIN_VERSION_hasql(1,9,0) acquire [ Hasql.Connection.Setting.connection . Hasql.Connection.Setting.Connection.string . decodeUtf8 $ connectionString ] @@ -193,6 +207,12 @@ acquireFromConnectionString connectionString = acquire connectionString #endif +#if MIN_VERSION_hasql(1,10,0) +hasqlSql = script +#else +hasqlSql = sql +#endif + testShowCreateTable :: IO TmpPostgres.DB -> TestTree testShowCreateTable getTestDatabase = testGroup "CREATE TABLE" [ testTypeChecker "tableTest" Rel8able.tableTest Rel8able.genTableTest getTestDatabase @@ -293,6 +313,9 @@ testShowCreateTable getTestDatabase = testGroup "CREATE TABLE" -- so we this is only here as one additional check length selected === length rows +#if MIN_VERSION_hasql(1,10,0) +instance Exception Hasql.Errors.SessionError +#endif databasePropertyTest :: TestName @@ -303,7 +326,7 @@ databasePropertyTest testName f getTestDatabase = testProperty testName $ property do connection <- lift c f $ test . hoist \m -> do - e <- run (Hasql.transaction Hasql.Serializable Hasql.Write (m <* condemn)) connection + e <- use connection (Hasql.transaction Hasql.Serializable Hasql.Write (m <* condemn)) either throwIO pure e @@ -649,25 +672,25 @@ testDBType getTestDatabase = testGroup "DBType instances" transaction do res <- lift do - statement x $ Rel8.prepared Rel8.run1 $ + statement x $ Rel8.preparedRun1 $ Rel8.select @(Rel8.Expr _) . pure diff res (==) x res' <- lift do - statement [x, y] $ Rel8.prepared Rel8.run1 $ + statement [x, y] $ Rel8.preparedRun1 $ Rel8.select @(Rel8.ListTable Rel8.Expr (Rel8.Expr _)) . Rel8.many . Rel8.catListTable diff res' (==) [x, y] res'' <- lift do - statement [[x, y]] $ Rel8.prepared Rel8.run1 $ + statement [[x, y]] $ Rel8.preparedRun1 $ Rel8.select @(Rel8.ListTable Rel8.Expr (Rel8.ListTable Rel8.Expr (Rel8.Expr _))) . Rel8.many . Rel8.many . (Rel8.catListTable >=> Rel8.catListTable) diff res'' (==) [[x, y]] res''' <- lift do - statement [[[x, y]]] $ Rel8.prepared Rel8.run1 $ + statement [[[x, y]]] $ Rel8.preparedRun1 $ Rel8.select @(Rel8.ListTable Rel8.Expr (Rel8.ListTable Rel8.Expr (Rel8.ListTable Rel8.Expr (Rel8.Expr _)))) . Rel8.many . Rel8.many . Rel8.many . (Rel8.catListTable >=> Rel8.catListTable >=> Rel8.catListTable) diff res''' (==) [[[x, y]]]