diff --git a/.ghci b/.ghci new file mode 100644 index 00000000..a48e262f --- /dev/null +++ b/.ghci @@ -0,0 +1 @@ +:set -XDeriveAnyClass -XDeriveGeneric -XTemplateHaskell diff --git a/bare_shell.nix b/bare_shell.nix new file mode 100644 index 00000000..e6efd0cf --- /dev/null +++ b/bare_shell.nix @@ -0,0 +1,4 @@ +let pkgs = (builtins.getFlake "nixpkgs").legacyPackages.x86_64-linux; +in + pkgs.mkShell { buildInputs = with pkgs; [ghc cabal-install postgresql postgresql.dev zlib + pkg-config];} diff --git a/cabal.project b/cabal.project index 9c5314c1..21e0d893 100644 --- a/cabal.project +++ b/cabal.project @@ -7,3 +7,5 @@ source-repository-package allow-newer: base16:base, base16:deepseq, base16:text allow-newer: *:base, *:template-haskell, *:ghc-prim + +tests: true diff --git a/notes b/notes new file mode 100644 index 00000000..39a89c3f --- /dev/null +++ b/notes @@ -0,0 +1,52 @@ +standing for ‘rel8-1.7.0.0:Rel8.Schema.HTable.Product.HProduct + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "foo" (rel8-1.7.0.0:Rel8.Schema.HTable.Identity.HIdentity Bool)) + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "bars" + (rel8-1.7.0.0:Rel8.Schema.HTable.Product.HProduct + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "_1" (rel8-1.7.0.0:Rel8.Schema.HTable.Identity.HIdentity Text)) + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "_2" (rel8-1.7.0.0:Rel8.Schema.HTable.Identity.HIdentity Text)))) + Expr +rel8-1.7.0.0:Rel8.Schema.HTable.Product.HProduct + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "foo" (rel8-1.7.0.0:Rel8.Schema.HTable.Identity.HIdentity Bool)) + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "bars" + (rel8-1.7.0.0:Rel8.Schema.HTable.Product.HProduct + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "_1" (rel8-1.7.0.0:Rel8.Schema.HTable.Identity.HIdentity Text)) + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "_2" (rel8-1.7.0.0:Rel8.Schema.HTable.Identity.HIdentity Text)))) + Expr + • Found type wildcard ‘_’ + standing for ‘rel8-1.7.0.0:Rel8.Kind.Context.SContext Identity + -> rel8-1.7.0.0:Rel8.Schema.HTable.Product.HProduct + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "foo" (rel8-1.7.0.0:Rel8.Schema.HTable.Identity.HIdentity Bool)) + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "bars" + (rel8-1.7.0.0:Rel8.Schema.HTable.Product.HProduct + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "_1" (rel8-1.7.0.0:Rel8.Schema.HTable.Identity.HIdentity Text)) + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "_2" + (rel8-1.7.0.0:Rel8.Schema.HTable.Identity.HIdentity Text)))) + Identity + -> TablePair Identity’ + • Found type wildcard ‘_’ + standing for ‘rel8-1.7.0.0:Rel8.Kind.Context.SContext Identity + -> rel8-1.7.0.0:Rel8.Schema.HTable.Product.HProduct + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "foo" (rel8-1.7.0.0:Rel8.Schema.HTable.Identity.HIdentity Bool)) + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "bars" + (rel8-1.7.0.0:Rel8.Schema.HTable.Product.HProduct + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "_1" (rel8-1.7.0.0:Rel8.Schema.HTable.Identity.HIdentity Text)) + (rel8-1.7.0.0:Rel8.Schema.HTable.Label.HLabel + "_2" + (rel8-1.7.0.0:Rel8.Schema.HTable.Identity.HIdentity Text)))) + Identity + -> TablePair Identity’ diff --git a/rel8.cabal b/rel8.cabal index 2703f99f..058e421c 100644 --- a/rel8.cabal +++ b/rel8.cabal @@ -39,6 +39,8 @@ library , scientific , semialign , semigroupoids + , template-haskell + , th-abstraction , text , these , time @@ -72,6 +74,8 @@ library Rel8.Expr.Time Rel8.Table.Verify Rel8.Tabulate + Rel8.TH + Rel8.Generic.Rel8able other-modules: Rel8.Aggregate @@ -118,7 +122,6 @@ library Rel8.Generic.Construction.Record Rel8.Generic.Map Rel8.Generic.Record - Rel8.Generic.Rel8able Rel8.Generic.Table Rel8.Generic.Table.ADT Rel8.Generic.Table.Record diff --git a/result b/result new file mode 100644 index 00000000..8236b477 --- /dev/null +++ b/result @@ -0,0 +1 @@ +/nix/store/z7sskjfby4bwgamzqyx51nlzchzdszmb-ghc-shell-for-packages \ No newline at end of file diff --git a/src/Rel8/Generic/Rel8able.hs b/src/Rel8/Generic/Rel8able.hs index 365c17cb..b677c1df 100644 --- a/src/Rel8/Generic/Rel8able.hs +++ b/src/Rel8/Generic/Rel8able.hs @@ -16,12 +16,11 @@ {-# language UndecidableInstances #-} module Rel8.Generic.Rel8able - ( KRel8able, Rel8able + ( KRel8able, Rel8able(..) , Algebra , GRep - , GColumns, gfromColumns, gtoColumns - , GFromExprs, gfromResult, gtoResult , TSerialize, serialize, deserialize + , GColumns ) where diff --git a/src/Rel8/Schema/HTable/Label.hs b/src/Rel8/Schema/HTable/Label.hs index 43c1843f..1eac6848 100644 --- a/src/Rel8/Schema/HTable/Label.hs +++ b/src/Rel8/Schema/HTable/Label.hs @@ -7,7 +7,7 @@ {-# language TypeFamilies #-} module Rel8.Schema.HTable.Label - ( HLabel, hlabel, hrelabel, hunlabel + ( HLabel(HLabel), hlabel, hrelabel, hunlabel , hproject ) where diff --git a/src/Rel8/TH.hs b/src/Rel8/TH.hs new file mode 100644 index 00000000..d0b969e8 --- /dev/null +++ b/src/Rel8/TH.hs @@ -0,0 +1,254 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +module Rel8.TH (deriveRel8able, parseDatatype) where + +import Prelude +import Language.Haskell.TH (Q) +import qualified Language.Haskell.TH as TH +import qualified Language.Haskell.TH.Syntax as TH +import Language.Haskell.TH.Datatype (reifyDatatype, ConstructorInfo (ConstructorInfo), DatatypeInfo (..), datatypeCons, constructorFields, ConstructorVariant (RecordConstructor), constructorVariant, constructorName, datatypeVars) +import qualified Language.Haskell.TH.Datatype as TH.Datatype +import Rel8.Generic.Rel8able ( Rel8able(..) ,serialize, deserialize) +import Rel8.Schema.Result (Result) +import Data.Foldable (foldl', toList ) +import Rel8.Schema.HTable.Identity (HIdentity(HIdentity)) +import Rel8.Schema.HTable.Product (HProduct(HProduct)) +import Data.Traversable (for) +import Data.Functor.Identity (Identity(Identity), runIdentity) +import Rel8.Kind.Context (SContext(..)) +import Data.Functor ( (<&>) ) +import Data.List.NonEmpty ( NonEmpty( (:|) ) ) +import Rel8.Column ( Column ) +import Rel8.Column.Maybe ( HMaybe ) +import Rel8.Schema.HTable.Maybe ( HMaybeTable ) +import Rel8.Expr ( Expr ) +import Rel8.Table (Table, Columns, toColumns, fromColumns, fromResult, toResult, FromExprs) +import Rel8.Schema.Kind (Context) +import Data.List (unsnoc) +import Debug.Trace +import Rel8.Schema.HTable.Label (HLabel(..)) +import Data.Data (constrFields) +import Data.Aeson (parseIndexedJSON) +import Data.Proxy +import qualified Data.Map.Strict as M + + +-- We derive a Rel8able instance using TH. +-- At it's core a Rel8able instance is a bijection between a datatype and the the SQL columns corresponding to its fields. +-- We only support datatypes with one constructor. +-- The datatype must have exactly one type arg and it is the index for our HKD stuff. +-- Question: Can we support multiple type args? +--- +-- We have three types of fields: +-- 1) Column f Text : Directly using Column, easy. This is just a special case of (3) +-- 2) OtherType f : They embed another Rel8able type +-- 3) TabledType : They embed a type with a table instance. +-- eg, we might see something like (Column f Text, Column f Bool). (,) has a Table instance, +-- so we know how to map this type to SQL columns. +-- +-- We represent a vector of SQL columns with basically: +-- HLabel "field label" (HIdentity Text) `HProduct` HLabel "another field" (HIdentity Bool) ... +-- Nothing too complicated here. I'm not sure if we are allowed to leave the HLabels out or if that will cause everything to explode. +-- This H* stuff is also used to thread around contexts if you look at the definitions of these things + +data ParsedDatatype = + ParsedDatatype + { name :: TH.Name + , conName :: TH.Name + , fBinder :: TH.Name + , fields :: [ParsedField] + } + deriving (Show) + +data ParsedField = + ParsedField + { fieldSelector :: Maybe TH.Name + , fieldVariant :: ParsedFieldVariant + , fieldType :: TH.Type + , fieldColumnType :: TH.Type + , fieldFreshName :: TH.Name + } + deriving (Show) + +data ParsedFieldVariant = + ColumnField + | Rel8ableField -- TODO rename to table field + deriving (Show) + +-- | 'fail' but indicate that the failure is coming from our code +prettyFail :: String -> Q a +prettyFail str = fail $ "deriveRel8able: " ++ str + +parseDatatype :: DatatypeInfo -> Q ParsedDatatype +parseDatatype datatypeInfo = do + constructor <- + -- Check that it only has one constructor + case datatypeCons datatypeInfo of + [cons] -> pure cons + _ -> prettyFail "exepecting a datatype with exactly 1 constructor" + let conName = TH.Datatype.constructorName constructor + let name = datatypeName datatypeInfo + fBinder <- case unsnoc $ datatypeInstTypes datatypeInfo of + Just (_, candidate) -> parseFBinder candidate + Nothing -> prettyFail "expecting the datatype to have a context type parameter like `data Foo f = ...`" + let fieldSelectors = case constructorVariant constructor of + -- Only record constructors have field names + RecordConstructor names -> map Just names + _ -> repeat Nothing + let columnName = ''Column + fields <- + mapM (uncurry $ parseField columnName fBinder) $ + zip (constructorFields constructor) fieldSelectors + -- TODO: check that we have at least one field, fail otherwise + pure ParsedDatatype{..} + +parseFBinder :: TH.Type -> Q TH.Name +parseFBinder (TH.SigT x (TH.ConT kind)) + | kind == ''Context = parseFBinder x + | otherwise = prettyFail $ "expected kind encountered for the context type argument: " ++ show kind +parseFBinder (TH.VarT name) = pure name +parseFBinder typ = prettyFail $ "unexpected type encountered while looking for the context type argument to the datatype: " ++ show typ + +typeApps :: TH.Type -> [TH.Type] +typeApps x = go x [] + where + go (TH.AppT x y) args = go x (y:args) + go x args = x:args + +unTypeApps :: TH.Type -> [TH.Type] -> TH.Type +unTypeApps = foldl' TH.AppT + +parseField :: TH.Name -> TH.Name -> TH.Type -> Maybe TH.Name -> Q ParsedField +parseField columnName fBinder fieldType fieldSelector + | (TH.ConT columnCandidate `TH.AppT` TH.VarT fBinderCandidate `TH.AppT` subType) <- fieldType + , columnCandidate == columnName + , fBinderCandidate == fBinder + = do + n <- TH.newName "x" + pure $ ParsedField { fieldSelector = fieldSelector, fieldVariant = ColumnField, fieldType = subType, fieldColumnType = TH.ConT ''HIdentity `TH.AppT` subType, fieldFreshName = n} + -- | (TH.ConT hmaybeCandidate `TH.AppT` TH.VarT fBinderCandidate `TH.AppT` subType) <- fieldType + -- , hmaybeCandidate == ''HMaybe + -- , fBinderCandidate == fBinder + -- = do + -- n <- TH.newName "x" + -- innerType <- [t| Columns $(pure subType)|] + -- let columnType = TH.Datatype.applySubstitution (M.fromList [(fBinder, TH.ConT ''Expr)]) $ TH.ConT ''HMaybeTable `TH.AppT` innerType + -- pure $ ParsedField { fieldSelector = fieldSelector, fieldVariant = Rel8ableField, fieldType = subType, fieldColumnType = columnType, fieldFreshName = n} + -- | subType:(TH.VarT name):other_apps <- typeApps fieldType + -- , name == fBinder + -- = do + -- traceShowM (subType:(TH.VarT name):other_apps) + -- n <- TH.newName "x" + -- columnType <- [t|Columns ($(pure $ TH.Datatype.applySubstitution (M.fromList [(fBinder, TH.ConT ''Expr)])$ unTypeApps subType ((TH.ConT ''Expr):other_apps))) |] + -- traceM $ TH.pprint columnType + -- pure $ ParsedField { fieldSelector = fieldSelector, fieldVariant = Rel8ableField, fieldType = subType, fieldColumnType = columnType, fieldFreshName = n} + | otherwise + = do + traceShowM fieldType + n <- TH.newName "x" + columnType <- [t|Columns ($(pure $ TH.Datatype.applySubstitution (M.fromList [(fBinder, TH.ConT ''Expr)]) $ fieldType)) |] + ft2 <- [t|($(pure $ TH.Datatype.applySubstitution (M.fromList [(fBinder, TH.ConT ''Expr)]) $ fieldType)) |] + traceM $ TH.pprint columnType + pure $ ParsedField { fieldSelector = fieldSelector, fieldVariant = Rel8ableField, fieldType = ft2, fieldColumnType = columnType, fieldFreshName = n} + | otherwise = prettyFail $ "Field of unexpected type: " ++ show fieldType ++ show (typeApps fieldType) + +generateGColumns :: ParsedDatatype -> Q TH.Type +generateGColumns ParsedDatatype{..} = + foldr1 (\x y -> [t|HProduct $x $y|]) $ map generateGColumn fields + where + generateGColumn ParsedField{..} = + [t| $(pure fieldColumnType)|] + >>= labelled fieldSelector + labelled Nothing x = pure x + labelled (Just (TH.Name (TH.OccName fieldSelector) _)) x = [t|HLabel $(TH.litT $ TH.strTyLit fieldSelector) $(pure x)|] + +generateColumnsE :: ParsedDatatype -> (TH.Exp -> TH.Exp) -> (TH.Type -> TH.Exp -> TH.Exp) -> TH.Exp +generateColumnsE ParsedDatatype{..} f g = + foldr1 (\x y -> TH.ConE 'HProduct `TH.AppE` x `TH.AppE` y) $ map generateColumnE fields + where + generateColumnE ParsedField{..} = + labelled fieldSelector $ + case fieldVariant of + ColumnField -> TH.ConE 'HIdentity `TH.AppE` (f $ TH.VarE fieldFreshName) + Rel8ableField -> (g fieldType $ TH.VarE fieldFreshName) + labelled Nothing x = x + labelled (Just _) x = TH.ConE 'HLabel `TH.AppE`x + +generateColumnsP :: ParsedDatatype -> TH.Pat +generateColumnsP ParsedDatatype{..} = + foldr1 (\x y -> TH.ConP 'HProduct [] [x, y]) $ map generateColumnP fields + where + generateColumnP ParsedField{..} = + labelled fieldSelector $ + case fieldVariant of + ColumnField -> TH.ConP 'HIdentity [] [TH.VarP fieldFreshName] + Rel8ableField -> TH.VarP fieldFreshName + labelled Nothing x = x + labelled (Just _) x = TH.ConP 'HLabel [] [x] + +generateConstructorE :: ParsedDatatype -> (TH.Exp -> TH.Exp) -> (TH.Type -> TH.Exp -> TH.Exp) -> Q TH.Exp +generateConstructorE parsedDatatype f g = + pure $ foldl' TH.AppE (TH.ConE (conName parsedDatatype)) . map generateFieldE $ fields parsedDatatype + where + generateFieldE ParsedField{..} = + case fieldVariant of + ColumnField -> f . TH.VarE $ fieldFreshName + Rel8ableField -> g fieldType $ (TH.VarE fieldFreshName --`TH.SigE` (fieldColumnType `TH.AppT` TH.WildCardT) + ) + +fromResult' :: forall context a. (Table context a) => Proxy a -> Columns a Result -> FromExprs a +fromResult' _ x = fromResult @_ @a x + +deriveRel8able :: TH.Name -> Q [TH.Dec] +deriveRel8able name = do + datatypeInfo <- reifyDatatype name + parsedDatatype <- parseDatatype datatypeInfo + let gColumns = generateGColumns parsedDatatype + let constructorE = generateConstructorE parsedDatatype + let constructorP = pure $ TH.ConP (conName parsedDatatype) [] . map (TH.VarP . fieldFreshName) $ fields parsedDatatype + let columnsE f g = pure $ generateColumnsE parsedDatatype f g + let columnsP = pure $ generateColumnsP parsedDatatype + contextName <- TH.newName "context" + [d| + instance Rel8able $(TH.conT name) where + -- Really the Generic code substitutes Expr for f and then does stuff. Maybe we want to move closer to that? + type GColumns $( TH.conT name) = + $( gColumns ) + + type GFromExprs $( TH.conT name ) = + $( TH.conT name ) Result + + -- the rest of the definition is just a few functions to go back and forth between Columns and the datatype + + gfromColumns :: SContext context -> GColumns $(TH.conT name) context -> $(TH.conT name) context + gfromColumns $( TH.varP contextName ) x = + case $( TH.varE contextName ) of + SResult -> case x of $columnsP -> $(constructorE (\x -> TH.VarE 'runIdentity `TH.AppE` x) (\ft x -> TH.VarE 'deserialize `TH.AppTypeE` TH.WildCardT `TH.AppTypeE` ft `TH.AppE` x)) + SExpr -> case x of $columnsP -> $(constructorE id (\_ x -> TH.VarE 'fromColumns `TH.AppE` x)) + SField -> case x of $columnsP -> $(constructorE id (\_ x -> TH.VarE 'fromColumns `TH.AppE` x)) + SName -> case x of $columnsP -> $(constructorE id (\_ x -> TH.VarE 'fromColumns `TH.AppE` x)) + + gtoColumns $(TH.varP contextName) $( constructorP ) = + case $( TH.varE contextName ) of + SExpr -> $(columnsE id (\_ x -> TH.VarE 'toColumns `TH.AppE` x)) + SField -> $(columnsE id (\_ x -> TH.VarE 'toColumns `TH.AppE` x)) + SName -> $(columnsE id (\_ x -> TH.VarE 'toColumns `TH.AppE` x)) + SResult -> $(columnsE (\x -> TH.ConE 'Identity `TH.AppE` x) (\ft x -> TH.VarE 'serialize `TH.AppTypeE` TH.WildCardT `TH.AppTypeE` ft `TH.AppE` x)) + + gfromResult $columnsP = + -- TODO: get rid of type application. Use a signature that references the generic value instead + $( constructorE (\x -> TH.VarE 'runIdentity `TH.AppE` x) (\ft x -> TH.VarE 'deserialize `TH.AppTypeE` TH.WildCardT `TH.AppTypeE` ft `TH.AppE` x)) + + gtoResult $constructorP = + -- TODO: get rid of type application. Use a signature that references the generic value instead + $( columnsE (\x -> TH.ConE 'Identity `TH.AppE` x) (\ft x -> TH.VarE 'serialize `TH.AppTypeE` TH.WildCardT `TH.AppTypeE` ft `TH.AppE` x)) + + |] diff --git a/src/Rel8Test.hs b/src/Rel8Test.hs new file mode 100644 index 00000000..d270eb31 --- /dev/null +++ b/src/Rel8Test.hs @@ -0,0 +1,7 @@ +-- | +{-# language DuplicateRecordFields #-} + +module Rel8Test where +import Rel8 (text) + +foo = text diff --git a/tests/Main.hs b/tests/Main.hs index 8ba5cd58..2872145b 100644 --- a/tests/Main.hs +++ b/tests/Main.hs @@ -116,1147 +116,1149 @@ import qualified Data.UUID import qualified Data.Vector as Vector -main :: IO () -main = defaultMain tests - - -tests :: TestTree -tests = - withResource startTestDatabase stopTestDatabase \getTestDatabase -> - testGroup "rel8" - [ testSelectTestTable getTestDatabase - , testWithStatement getTestDatabase - , testWhere_ getTestDatabase - , testFilter getTestDatabase - , testLimit getTestDatabase - , testUnion getTestDatabase - , testDistinct getTestDatabase - , testExists getTestDatabase - , testOptional getTestDatabase - , testAnd getTestDatabase - , testOr getTestDatabase - , testNot getTestDatabase - , testBool getTestDatabase - , testAp getTestDatabase - , testDBType getTestDatabase - , testDBEq getTestDatabase - , testTableEquality getTestDatabase - , testFromRational getTestDatabase - , testCatMaybeTable getTestDatabase - , testCatMaybe getTestDatabase - , testMaybeTable getTestDatabase - , testAggregateMaybeTable getTestDatabase - , testNestedTables getTestDatabase - , testMaybeTableApplicative getTestDatabase - , testLogicalFixities getTestDatabase - , testUpdate getTestDatabase - , testDelete getTestDatabase - , testUpsert getTestDatabase - , testSelectNestedPairs getTestDatabase - , testSelectArray getTestDatabase - , testNestedMaybeTable getTestDatabase - , testEvaluate getTestDatabase - , testShowCreateTable getTestDatabase - ] - where - startTestDatabase = do - 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[])" - - return db - - stopTestDatabase = TmpPostgres.stop - - -connect :: TmpPostgres.DB -> IO Connection -connect = acquireFromConnectionString . TmpPostgres.toConnectionString >=> either (maybe empty (fail . unpack . decodeUtf8)) pure - -acquireFromConnectionString :: ByteString -> IO (Either ConnectionError Connection) -acquireFromConnectionString connectionString = -#if MIN_VERSION_hasql(1,9,0) - acquire - [ Hasql.Connection.Setting.connection . Hasql.Connection.Setting.Connection.string . decodeUtf8 $ connectionString - ] -#else - acquire connectionString -#endif +-- main :: IO () +-- main = defaultMain tests + + +-- tests :: TestTree +-- tests = +-- withResource startTestDatabase stopTestDatabase \getTestDatabase -> +-- testGroup "rel8" +-- [ testSelectTestTable getTestDatabase +-- , testWithStatement getTestDatabase +-- , testWhere_ getTestDatabase +-- , testFilter getTestDatabase +-- , testLimit getTestDatabase +-- , testUnion getTestDatabase +-- , testDistinct getTestDatabase +-- , testExists getTestDatabase +-- , testOptional getTestDatabase +-- , testAnd getTestDatabase +-- , testOr getTestDatabase +-- , testNot getTestDatabase +-- , testBool getTestDatabase +-- , testAp getTestDatabase +-- , testDBType getTestDatabase +-- , testDBEq getTestDatabase +-- , testTableEquality getTestDatabase +-- , testFromRational getTestDatabase +-- , testCatMaybeTable getTestDatabase +-- , testCatMaybe getTestDatabase +-- , testMaybeTable getTestDatabase +-- , testAggregateMaybeTable getTestDatabase +-- , testNestedTables getTestDatabase +-- , testMaybeTableApplicative getTestDatabase +-- , testLogicalFixities getTestDatabase +-- , testUpdate getTestDatabase +-- , testDelete getTestDatabase +-- , testUpsert getTestDatabase +-- , testSelectNestedPairs getTestDatabase +-- , testSelectArray getTestDatabase +-- , testNestedMaybeTable getTestDatabase +-- , testEvaluate getTestDatabase +-- , testShowCreateTable getTestDatabase +-- ] +-- where +-- startTestDatabase = do +-- 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[])" + +-- return db + +-- stopTestDatabase = TmpPostgres.stop + + +-- connect :: TmpPostgres.DB -> IO Connection +-- connect = acquireFromConnectionString . TmpPostgres.toConnectionString >=> either (maybe empty (fail . unpack . decodeUtf8)) pure + +-- acquireFromConnectionString :: ByteString -> IO (Either ConnectionError Connection) +-- acquireFromConnectionString connectionString = +-- #if MIN_VERSION_hasql(1,9,0) +-- acquire +-- [ Hasql.Connection.Setting.connection . Hasql.Connection.Setting.Connection.string . decodeUtf8 $ connectionString +-- ] +-- #else +-- acquire connectionString +-- #endif + +-- testShowCreateTable :: IO TmpPostgres.DB -> TestTree +-- testShowCreateTable getTestDatabase = testGroup "CREATE TABLE" +-- [ testTypeChecker "tableTest" Rel8able.tableTest Rel8able.genTableTest getTestDatabase +-- , testTypeChecker "tablePair" Rel8able.tablePair Rel8able.genTablePair getTestDatabase +-- , testTypeChecker "tableMaybe" Rel8able.tableMaybe Rel8able.genTableMaybe getTestDatabase +-- , testTypeChecker "tableEither" Rel8able.tableEither Rel8able.genTableEither getTestDatabase +-- , testTypeChecker "tableThese" Rel8able.tableThese Rel8able.genTableThese getTestDatabase +-- , testTypeChecker "tableList" Rel8able.tableList Rel8able.genTableList getTestDatabase +-- , testTypeChecker "tableNest" Rel8able.tableNest Rel8able.genTableNest getTestDatabase +-- , testTypeChecker "nonRecord" Rel8able.nonRecord Rel8able.genNonRecord getTestDatabase +-- , testTypeChecker "tableProduct" Rel8able.tableProduct Rel8able.genTableProduct getTestDatabase +-- , testTypeChecker "tableType" Rel8able.tableType Rel8able.genTableType getTestDatabase +-- , testWrongTable getTestDatabase +-- , testDuplicateTable getTestDatabase +-- , testCharMismatch getTestDatabase +-- , testNumericMismatch getTestDatabase +-- ] +-- where +-- -- confirms that the type checker works correctly for numeric modifiers +-- testNumericMismatch = databasePropertyTest "numeric mismatch" \transaction -> transaction do +-- lift $ Hasql.sql $ "create table \"tableNumeric\" ( foo numeric(1000, 4) not null );" +-- typeErrors <- lift $ statement () $ Verify.getSchemaErrors +-- [Verify.SomeTableSchema Rel8able.tableNumeric] +-- case typeErrors of +-- Nothing -> failure +-- Just _ -> pure () +-- lift $ Hasql.sql $ "alter table \"tableNumeric\" alter column foo set data type numeric(1000, 2);" +-- typeErrors <- lift $ statement () $ Verify.getSchemaErrors +-- [Verify.SomeTableSchema Rel8able.tableNumeric] +-- case typeErrors of +-- Nothing -> pure () +-- Just _ -> failure + +-- -- tests that the type checker works correctly for bpchar modifiers +-- testCharMismatch = databasePropertyTest "bpchar mismatch" \transaction -> transaction do +-- lift $ Hasql.sql $ "create table \"tableChar\" ( foo bpchar(2) not null );" +-- typeErrors <- lift $ statement () $ Verify.getSchemaErrors +-- [Verify.SomeTableSchema Rel8able.tableChar] +-- case typeErrors of +-- Nothing -> failure +-- Just _ -> pure () +-- lift $ Hasql.sql $ "alter table \"tableChar\" alter column foo set data type bpchar(1);" +-- typeErrors <- lift $ statement () $ Verify.getSchemaErrors +-- [Verify.SomeTableSchema Rel8able.tableChar] +-- case typeErrors of +-- Nothing -> pure () +-- Just a -> do +-- annotate (unpack a) +-- failure + +-- -- confirms that the type checker fails when no type errors are present in a +-- -- table with duplicate column names +-- testDuplicateTable = databasePropertyTest "duplicate columns" \transaction -> transaction do +-- lift $ Hasql.sql $ B.pack $ Verify.showCreateTable Rel8able.tableDuplicate +-- typeErrors <- lift $ statement () $ Verify.getSchemaErrors +-- [Verify.SomeTableSchema Rel8able.tableDuplicate] +-- case typeErrors of +-- Nothing -> failure +-- Just _ -> pure () + +-- -- confirms that the type checker fails if the types mismatch +-- testWrongTable = databasePropertyTest "type mismatch" \transaction -> transaction do +-- lift $ Hasql.sql $ B.pack $ Verify.showCreateTable Rel8able.tableType +-- typeErrors <- lift $ statement () $ Verify.getSchemaErrors +-- [Verify.SomeTableSchema Rel8able.badTableType] +-- case typeErrors of +-- Nothing -> failure +-- Just _ -> pure () + +-- testTypeChecker :: +-- ( Show (k Result), Rel8.Rel8able k, Rel8.Selects (k Rel8.Name) (k Rel8.Expr) +-- , Rel8.Serializable (k Rel8.Expr) (k Rel8.Result)) +-- => TestName -> Rel8.TableSchema (k Rel8.Name) -> Gen (k Result) -> IO TmpPostgres.DB -> TestTree +-- testTypeChecker testName tableSchema genRows = databasePropertyTest testName \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 0 10) genRows + +-- transaction do +-- lift $ Hasql.sql $ B.pack $ Verify.showCreateTable tableSchema +-- typeErrors <- lift $ statement () $ Verify.getSchemaErrors [Verify.SomeTableSchema tableSchema] +-- case typeErrors of +-- Nothing -> pure () +-- Just typ -> do +-- annotate (unpack typ) +-- failure + +-- selected <- lift do +-- statement () $ Rel8.run_ $ Rel8.insert Rel8.Insert +-- { into = tableSchema +-- , rows = Rel8.values $ map Rel8.lit rows +-- , onConflict = Rel8.DoNothing Nothing +-- , returning = Rel8.NoReturning +-- } +-- statement () $ Rel8.run $ Rel8.select do +-- Rel8.each tableSchema + +-- -- not every type we use this with has an ord instance, and we're +-- -- primarily checking the type checker here, not the parser/printer, +-- -- so we this is only here as one additional check +-- length selected === length rows + + +-- databasePropertyTest +-- :: TestName +-- -> ((TestT Transaction () -> PropertyT IO ()) -> PropertyT IO ()) +-- -> IO TmpPostgres.DB -> TestTree +-- databasePropertyTest testName f getTestDatabase = +-- withResource (connect =<< getTestDatabase) release $ \c -> +-- testProperty testName $ property do +-- connection <- lift c +-- f $ test . hoist \m -> do +-- e <- run (Hasql.transaction Hasql.Serializable Hasql.Write (m <* condemn)) connection +-- either throwIO pure e + + +-- data TestTable f = TestTable +-- { testTableColumn1 :: Rel8.Column f Text +-- , testTableColumn2 :: Rel8.Column f Bool +-- } +-- deriving stock Generic +-- deriving anyclass Rel8.Rel8able + + +-- deriving stock instance Eq (TestTable Result) +-- deriving stock instance Ord (TestTable Result) +-- deriving stock instance Show (TestTable Result) + + +-- testTableSchema :: Rel8.TableSchema (TestTable Rel8.Name) +-- testTableSchema = +-- Rel8.TableSchema +-- { name = "test_table" +-- , columns = TestTable +-- { testTableColumn1 = "column1" +-- , testTableColumn2 = "column2" +-- } +-- } + + +-- testSelectTestTable :: IO TmpPostgres.DB -> TestTree +-- testSelectTestTable = databasePropertyTest "Can SELECT TestTable" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable + +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run_ $ Rel8.insert Rel8.Insert +-- { into = testTableSchema +-- , rows = Rel8.values $ map Rel8.lit rows +-- , onConflict = Rel8.DoNothing Nothing +-- , returning = Rel8.NoReturning +-- } + +-- statement () $ Rel8.run $ Rel8.select do +-- Rel8.each testTableSchema + +-- sort selected === sort rows -testShowCreateTable :: IO TmpPostgres.DB -> TestTree -testShowCreateTable getTestDatabase = testGroup "CREATE TABLE" - [ testTypeChecker "tableTest" Rel8able.tableTest Rel8able.genTableTest getTestDatabase - , testTypeChecker "tablePair" Rel8able.tablePair Rel8able.genTablePair getTestDatabase - , testTypeChecker "tableMaybe" Rel8able.tableMaybe Rel8able.genTableMaybe getTestDatabase - , testTypeChecker "tableEither" Rel8able.tableEither Rel8able.genTableEither getTestDatabase - , testTypeChecker "tableThese" Rel8able.tableThese Rel8able.genTableThese getTestDatabase - , testTypeChecker "tableList" Rel8able.tableList Rel8able.genTableList getTestDatabase - , testTypeChecker "tableNest" Rel8able.tableNest Rel8able.genTableNest getTestDatabase - , testTypeChecker "nonRecord" Rel8able.nonRecord Rel8able.genNonRecord getTestDatabase - , testTypeChecker "tableProduct" Rel8able.tableProduct Rel8able.genTableProduct getTestDatabase - , testTypeChecker "tableType" Rel8able.tableType Rel8able.genTableType getTestDatabase - , testWrongTable getTestDatabase - , testDuplicateTable getTestDatabase - , testCharMismatch getTestDatabase - , testNumericMismatch getTestDatabase - ] - where - -- confirms that the type checker works correctly for numeric modifiers - testNumericMismatch = databasePropertyTest "numeric mismatch" \transaction -> transaction do - lift $ Hasql.sql $ "create table \"tableNumeric\" ( foo numeric(1000, 4) not null );" - typeErrors <- lift $ statement () $ Verify.getSchemaErrors - [Verify.SomeTableSchema Rel8able.tableNumeric] - case typeErrors of - Nothing -> failure - Just _ -> pure () - lift $ Hasql.sql $ "alter table \"tableNumeric\" alter column foo set data type numeric(1000, 2);" - typeErrors <- lift $ statement () $ Verify.getSchemaErrors - [Verify.SomeTableSchema Rel8able.tableNumeric] - case typeErrors of - Nothing -> pure () - Just _ -> failure - - -- tests that the type checker works correctly for bpchar modifiers - testCharMismatch = databasePropertyTest "bpchar mismatch" \transaction -> transaction do - lift $ Hasql.sql $ "create table \"tableChar\" ( foo bpchar(2) not null );" - typeErrors <- lift $ statement () $ Verify.getSchemaErrors - [Verify.SomeTableSchema Rel8able.tableChar] - case typeErrors of - Nothing -> failure - Just _ -> pure () - lift $ Hasql.sql $ "alter table \"tableChar\" alter column foo set data type bpchar(1);" - typeErrors <- lift $ statement () $ Verify.getSchemaErrors - [Verify.SomeTableSchema Rel8able.tableChar] - case typeErrors of - Nothing -> pure () - Just a -> do - annotate (unpack a) - failure - - -- confirms that the type checker fails when no type errors are present in a - -- table with duplicate column names - testDuplicateTable = databasePropertyTest "duplicate columns" \transaction -> transaction do - lift $ Hasql.sql $ B.pack $ Verify.showCreateTable Rel8able.tableDuplicate - typeErrors <- lift $ statement () $ Verify.getSchemaErrors - [Verify.SomeTableSchema Rel8able.tableDuplicate] - case typeErrors of - Nothing -> failure - Just _ -> pure () - - -- confirms that the type checker fails if the types mismatch - testWrongTable = databasePropertyTest "type mismatch" \transaction -> transaction do - lift $ Hasql.sql $ B.pack $ Verify.showCreateTable Rel8able.tableType - typeErrors <- lift $ statement () $ Verify.getSchemaErrors - [Verify.SomeTableSchema Rel8able.badTableType] - case typeErrors of - Nothing -> failure - Just _ -> pure () - - testTypeChecker :: - ( Show (k Result), Rel8.Rel8able k, Rel8.Selects (k Rel8.Name) (k Rel8.Expr) - , Rel8.Serializable (k Rel8.Expr) (k Rel8.Result)) - => TestName -> Rel8.TableSchema (k Rel8.Name) -> Gen (k Result) -> IO TmpPostgres.DB -> TestTree - testTypeChecker testName tableSchema genRows = databasePropertyTest testName \transaction -> do - rows <- forAll $ Gen.list (Range.linear 0 10) genRows - - transaction do - lift $ Hasql.sql $ B.pack $ Verify.showCreateTable tableSchema - typeErrors <- lift $ statement () $ Verify.getSchemaErrors [Verify.SomeTableSchema tableSchema] - case typeErrors of - Nothing -> pure () - Just typ -> do - annotate (unpack typ) - failure - - selected <- lift do - statement () $ Rel8.run_ $ Rel8.insert Rel8.Insert - { into = tableSchema - , rows = Rel8.values $ map Rel8.lit rows - , onConflict = Rel8.DoNothing Nothing - , returning = Rel8.NoReturning - } - statement () $ Rel8.run $ Rel8.select do - Rel8.each tableSchema - - -- not every type we use this with has an ord instance, and we're - -- primarily checking the type checker here, not the parser/printer, - -- so we this is only here as one additional check - length selected === length rows - - -databasePropertyTest - :: TestName - -> ((TestT Transaction () -> PropertyT IO ()) -> PropertyT IO ()) - -> IO TmpPostgres.DB -> TestTree -databasePropertyTest testName f getTestDatabase = - withResource (connect =<< getTestDatabase) release $ \c -> - testProperty testName $ property do - connection <- lift c - f $ test . hoist \m -> do - e <- run (Hasql.transaction Hasql.Serializable Hasql.Write (m <* condemn)) connection - either throwIO pure e - - -data TestTable f = TestTable - { testTableColumn1 :: Rel8.Column f Text - , testTableColumn2 :: Rel8.Column f Bool - } - deriving stock Generic - deriving anyclass Rel8.Rel8able - - -deriving stock instance Eq (TestTable Result) -deriving stock instance Ord (TestTable Result) -deriving stock instance Show (TestTable Result) - - -testTableSchema :: Rel8.TableSchema (TestTable Rel8.Name) -testTableSchema = - Rel8.TableSchema - { name = "test_table" - , columns = TestTable - { testTableColumn1 = "column1" - , testTableColumn2 = "column2" - } - } - - -testSelectTestTable :: IO TmpPostgres.DB -> TestTree -testSelectTestTable = databasePropertyTest "Can SELECT TestTable" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable - - transaction do - selected <- lift do - statement () $ Rel8.run_ $ Rel8.insert Rel8.Insert - { into = testTableSchema - , rows = Rel8.values $ map Rel8.lit rows - , onConflict = Rel8.DoNothing Nothing - , returning = Rel8.NoReturning - } - - statement () $ Rel8.run $ Rel8.select do - Rel8.each testTableSchema +-- cover 1 "Empty" $ null rows +-- cover 1 "Singleton" $ null $ drop 1 rows +-- cover 1 ">1 row" $ not $ null $ drop 1 rows - sort selected === sort rows - cover 1 "Empty" $ null rows - cover 1 "Singleton" $ null $ drop 1 rows - cover 1 ">1 row" $ not $ null $ drop 1 rows +-- testWhere_ :: IO TmpPostgres.DB -> TestTree +-- testWhere_ = databasePropertyTest "WHERE (Rel8.where_)" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 1 10) genTestTable +-- magicBool <- forAll Gen.bool -testWhere_ :: IO TmpPostgres.DB -> TestTree -testWhere_ = databasePropertyTest "WHERE (Rel8.where_)" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 1 10) genTestTable +-- let expected = filter (\t -> testTableColumn2 t == magicBool) rows - magicBool <- forAll Gen.bool +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- t <- Rel8.values $ Rel8.lit <$> rows +-- Rel8.where_ $ testTableColumn2 t Rel8.==. Rel8.lit magicBool +-- return t - let expected = filter (\t -> testTableColumn2 t == magicBool) rows +-- sort selected === sort expected - transaction do - selected <- lift do - statement () $ Rel8.run $ Rel8.select do - t <- Rel8.values $ Rel8.lit <$> rows - Rel8.where_ $ testTableColumn2 t Rel8.==. Rel8.lit magicBool - return t +-- cover 1 "No results" $ null expected +-- cover 1 "Some results" $ not $ null expected +-- cover 1 "All results" $ expected == rows - sort selected === sort expected - cover 1 "No results" $ null expected - cover 1 "Some results" $ not $ null expected - cover 1 "All results" $ expected == rows +-- testFilter :: IO TmpPostgres.DB -> TestTree +-- testFilter = databasePropertyTest "filter" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 1 10) genTestTable +-- transaction do +-- let expected = filter testTableColumn2 rows -testFilter :: IO TmpPostgres.DB -> TestTree -testFilter = databasePropertyTest "filter" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 1 10) genTestTable +-- selected <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- Rel8.filter testTableColumn2 =<< Rel8.values (Rel8.lit <$> rows) - transaction do - let expected = filter testTableColumn2 rows +-- sort selected === sort expected - selected <- lift do - statement () $ Rel8.run $ Rel8.select do - Rel8.filter testTableColumn2 =<< Rel8.values (Rel8.lit <$> rows) +-- cover 1 "No results" $ null expected +-- cover 1 "Some results" $ not $ null expected +-- cover 1 "All results" $ expected == rows - sort selected === sort expected - cover 1 "No results" $ null expected - cover 1 "Some results" $ not $ null expected - cover 1 "All results" $ expected == rows +-- testLimit :: IO TmpPostgres.DB -> TestTree +-- testLimit = databasePropertyTest "LIMIT (Rel8.limit)" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 1 10) genTestTable +-- n <- forAll $ Gen.integral (Range.linear 0 10) -testLimit :: IO TmpPostgres.DB -> TestTree -testLimit = databasePropertyTest "LIMIT (Rel8.limit)" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 1 10) genTestTable +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- Rel8.limit n $ Rel8.values (Rel8.lit <$> rows) - n <- forAll $ Gen.integral (Range.linear 0 10) +-- diff (length selected) (<=) (fromIntegral n) - transaction do - selected <- lift do - statement () $ Rel8.run $ Rel8.select do - Rel8.limit n $ Rel8.values (Rel8.lit <$> rows) +-- for_ selected \row -> +-- diff row elem rows - diff (length selected) (<=) (fromIntegral n) +-- cover 1 "n == 0" $ n == 0 +-- cover 1 "n < length rows" $ fromIntegral n < length rows +-- cover 1 "n == length rows" $ fromIntegral n == length rows +-- cover 1 "n >= length rows" $ fromIntegral n >= length rows - for_ selected \row -> - diff row elem rows - cover 1 "n == 0" $ n == 0 - cover 1 "n < length rows" $ fromIntegral n < length rows - cover 1 "n == length rows" $ fromIntegral n == length rows - cover 1 "n >= length rows" $ fromIntegral n >= length rows +-- testUnion :: IO TmpPostgres.DB -> TestTree +-- testUnion = databasePropertyTest "UNION (Rel8.union)" \transaction -> evalM do +-- left <- forAll $ Gen.list (Range.linear 0 10) genTestTable +-- right <- forAll $ Gen.list (Range.linear 0 10) genTestTable +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- Rel8.values (Rel8.lit <$> nub left) `Rel8.union` Rel8.values (Rel8.lit <$> nub right) -testUnion :: IO TmpPostgres.DB -> TestTree -testUnion = databasePropertyTest "UNION (Rel8.union)" \transaction -> evalM do - left <- forAll $ Gen.list (Range.linear 0 10) genTestTable - right <- forAll $ Gen.list (Range.linear 0 10) genTestTable +-- sort selected === sort (nub (left ++ right)) - transaction do - selected <- lift do - statement () $ Rel8.run $ Rel8.select do - Rel8.values (Rel8.lit <$> nub left) `Rel8.union` Rel8.values (Rel8.lit <$> nub right) - sort selected === sort (nub (left ++ right)) +-- testDistinct :: IO TmpPostgres.DB -> TestTree +-- testDistinct = databasePropertyTest "DISTINCT (Rel8.distinct)" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- Rel8.distinct do +-- Rel8.values (Rel8.lit <$> rows) -testDistinct :: IO TmpPostgres.DB -> TestTree -testDistinct = databasePropertyTest "DISTINCT (Rel8.distinct)" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable +-- sort selected === nub (sort rows) - transaction do - selected <- lift do - statement () $ Rel8.run $ Rel8.select do - Rel8.distinct do - Rel8.values (Rel8.lit <$> rows) +-- cover 1 "Empty" $ null rows +-- cover 1 "Duplicates" $ not (null rows) && rows /= nub rows +-- cover 1 "No duplicates" $ not (null rows) && rows == nub rows - sort selected === nub (sort rows) - cover 1 "Empty" $ null rows - cover 1 "Duplicates" $ not (null rows) && rows /= nub rows - cover 1 "No duplicates" $ not (null rows) && rows == nub rows +-- testExists :: IO TmpPostgres.DB -> TestTree +-- testExists = databasePropertyTest "EXISTS (Rel8.exists)" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable +-- transaction do +-- exists <- lift do +-- statement () $ Rel8.run1 $ Rel8.select do +-- Rel8.exists $ Rel8.values $ Rel8.lit <$> rows -testExists :: IO TmpPostgres.DB -> TestTree -testExists = databasePropertyTest "EXISTS (Rel8.exists)" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable +-- case rows of +-- [] -> exists === False +-- _ -> exists === True - transaction do - exists <- lift do - statement () $ Rel8.run1 $ Rel8.select do - Rel8.exists $ Rel8.values $ Rel8.lit <$> rows - case rows of - [] -> exists === False - _ -> exists === True +-- testOptional :: IO TmpPostgres.DB -> TestTree +-- testOptional = databasePropertyTest "Rel8.optional" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- Rel8.optional $ Rel8.values (Rel8.lit <$> rows) -testOptional :: IO TmpPostgres.DB -> TestTree -testOptional = databasePropertyTest "Rel8.optional" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable +-- case rows of +-- [] -> selected === [Nothing] +-- _ -> sort selected === fmap Just (sort rows) - transaction do - selected <- lift do - statement () $ Rel8.run $ Rel8.select do - Rel8.optional $ Rel8.values (Rel8.lit <$> rows) - case rows of - [] -> selected === [Nothing] - _ -> sort selected === fmap Just (sort rows) +-- testAnd :: IO TmpPostgres.DB -> TestTree +-- testAnd = databasePropertyTest "AND (&&.)" \transaction -> do +-- (x, y) <- forAll $ liftA2 (,) Gen.bool Gen.bool +-- transaction do +-- result <- lift do +-- statement () $ Rel8.run1 $ Rel8.select do +-- pure $ Rel8.lit x Rel8.&&. Rel8.lit y -testAnd :: IO TmpPostgres.DB -> TestTree -testAnd = databasePropertyTest "AND (&&.)" \transaction -> do - (x, y) <- forAll $ liftA2 (,) Gen.bool Gen.bool +-- result === (x && y) - transaction do - result <- lift do - statement () $ Rel8.run1 $ Rel8.select do - pure $ Rel8.lit x Rel8.&&. Rel8.lit y - result === (x && y) +-- testOr :: IO TmpPostgres.DB -> TestTree +-- testOr = databasePropertyTest "OR (||.)" \transaction -> do +-- (x, y) <- forAll $ liftA2 (,) Gen.bool Gen.bool +-- transaction do +-- result <- lift do +-- statement () $ Rel8.run1 $ Rel8.select $ pure $ +-- Rel8.lit x Rel8.||. Rel8.lit y -testOr :: IO TmpPostgres.DB -> TestTree -testOr = databasePropertyTest "OR (||.)" \transaction -> do - (x, y) <- forAll $ liftA2 (,) Gen.bool Gen.bool +-- result === (x || y) - transaction do - result <- lift do - statement () $ Rel8.run1 $ Rel8.select $ pure $ - Rel8.lit x Rel8.||. Rel8.lit y - result === (x || y) +-- testLogicalFixities :: IO TmpPostgres.DB -> TestTree +-- testLogicalFixities = databasePropertyTest "Logical operator fixities" \transaction -> do +-- (u, v, w, x) <- forAll $ (,,,) <$> Gen.bool <*> Gen.bool <*> Gen.bool <*> Gen.bool +-- transaction do +-- result <- lift do +-- statement () $ Rel8.run1 $ Rel8.select do +-- pure $ Rel8.lit u Rel8.||. Rel8.lit v Rel8.&&. Rel8.lit w Rel8.==. Rel8.lit x -testLogicalFixities :: IO TmpPostgres.DB -> TestTree -testLogicalFixities = databasePropertyTest "Logical operator fixities" \transaction -> do - (u, v, w, x) <- forAll $ (,,,) <$> Gen.bool <*> Gen.bool <*> Gen.bool <*> Gen.bool +-- result === (u || v && w == x) - transaction do - result <- lift do - statement () $ Rel8.run1 $ Rel8.select do - pure $ Rel8.lit u Rel8.||. Rel8.lit v Rel8.&&. Rel8.lit w Rel8.==. Rel8.lit x - result === (u || v && w == x) +-- testNot :: IO TmpPostgres.DB -> TestTree +-- testNot = databasePropertyTest "NOT (not_)" \transaction -> do +-- x <- forAll Gen.bool +-- transaction do +-- result <- lift do +-- statement () $ Rel8.run1 $ Rel8.select do +-- pure $ Rel8.not_ $ Rel8.lit x + +-- result === not x + + +-- testBool :: IO TmpPostgres.DB -> TestTree +-- testBool = databasePropertyTest "ifThenElse_" \transaction -> do +-- (x, y, z) <- forAll $ liftA3 (,,) Gen.bool Gen.bool Gen.bool + +-- transaction do +-- result <- lift do +-- statement () $ Rel8.run1 $ Rel8.select do +-- pure $ Rel8.bool (Rel8.lit z) (Rel8.lit y) (Rel8.lit x) + +-- result === if x then y else z + + +-- testAp :: IO TmpPostgres.DB -> TestTree +-- testAp = databasePropertyTest "Cartesian product (<*>)" \transaction -> do +-- (rows1, rows2) <- forAll $ +-- liftA2 (,) +-- (Gen.list (Range.linear 1 10) genTestTable) +-- (Gen.list (Range.linear 1 10) genTestTable) + +-- transaction do +-- result <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- liftA2 (,) (Rel8.values (Rel8.lit <$> rows1)) (Rel8.values (Rel8.lit <$> rows2)) + +-- sort result === sort (liftA2 (,) rows1 rows2) + + +-- data Composite = Composite +-- { bool :: !Bool +-- , char :: !Text +-- , array :: ![Int32] +-- } +-- deriving stock (Eq, Show, Generic) +-- deriving (Rel8.DBType) via Rel8.Composite Composite + + +-- instance Rel8.DBComposite Composite where +-- compositeTypeName = "composite" +-- compositeFields = Rel8.namesFromLabels + + +-- testDBType :: IO TmpPostgres.DB -> TestTree +-- testDBType getTestDatabase = testGroup "DBType instances" +-- [ dbTypeTest "Bool" Gen.bool +-- , dbTypeTest "ByteString" $ Gen.bytes (Range.linear 0 128) +-- , dbTypeTest "CalendarDiffTime" genCalendarDiffTime +-- , dbTypeTest "Char" Gen.unicode +-- , dbTypeTest "CI Lazy Text" $ mk . Data.Text.Lazy.fromStrict <$> genText +-- , dbTypeTest "CI Text" $ mk <$> genText +-- , dbTypeTest "Composite" genComposite +-- , dbTypeTest "Day" genDay +-- , dbTypeTest "Double" $ (/ 10) . fromIntegral @Int @Double <$> Gen.integral (Range.linear (-100) 100) +-- , dbTypeTest "Fixed" $ toEnum @Centi <$> Gen.integral (Range.linear (-10000) 10000) +-- , dbTypeTest "Float" $ (/ 10) . fromIntegral @Int @Float <$> Gen.integral (Range.linear (-100) 100) +-- , dbTypeTest "Int32" $ Gen.integral @_ @Int32 Range.linearBounded +-- , dbTypeTest "Int64" $ Gen.integral @_ @Int64 Range.linearBounded +-- , dbTypeTest "Lazy ByteString" $ Data.ByteString.Lazy.fromStrict <$> Gen.bytes (Range.linear 0 128) +-- , dbTypeTest "Lazy Text" $ Data.Text.Lazy.fromStrict <$> genText +-- , dbTypeTest "LocalTime" genLocalTime +-- , dbTypeTest "Scientific" $ genScientific +-- , dbTypeTest "Text" genText +-- , dbTypeTest "TimeOfDay" genTimeOfDay +-- , dbTypeTest "UTCTime" $ UTCTime <$> genDay <*> genDiffTime +-- , dbTypeTest "UUID" $ Data.UUID.fromWords <$> genWord32 <*> genWord32 <*> genWord32 <*> genWord32 +-- , dbTypeTest "INet" genIPRange +-- , dbTypeTest "Value" genValue +-- , dbTypeTest "JSONEncoded" genJSONEncoded +-- , dbTypeTest "JSONBEncoded" genJSONBEncoded +-- ] + +-- where +-- dbTypeTest :: (Eq a, Show a, Rel8.DBType a, Rel8.ToExprs (Rel8.Expr a) a) => TestName -> Gen a -> TestTree +-- dbTypeTest name generator = testGroup name +-- [ databasePropertyTest name (t generator) getTestDatabase +-- , databasePropertyTest ("Maybe " <> name) (t (Gen.maybe generator)) getTestDatabase +-- ] + +-- t :: forall a. (Eq a, Show a, Rel8.Sql Rel8.DBType a, Rel8.ToExprs (Rel8.Expr a) a) +-- => Gen a +-- -> (TestT Transaction () -> PropertyT IO ()) +-- -> PropertyT IO () +-- t generator transaction = do +-- x <- forAll generator +-- y <- forAll generator +-- xss <- forAll $ Gen.list (Range.linear 0 10) (Gen.list (Range.linear 0 10) generator) +-- xsss <- forAll $ Gen.list (Range.linear 0 10) (Gen.list (Range.linear 0 10) (Gen.list (Range.linear 0 10) generator)) + +-- transaction do +-- res <- lift do +-- statement () $ Rel8.run1 $ Rel8.select do +-- pure (Rel8.litExpr x) +-- diff res (==) x +-- res' <- lift do +-- statement () $ Rel8.run1 $ Rel8.select $ Rel8.many $ Rel8.many do +-- Rel8.values [Rel8.litExpr x, Rel8.litExpr y] +-- diff res' (==) [[x, y]] +-- res3 <- lift do +-- statement () $ Rel8.run1 $ Rel8.select $ Rel8.many $ Rel8.many $ Rel8.many do +-- Rel8.values [Rel8.litExpr x, Rel8.litExpr y] +-- diff res3 (==) [[[x, y]]] +-- res'' <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- xs <- Rel8.catListTable (Rel8.listTable [Rel8.listTable [Rel8.litExpr x, Rel8.litExpr y]]) +-- Rel8.catListTable xs +-- diff res'' (==) [x, y] +-- res''' <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- xss' <- Rel8.catListTable (Rel8.listTable [Rel8.listTable [Rel8.listTable [Rel8.litExpr x, Rel8.litExpr y]]]) +-- xs <- Rel8.catListTable xss' +-- Rel8.catListTable xs +-- diff res''' (==) [x, y] +-- res'''' <- lift do +-- statement () $ Rel8.run1 $ Rel8.select $ +-- Rel8.aggregate Rel8.listCatExpr $ +-- Rel8.values $ map Rel8.litExpr xss +-- diff res'''' (==) (concat xss) +-- res''''' <- lift do +-- statement () $ Rel8.run1 $ Rel8.select $ +-- Rel8.aggregate Rel8.listCatExpr $ +-- Rel8.values $ map Rel8.litExpr xsss +-- diff res''''' (==) (concat xsss) + +-- transaction do +-- res <- lift do +-- statement x $ Rel8.prepared Rel8.run1 $ +-- Rel8.select @(Rel8.Expr _) . +-- pure +-- diff res (==) x + +-- res' <- lift do +-- statement [x, y] $ Rel8.prepared Rel8.run1 $ +-- 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 $ +-- 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 $ +-- 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]]] + +-- genScientific :: Gen Scientific +-- genScientific = (/ 10) . fromIntegral @Int @Scientific <$> Gen.integral (Range.linear (-100) 100) + +-- genComposite :: Gen Composite +-- genComposite = do +-- bool <- Gen.bool +-- char <- genText +-- array <- Gen.list (Range.linear 0 10) (Gen.int32 (Range.linear (-10000) 10000)) +-- pure Composite {..} + +-- genDay :: Gen Day +-- genDay = do +-- year <- Gen.integral (Range.linear 1970 3000) +-- month <- Gen.integral (Range.linear 1 12) +-- day <- Gen.integral (Range.linear 1 31) +-- Gen.just $ pure $ fromGregorianValid year month day + +-- genCalendarDiffTime :: Gen CalendarDiffTime +-- genCalendarDiffTime = do +-- -- hardcoded to 0 because Hasql's 'interval' decoder needs to return a +-- -- CalendarDiffTime for this to be properly round-trippable +-- months <- pure 0 -- Gen.integral (Range.linear 0 120) +-- diffTime <- secondsToNominalDiffTime . MkFixed . (* 1000000) <$> Gen.integral (Range.linear 0 2147483647999999) +-- pure $ CalendarDiffTime months diffTime + +-- genDiffTime :: Gen DiffTime +-- genDiffTime = secondsToDiffTime <$> Gen.integral (Range.linear 0 86401) + +-- genTimeOfDay :: Gen TimeOfDay +-- genTimeOfDay = do +-- hour <- Gen.integral (Range.linear 0 23) +-- minute <- Gen.integral (Range.linear 0 59) +-- sec <- fromIntegral @Int <$> Gen.integral (Range.linear 0 59) +-- Gen.just $ pure $ makeTimeOfDayValid hour minute sec + +-- genLocalTime = LocalTime <$> genDay <*> genTimeOfDay + +-- genWord32 :: Gen Word32 +-- genWord32 = Gen.integral Range.linearBounded + +-- genIPRange :: Gen (Data.IP.IPRange) +-- genIPRange = +-- Gen.choice +-- [ Data.IP.IPv4Range <$> (Data.IP.makeAddrRange <$> genIPv4 <*> genIP4Mask) +-- , Data.IP.IPv6Range <$> (Data.IP.makeAddrRange <$> genIPv6 <*> genIP6Mask) +-- ] +-- where +-- genIP4Mask :: Gen Int +-- genIP4Mask = Gen.integral (Range.linearFrom 0 0 32) + +-- genIPv4 :: Gen Data.IP.IPv4 +-- genIPv4 = Data.IP.toIPv4w <$> genWord32 + +-- genIP6Mask :: Gen Int +-- genIP6Mask = Gen.integral (Range.linearFrom 0 0 128) + +-- genIPv6 :: Gen (Data.IP.IPv6) +-- genIPv6 = Data.IP.toIPv6w <$> ((,,,) <$> genWord32 <*> genWord32 <*> genWord32 <*> genWord32) + +-- genKey :: Gen Aeson.Key +-- genKey = Aeson.Key.fromText <$> genText + +-- genValue :: Gen Aeson.Value +-- genValue = Gen.recursive Gen.choice +-- [ pure Aeson.Null +-- , Aeson.Bool <$> Gen.bool +-- , Aeson.Number <$> genScientific +-- , Aeson.String <$> genText +-- ] +-- [ Aeson.Object . Aeson.KeyMap.fromMap <$> Gen.map (Range.linear 0 10) ((,) <$> genKey <*> genValue) +-- , Aeson.Array . Vector.fromList <$> Gen.list (Range.linear 0 10) genValue +-- ] + +-- genJSONEncoded = Rel8.JSONEncoded <$> genValue +-- genJSONBEncoded = Rel8.JSONBEncoded <$> genValue + + +-- testDBEq :: IO TmpPostgres.DB -> TestTree +-- testDBEq getTestDatabase = testGroup "DBEq instances" +-- [ dbEqTest "Bool" Gen.bool +-- , dbEqTest "Int32" $ Gen.integral @_ @Int32 Range.linearBounded +-- , dbEqTest "Int64" $ Gen.integral @_ @Int64 Range.linearBounded +-- , dbEqTest "Text" $ genText +-- ] + +-- where +-- dbEqTest :: (Eq a, Show a, Rel8.DBEq a) => TestName -> Gen a -> TestTree +-- dbEqTest name generator = testGroup name +-- [ databasePropertyTest name (t generator) getTestDatabase +-- , databasePropertyTest ("Maybe " <> name) (t (Gen.maybe generator)) getTestDatabase +-- ] + +-- t :: forall a. (Eq a, Show a, Rel8.Sql Rel8.DBEq a) +-- => Gen a +-- -> (TestT Transaction () -> PropertyT IO ()) +-- -> PropertyT IO () +-- t generator transaction = do +-- (x, y) <- forAll (liftA2 (,) generator generator) + +-- transaction do +-- res <- lift do +-- statement () $ Rel8.run1 $ Rel8.select do +-- pure $ Rel8.litExpr x Rel8.==. Rel8.litExpr y +-- res === (x == y) + + +-- genText :: Gen Text +-- genText = removeNull <$> Gen.text (Range.linear 0 10) Gen.unicode +-- where +-- -- | Postgres doesn't support the NULL character (not to be confused with a NULL value) inside strings. +-- removeNull :: Text -> Text +-- removeNull = T.filter (/= '\0') + + + +-- testTableEquality :: IO TmpPostgres.DB -> TestTree +-- testTableEquality = databasePropertyTest "TestTable equality" \transaction -> do +-- (x, y) <- forAll $ liftA2 (,) genTestTable genTestTable + +-- transaction do +-- eq <- lift do +-- statement () $ Rel8.run1 $ Rel8.select do +-- pure $ Rel8.lit x Rel8.==: Rel8.lit y + +-- eq === (x == y) + + +-- testFromRational :: IO TmpPostgres.DB -> TestTree +-- testFromRational = databasePropertyTest "fromRational" \transaction -> do +-- numerator <- forAll $ Gen.int64 Range.linearBounded +-- denominator <- forAll $ Gen.int64 $ Range.linear 1 maxBound + +-- let +-- rational = toInteger numerator % toInteger denominator +-- double = fromRational @Double rational + +-- transaction do +-- result <- lift do +-- statement () $ Rel8.run1 $ Rel8.select do +-- pure $ fromRational rational +-- diff result (~=) double +-- where +-- wholeDigits x = fromIntegral $ length $ show $ round @_ @Integer x +-- -- A Double gives us between 15-17 decimal digits of precision. +-- -- It's tempting to say that two numbers are equal if they differ by less than 1e15. +-- -- But this doesn't hold. +-- -- The precision is split between the whole numer part and the decimal part of the number. +-- -- For instance, a number between 10 and 99 only has around 13 digits of precision in its decimal part. +-- -- Postgres and Haskell show differing amounts of digits in these cases, +-- a ~= b = abs (a - b) < 10 ** (-15 + wholeDigits a) +-- infix 4 ~= + + +-- testCatMaybeTable :: IO TmpPostgres.DB -> TestTree +-- testCatMaybeTable = databasePropertyTest "catMaybeTable" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable + +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- testTable <- Rel8.values $ Rel8.lit <$> rows +-- Rel8.catMaybeTable $ Rel8.bool Rel8.nothingTable (pure testTable) (testTableColumn2 testTable) + +-- sort selected === sort (filter testTableColumn2 rows) + + +-- testCatMaybe :: IO TmpPostgres.DB -> TestTree +-- testCatMaybe = databasePropertyTest "catMaybe" \transaction -> evalM do +-- rows <- forAll $ Gen.list (Range.linear 0 10) $ Gen.maybe Gen.bool + +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- Rel8.catNull =<< Rel8.values (map Rel8.lit rows) + +-- sort selected === sort (catMaybes rows) + + +-- testMaybeTable :: IO TmpPostgres.DB -> TestTree +-- testMaybeTable = databasePropertyTest "maybeTable" \transaction -> evalM do +-- (rows, def) <- forAll $ liftA2 (,) (Gen.list (Range.linear 0 10) genTestTable) genTestTable + +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- Rel8.maybeTable (Rel8.lit def) id <$> Rel8.optional (Rel8.values (Rel8.lit <$> rows)) + +-- case rows of +-- [] -> selected === [def] +-- _ -> sort selected === sort rows + + +-- testAggregateMaybeTable :: IO TmpPostgres.DB -> TestTree +-- testAggregateMaybeTable = databasePropertyTest "aggregateMaybeTable" \transaction -> evalM do +-- rows <- forAll $ Gen.list (Range.linear 0 10) (Gen.maybe (Gen.int64 (Range.linear 0 10))) + +-- let +-- aggregate = go 0 False False +-- where +-- go !n nothing _ (Just a : as) = go (n + a) nothing True as +-- go n _ just (Nothing : as) = go n True just as +-- go _ False False [] = [] +-- go _ True False [] = [Nothing] +-- go n False True [] = [Just n] +-- go n True True [] = [Nothing, Just n] + +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- Rel8.aggregate1 (Rel8.aggregateMaybeTable Rel8.sum) $ Rel8.values (Rel8.lit <$> rows) + +-- sort selected === aggregate rows + + +-- data TwoTestTables f = +-- TwoTestTables +-- { testTable1 :: TestTable f +-- , testTable2 :: TestTable f +-- } +-- deriving stock Generic +-- deriving anyclass Rel8.Rel8able + + +-- deriving stock instance Eq (TwoTestTables Result) +-- deriving stock instance Ord (TwoTestTables Result) +-- deriving stock instance Show (TwoTestTables Result) + + +-- testNestedTables :: IO TmpPostgres.DB -> TestTree +-- testNestedTables = databasePropertyTest "Nested TestTables" \transaction -> evalM do +-- rows <- forAll do +-- Gen.list (Range.linear 0 10) $ +-- liftA2 TwoTestTables genTestTable genTestTable + +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- Rel8.values (Rel8.lit <$> rows) + +-- sort selected === sort rows + + +-- testMaybeTableApplicative :: IO TmpPostgres.DB -> TestTree +-- testMaybeTableApplicative = databasePropertyTest "MaybeTable (<*>)" \transaction -> evalM do +-- rows1 <- genRows +-- rows2 <- genRows + +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- as <- Rel8.optional (Rel8.values (Rel8.lit <$> rows1)) +-- bs <- Rel8.optional (Rel8.values (Rel8.lit <$> rows2)) +-- pure $ liftA2 (,) as bs + +-- case (rows1, rows2) of +-- ([], []) -> selected === [Nothing] +-- ([], bs) -> selected === (Nothing <$ bs) +-- (as, []) -> selected === (Nothing <$ as) +-- (as, bs) -> sort selected === sort (Just <$> liftA2 (,) as bs) +-- where +-- genRows :: PropertyT IO [TestTable Result] +-- genRows = forAll do +-- Gen.list (Range.linear 0 10) $ liftA2 TestTable genText (pure True) + + +-- genTestTable :: Gen (TestTable Result) +-- genTestTable = do +-- testTableColumn1 <- Gen.text (Range.linear 0 5) Gen.alphaNum +-- testTableColumn2 <- Gen.bool +-- return TestTable{..} + + +-- testUpdate :: IO TmpPostgres.DB -> TestTree +-- testUpdate = databasePropertyTest "Can UPDATE TestTable" \transaction -> do +-- rows <- forAll $ Gen.map (Range.linear 0 5) $ liftA2 (,) genTestTable genTestTable + +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run_ $ Rel8.insert Rel8.Insert +-- { into = testTableSchema +-- , rows = Rel8.values $ map Rel8.lit $ Map.keys rows +-- , onConflict = Rel8.DoNothing Nothing +-- , returning = Rel8.NoReturning +-- } + +-- statement () $ Rel8.run_ $ Rel8.update Rel8.Update +-- { target = testTableSchema +-- , from = pure () +-- , set = \_ r -> +-- let updates = map (bimap Rel8.lit Rel8.lit) $ Map.toList rows +-- in +-- foldl +-- ( \e (x, y) -> +-- Rel8.bool +-- e +-- y +-- ( testTableColumn1 r Rel8.==. testTableColumn1 x Rel8.&&. +-- testTableColumn2 r Rel8.==. testTableColumn2 x +-- ) +-- ) +-- r +-- updates +-- , updateWhere = \_ _ -> Rel8.lit True +-- , returning = Rel8.NoReturning +-- } + +-- statement () $ Rel8.run $ Rel8.select do +-- Rel8.each testTableSchema + +-- sort selected === sort (Map.elems rows) + +-- cover 1 "Empty" $ null rows +-- cover 1 "Singleton" $ null $ drop 1 $ Map.keys rows +-- cover 1 ">1 row" $ not $ null $ drop 1 $ Map.keys rows + + +-- testDelete :: IO TmpPostgres.DB -> TestTree +-- testDelete = databasePropertyTest "Can DELETE TestTable" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 0 5) genTestTable + +-- transaction do +-- (deleted, selected) <- lift do +-- statement () $ Rel8.run_ $ Rel8.insert Rel8.Insert +-- { into = testTableSchema +-- , rows = Rel8.values $ map Rel8.lit rows +-- , onConflict = Rel8.DoNothing Nothing +-- , returning = Rel8.NoReturning +-- } + +-- deleted <- statement () $ Rel8.run $ Rel8.delete Rel8.Delete +-- { from = testTableSchema +-- , using = pure () +-- , deleteWhere = const testTableColumn2 +-- , returning = Rel8.Returning id +-- } + +-- selected <- statement () $ Rel8.run $ Rel8.select do +-- Rel8.each testTableSchema + +-- pure (deleted, selected) + +-- sort (deleted <> selected) === sort rows + + +-- testWithStatement :: IO TmpPostgres.DB -> TestTree +-- testWithStatement genTestDatabase = +-- testGroup "WITH" +-- [ selectUnionInsert genTestDatabase +-- , rowsAffectedNoReturning genTestDatabase +-- , rowsAffectedReturing genTestDatabase +-- , pureQuery genTestDatabase +-- ] +-- where +-- selectUnionInsert = +-- databasePropertyTest "Can UNION results of SELECT with results of INSERT" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 0 50) genTestTable + +-- transaction do +-- rows' <- lift do +-- statement () $ Rel8.run $ do +-- values <- Rel8.select $ Rel8.values $ map Rel8.lit rows + +-- inserted <- Rel8.insert $ Rel8.Insert +-- { into = testTableSchema +-- , rows = values +-- , onConflict = Rel8.DoNothing Nothing +-- , returning = Rel8.Returning id +-- } + +-- pure $ values <> inserted + +-- sort rows' === sort (rows <> rows) + +-- rowsAffectedNoReturning = +-- databasePropertyTest "Can read rows affected from INSERT without RETURNING" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 0 50) genTestTable + +-- transaction do +-- affected <- lift do +-- statement () $ Rel8.runN $ do +-- Rel8.insert $ Rel8.Insert +-- { into = testTableSchema +-- , rows = Rel8.values $ map Rel8.lit rows +-- , onConflict = Rel8.DoNothing Nothing +-- , returning = Rel8.NoReturning +-- } + +-- length rows === fromIntegral affected + +-- rowsAffectedReturing = +-- databasePropertyTest "Can read rows affected from INSERT with RETURNING" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 0 50) genTestTable + +-- transaction do +-- affected <- lift do +-- statement () $ Rel8.runN $ void $ do +-- Rel8.insert $ Rel8.Insert +-- { into = testTableSchema +-- , rows = Rel8.values $ map Rel8.lit rows +-- , onConflict = Rel8.DoNothing Nothing +-- , returning = Rel8.Returning id +-- } + +-- length rows === fromIntegral affected + +-- pureQuery = +-- databasePropertyTest "Can read pure Query" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 0 50) genTestTable + +-- transaction do +-- rows' <- lift do +-- statement () $ Rel8.run $ pure do +-- Rel8.values $ map Rel8.lit rows + +-- sort rows === sort rows' + + + +-- data UniqueTable f = UniqueTable +-- { uniqueTableKey :: Rel8.Column f Text +-- , uniqueTableValue :: Rel8.Column f Text +-- } +-- deriving stock Generic +-- deriving anyclass Rel8.Rel8able + + +-- deriving stock instance Eq (UniqueTable Result) +-- deriving stock instance Ord (UniqueTable Result) +-- deriving stock instance Show (UniqueTable Result) + + +-- uniqueTableSchema :: Rel8.TableSchema (UniqueTable Rel8.Name) +-- uniqueTableSchema = +-- Rel8.TableSchema +-- { name = "unique_table" +-- , columns = UniqueTable +-- { uniqueTableKey = "key" +-- , uniqueTableValue = "value" +-- } +-- } + + +-- genUniqueTable :: Gen (UniqueTable Result) +-- genUniqueTable = do +-- uniqueTableKey <- Gen.text (Range.linear 0 5) Gen.alphaNum +-- uniqueTableValue <- Gen.text (Range.linear 0 5) Gen.alphaNum +-- pure UniqueTable {..} + + +-- testUpsert :: IO TmpPostgres.DB -> TestTree +-- testUpsert = databasePropertyTest "Can UPSERT UniqueTable" \transaction -> do +-- as <- unique $ forAll $ Gen.list (Range.linear 0 20) genUniqueTable +-- bs <- unique $ forAll $ Gen.list (Range.linear 0 20) genUniqueTable + +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run_ $ Rel8.insert Rel8.Insert +-- { into = uniqueTableSchema +-- , rows = Rel8.values $ Rel8.lit <$> as +-- , onConflict = Rel8.DoNothing Nothing +-- , returning = Rel8.NoReturning +-- } -testNot :: IO TmpPostgres.DB -> TestTree -testNot = databasePropertyTest "NOT (not_)" \transaction -> do - x <- forAll Gen.bool +-- statement () $ Rel8.run_ $ Rel8.insert Rel8.Insert +-- { into = uniqueTableSchema +-- , rows = Rel8.values $ Rel8.lit <$> bs +-- , onConflict = Rel8.DoUpdate Rel8.Upsert +-- { conflict = +-- Rel8.OnIndex +-- Rel8.Index +-- { columns = uniqueTableKey +-- , predicate = Nothing +-- } +-- , set = \UniqueTable {uniqueTableValue} old -> old {uniqueTableValue} +-- , updateWhere = \_ _ -> Rel8.true +-- } +-- , returning = Rel8.NoReturning +-- } + +-- statement () $ Rel8.run $ Rel8.select do +-- Rel8.each uniqueTableSchema + +-- fromUniqueTables selected === fromUniqueTables bs <> fromUniqueTables as +-- where +-- unique = fmap (nubOrdOn uniqueTableKey) +-- fromUniqueTables = Map.fromList . map \(UniqueTable key value) -> (key, value) + + +-- newtype HKNestedPair f = HKNestedPair { pairOne :: (TestTable f, TestTable f) } +-- deriving stock Generic +-- deriving anyclass Rel8.Rel8able + +-- deriving stock instance Eq (HKNestedPair Result) +-- deriving stock instance Ord (HKNestedPair Result) +-- deriving stock instance Show (HKNestedPair Result) + + +-- testSelectNestedPairs :: IO TmpPostgres.DB -> TestTree +-- testSelectNestedPairs = databasePropertyTest "Can SELECT nested pairs" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 0 10) $ HKNestedPair <$> liftA2 (,) genTestTable genTestTable + +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- Rel8.values $ map Rel8.lit rows + +-- sort selected === sort rows + + +-- testSelectArray :: IO TmpPostgres.DB -> TestTree +-- testSelectArray = databasePropertyTest "Can SELECT Arrays (with aggregation)" \transaction -> do +-- rows <- forAll $ Gen.list (Range.linear 1 10) Gen.bool + +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run1 $ Rel8.select do +-- Rel8.many $ Rel8.values (map Rel8.lit rows) + +-- selected === rows + +-- selected' <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- a <- Rel8.catListTable =<< do +-- Rel8.many $ Rel8.values (map Rel8.lit rows) +-- b <- Rel8.catListTable =<< do +-- Rel8.many $ Rel8.values (map Rel8.lit rows) +-- pure (a, b) + +-- selected' === liftA2 (,) rows rows + + +-- data NestedMaybeTable f = NestedMaybeTable +-- { nmt1 :: Rel8.Column f Bool +-- , nmt2 :: Rel8.HMaybe f (TestTable f) +-- } +-- deriving stock Generic +-- deriving anyclass Rel8.Rel8able + + +-- deriving stock instance Eq (NestedMaybeTable Result) +-- deriving stock instance Ord (NestedMaybeTable Result) +-- deriving stock instance Show (NestedMaybeTable Result) + + +-- testNestedMaybeTable :: IO TmpPostgres.DB -> TestTree +-- testNestedMaybeTable = databasePropertyTest "Can nest MaybeTable within other tables" \transaction -> do +-- let example = NestedMaybeTable { nmt1 = True, nmt2 = Just (TestTable "Hi" True) } + +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run1 $ Rel8.select do +-- x <- Rel8.values [Rel8.lit example] +-- pure $ Rel8.maybeTable (Rel8.lit False) (\_ -> Rel8.lit True) (nmt2 x) + +-- selected === True + + +-- testEvaluate :: IO TmpPostgres.DB -> TestTree +-- testEvaluate = databasePropertyTest "evaluate has the evaluation order we expect" \transaction -> do + +-- transaction do +-- selected <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- x <- Rel8.values (Rel8.lit <$> ['a', 'b', 'c']) +-- y <- Rel8.evaluate (Rel8.nextval "test_seq") +-- pure (x, (y, y)) + +-- normalize selected === +-- [ ('a', (0, 0)) +-- , ('b', (1, 1)) +-- , ('c', (2, 2)) +-- ] - transaction do - result <- lift do - statement () $ Rel8.run1 $ Rel8.select do - pure $ Rel8.not_ $ Rel8.lit x - - result === not x - - -testBool :: IO TmpPostgres.DB -> TestTree -testBool = databasePropertyTest "ifThenElse_" \transaction -> do - (x, y, z) <- forAll $ liftA3 (,,) Gen.bool Gen.bool Gen.bool - - transaction do - result <- lift do - statement () $ Rel8.run1 $ Rel8.select do - pure $ Rel8.bool (Rel8.lit z) (Rel8.lit y) (Rel8.lit x) - - result === if x then y else z - - -testAp :: IO TmpPostgres.DB -> TestTree -testAp = databasePropertyTest "Cartesian product (<*>)" \transaction -> do - (rows1, rows2) <- forAll $ - liftA2 (,) - (Gen.list (Range.linear 1 10) genTestTable) - (Gen.list (Range.linear 1 10) genTestTable) - - transaction do - result <- lift do - statement () $ Rel8.run $ Rel8.select do - liftA2 (,) (Rel8.values (Rel8.lit <$> rows1)) (Rel8.values (Rel8.lit <$> rows2)) - - sort result === sort (liftA2 (,) rows1 rows2) - - -data Composite = Composite - { bool :: !Bool - , char :: !Text - , array :: ![Int32] - } - deriving stock (Eq, Show, Generic) - deriving (Rel8.DBType) via Rel8.Composite Composite - - -instance Rel8.DBComposite Composite where - compositeTypeName = "composite" - compositeFields = Rel8.namesFromLabels - - -testDBType :: IO TmpPostgres.DB -> TestTree -testDBType getTestDatabase = testGroup "DBType instances" - [ dbTypeTest "Bool" Gen.bool - , dbTypeTest "ByteString" $ Gen.bytes (Range.linear 0 128) - , dbTypeTest "CalendarDiffTime" genCalendarDiffTime - , dbTypeTest "Char" Gen.unicode - , dbTypeTest "CI Lazy Text" $ mk . Data.Text.Lazy.fromStrict <$> genText - , dbTypeTest "CI Text" $ mk <$> genText - , dbTypeTest "Composite" genComposite - , dbTypeTest "Day" genDay - , dbTypeTest "Double" $ (/ 10) . fromIntegral @Int @Double <$> Gen.integral (Range.linear (-100) 100) - , dbTypeTest "Fixed" $ toEnum @Centi <$> Gen.integral (Range.linear (-10000) 10000) - , dbTypeTest "Float" $ (/ 10) . fromIntegral @Int @Float <$> Gen.integral (Range.linear (-100) 100) - , dbTypeTest "Int32" $ Gen.integral @_ @Int32 Range.linearBounded - , dbTypeTest "Int64" $ Gen.integral @_ @Int64 Range.linearBounded - , dbTypeTest "Lazy ByteString" $ Data.ByteString.Lazy.fromStrict <$> Gen.bytes (Range.linear 0 128) - , dbTypeTest "Lazy Text" $ Data.Text.Lazy.fromStrict <$> genText - , dbTypeTest "LocalTime" genLocalTime - , dbTypeTest "Scientific" $ genScientific - , dbTypeTest "Text" genText - , dbTypeTest "TimeOfDay" genTimeOfDay - , dbTypeTest "UTCTime" $ UTCTime <$> genDay <*> genDiffTime - , dbTypeTest "UUID" $ Data.UUID.fromWords <$> genWord32 <*> genWord32 <*> genWord32 <*> genWord32 - , dbTypeTest "INet" genIPRange - , dbTypeTest "Value" genValue - , dbTypeTest "JSONEncoded" genJSONEncoded - , dbTypeTest "JSONBEncoded" genJSONBEncoded - ] - - where - dbTypeTest :: (Eq a, Show a, Rel8.DBType a, Rel8.ToExprs (Rel8.Expr a) a) => TestName -> Gen a -> TestTree - dbTypeTest name generator = testGroup name - [ databasePropertyTest name (t generator) getTestDatabase - , databasePropertyTest ("Maybe " <> name) (t (Gen.maybe generator)) getTestDatabase - ] - - t :: forall a. (Eq a, Show a, Rel8.Sql Rel8.DBType a, Rel8.ToExprs (Rel8.Expr a) a) - => Gen a - -> (TestT Transaction () -> PropertyT IO ()) - -> PropertyT IO () - t generator transaction = do - x <- forAll generator - y <- forAll generator - xss <- forAll $ Gen.list (Range.linear 0 10) (Gen.list (Range.linear 0 10) generator) - xsss <- forAll $ Gen.list (Range.linear 0 10) (Gen.list (Range.linear 0 10) (Gen.list (Range.linear 0 10) generator)) - - transaction do - res <- lift do - statement () $ Rel8.run1 $ Rel8.select do - pure (Rel8.litExpr x) - diff res (==) x - res' <- lift do - statement () $ Rel8.run1 $ Rel8.select $ Rel8.many $ Rel8.many do - Rel8.values [Rel8.litExpr x, Rel8.litExpr y] - diff res' (==) [[x, y]] - res3 <- lift do - statement () $ Rel8.run1 $ Rel8.select $ Rel8.many $ Rel8.many $ Rel8.many do - Rel8.values [Rel8.litExpr x, Rel8.litExpr y] - diff res3 (==) [[[x, y]]] - res'' <- lift do - statement () $ Rel8.run $ Rel8.select do - xs <- Rel8.catListTable (Rel8.listTable [Rel8.listTable [Rel8.litExpr x, Rel8.litExpr y]]) - Rel8.catListTable xs - diff res'' (==) [x, y] - res''' <- lift do - statement () $ Rel8.run $ Rel8.select do - xss' <- Rel8.catListTable (Rel8.listTable [Rel8.listTable [Rel8.listTable [Rel8.litExpr x, Rel8.litExpr y]]]) - xs <- Rel8.catListTable xss' - Rel8.catListTable xs - diff res''' (==) [x, y] - res'''' <- lift do - statement () $ Rel8.run1 $ Rel8.select $ - Rel8.aggregate Rel8.listCatExpr $ - Rel8.values $ map Rel8.litExpr xss - diff res'''' (==) (concat xss) - res''''' <- lift do - statement () $ Rel8.run1 $ Rel8.select $ - Rel8.aggregate Rel8.listCatExpr $ - Rel8.values $ map Rel8.litExpr xsss - diff res''''' (==) (concat xsss) - - transaction do - res <- lift do - statement x $ Rel8.prepared Rel8.run1 $ - Rel8.select @(Rel8.Expr _) . - pure - diff res (==) x - - res' <- lift do - statement [x, y] $ Rel8.prepared Rel8.run1 $ - 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 $ - 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 $ - 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]]] - - genScientific :: Gen Scientific - genScientific = (/ 10) . fromIntegral @Int @Scientific <$> Gen.integral (Range.linear (-100) 100) - - genComposite :: Gen Composite - genComposite = do - bool <- Gen.bool - char <- genText - array <- Gen.list (Range.linear 0 10) (Gen.int32 (Range.linear (-10000) 10000)) - pure Composite {..} - - genDay :: Gen Day - genDay = do - year <- Gen.integral (Range.linear 1970 3000) - month <- Gen.integral (Range.linear 1 12) - day <- Gen.integral (Range.linear 1 31) - Gen.just $ pure $ fromGregorianValid year month day - - genCalendarDiffTime :: Gen CalendarDiffTime - genCalendarDiffTime = do - -- hardcoded to 0 because Hasql's 'interval' decoder needs to return a - -- CalendarDiffTime for this to be properly round-trippable - months <- pure 0 -- Gen.integral (Range.linear 0 120) - diffTime <- secondsToNominalDiffTime . MkFixed . (* 1000000) <$> Gen.integral (Range.linear 0 2147483647999999) - pure $ CalendarDiffTime months diffTime - - genDiffTime :: Gen DiffTime - genDiffTime = secondsToDiffTime <$> Gen.integral (Range.linear 0 86401) - - genTimeOfDay :: Gen TimeOfDay - genTimeOfDay = do - hour <- Gen.integral (Range.linear 0 23) - minute <- Gen.integral (Range.linear 0 59) - sec <- fromIntegral @Int <$> Gen.integral (Range.linear 0 59) - Gen.just $ pure $ makeTimeOfDayValid hour minute sec - - genLocalTime = LocalTime <$> genDay <*> genTimeOfDay - - genWord32 :: Gen Word32 - genWord32 = Gen.integral Range.linearBounded - - genIPRange :: Gen (Data.IP.IPRange) - genIPRange = - Gen.choice - [ Data.IP.IPv4Range <$> (Data.IP.makeAddrRange <$> genIPv4 <*> genIP4Mask) - , Data.IP.IPv6Range <$> (Data.IP.makeAddrRange <$> genIPv6 <*> genIP6Mask) - ] - where - genIP4Mask :: Gen Int - genIP4Mask = Gen.integral (Range.linearFrom 0 0 32) - - genIPv4 :: Gen Data.IP.IPv4 - genIPv4 = Data.IP.toIPv4w <$> genWord32 - - genIP6Mask :: Gen Int - genIP6Mask = Gen.integral (Range.linearFrom 0 0 128) - - genIPv6 :: Gen (Data.IP.IPv6) - genIPv6 = Data.IP.toIPv6w <$> ((,,,) <$> genWord32 <*> genWord32 <*> genWord32 <*> genWord32) - - genKey :: Gen Aeson.Key - genKey = Aeson.Key.fromText <$> genText - - genValue :: Gen Aeson.Value - genValue = Gen.recursive Gen.choice - [ pure Aeson.Null - , Aeson.Bool <$> Gen.bool - , Aeson.Number <$> genScientific - , Aeson.String <$> genText - ] - [ Aeson.Object . Aeson.KeyMap.fromMap <$> Gen.map (Range.linear 0 10) ((,) <$> genKey <*> genValue) - , Aeson.Array . Vector.fromList <$> Gen.list (Range.linear 0 10) genValue - ] - - genJSONEncoded = Rel8.JSONEncoded <$> genValue - genJSONBEncoded = Rel8.JSONBEncoded <$> genValue - - -testDBEq :: IO TmpPostgres.DB -> TestTree -testDBEq getTestDatabase = testGroup "DBEq instances" - [ dbEqTest "Bool" Gen.bool - , dbEqTest "Int32" $ Gen.integral @_ @Int32 Range.linearBounded - , dbEqTest "Int64" $ Gen.integral @_ @Int64 Range.linearBounded - , dbEqTest "Text" $ genText - ] - - where - dbEqTest :: (Eq a, Show a, Rel8.DBEq a) => TestName -> Gen a -> TestTree - dbEqTest name generator = testGroup name - [ databasePropertyTest name (t generator) getTestDatabase - , databasePropertyTest ("Maybe " <> name) (t (Gen.maybe generator)) getTestDatabase - ] - - t :: forall a. (Eq a, Show a, Rel8.Sql Rel8.DBEq a) - => Gen a - -> (TestT Transaction () -> PropertyT IO ()) - -> PropertyT IO () - t generator transaction = do - (x, y) <- forAll (liftA2 (,) generator generator) - - transaction do - res <- lift do - statement () $ Rel8.run1 $ Rel8.select do - pure $ Rel8.litExpr x Rel8.==. Rel8.litExpr y - res === (x == y) - - -genText :: Gen Text -genText = removeNull <$> Gen.text (Range.linear 0 10) Gen.unicode - where - -- | Postgres doesn't support the NULL character (not to be confused with a NULL value) inside strings. - removeNull :: Text -> Text - removeNull = T.filter (/= '\0') - - - -testTableEquality :: IO TmpPostgres.DB -> TestTree -testTableEquality = databasePropertyTest "TestTable equality" \transaction -> do - (x, y) <- forAll $ liftA2 (,) genTestTable genTestTable - - transaction do - eq <- lift do - statement () $ Rel8.run1 $ Rel8.select do - pure $ Rel8.lit x Rel8.==: Rel8.lit y - - eq === (x == y) - - -testFromRational :: IO TmpPostgres.DB -> TestTree -testFromRational = databasePropertyTest "fromRational" \transaction -> do - numerator <- forAll $ Gen.int64 Range.linearBounded - denominator <- forAll $ Gen.int64 $ Range.linear 1 maxBound - - let - rational = toInteger numerator % toInteger denominator - double = fromRational @Double rational - - transaction do - result <- lift do - statement () $ Rel8.run1 $ Rel8.select do - pure $ fromRational rational - diff result (~=) double - where - wholeDigits x = fromIntegral $ length $ show $ round @_ @Integer x - -- A Double gives us between 15-17 decimal digits of precision. - -- It's tempting to say that two numbers are equal if they differ by less than 1e15. - -- But this doesn't hold. - -- The precision is split between the whole numer part and the decimal part of the number. - -- For instance, a number between 10 and 99 only has around 13 digits of precision in its decimal part. - -- Postgres and Haskell show differing amounts of digits in these cases, - a ~= b = abs (a - b) < 10 ** (-15 + wholeDigits a) - infix 4 ~= - - -testCatMaybeTable :: IO TmpPostgres.DB -> TestTree -testCatMaybeTable = databasePropertyTest "catMaybeTable" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable - - transaction do - selected <- lift do - statement () $ Rel8.run $ Rel8.select do - testTable <- Rel8.values $ Rel8.lit <$> rows - Rel8.catMaybeTable $ Rel8.bool Rel8.nothingTable (pure testTable) (testTableColumn2 testTable) - - sort selected === sort (filter testTableColumn2 rows) - - -testCatMaybe :: IO TmpPostgres.DB -> TestTree -testCatMaybe = databasePropertyTest "catMaybe" \transaction -> evalM do - rows <- forAll $ Gen.list (Range.linear 0 10) $ Gen.maybe Gen.bool - - transaction do - selected <- lift do - statement () $ Rel8.run $ Rel8.select do - Rel8.catNull =<< Rel8.values (map Rel8.lit rows) - - sort selected === sort (catMaybes rows) - - -testMaybeTable :: IO TmpPostgres.DB -> TestTree -testMaybeTable = databasePropertyTest "maybeTable" \transaction -> evalM do - (rows, def) <- forAll $ liftA2 (,) (Gen.list (Range.linear 0 10) genTestTable) genTestTable - - transaction do - selected <- lift do - statement () $ Rel8.run $ Rel8.select do - Rel8.maybeTable (Rel8.lit def) id <$> Rel8.optional (Rel8.values (Rel8.lit <$> rows)) - - case rows of - [] -> selected === [def] - _ -> sort selected === sort rows - - -testAggregateMaybeTable :: IO TmpPostgres.DB -> TestTree -testAggregateMaybeTable = databasePropertyTest "aggregateMaybeTable" \transaction -> evalM do - rows <- forAll $ Gen.list (Range.linear 0 10) (Gen.maybe (Gen.int64 (Range.linear 0 10))) - - let - aggregate = go 0 False False - where - go !n nothing _ (Just a : as) = go (n + a) nothing True as - go n _ just (Nothing : as) = go n True just as - go _ False False [] = [] - go _ True False [] = [Nothing] - go n False True [] = [Just n] - go n True True [] = [Nothing, Just n] - - transaction do - selected <- lift do - statement () $ Rel8.run $ Rel8.select do - Rel8.aggregate1 (Rel8.aggregateMaybeTable Rel8.sum) $ Rel8.values (Rel8.lit <$> rows) - - sort selected === aggregate rows - - -data TwoTestTables f = - TwoTestTables - { testTable1 :: TestTable f - , testTable2 :: TestTable f - } - deriving stock Generic - deriving anyclass Rel8.Rel8able - - -deriving stock instance Eq (TwoTestTables Result) -deriving stock instance Ord (TwoTestTables Result) -deriving stock instance Show (TwoTestTables Result) - - -testNestedTables :: IO TmpPostgres.DB -> TestTree -testNestedTables = databasePropertyTest "Nested TestTables" \transaction -> evalM do - rows <- forAll do - Gen.list (Range.linear 0 10) $ - liftA2 TwoTestTables genTestTable genTestTable - - transaction do - selected <- lift do - statement () $ Rel8.run $ Rel8.select do - Rel8.values (Rel8.lit <$> rows) - - sort selected === sort rows - - -testMaybeTableApplicative :: IO TmpPostgres.DB -> TestTree -testMaybeTableApplicative = databasePropertyTest "MaybeTable (<*>)" \transaction -> evalM do - rows1 <- genRows - rows2 <- genRows - - transaction do - selected <- lift do - statement () $ Rel8.run $ Rel8.select do - as <- Rel8.optional (Rel8.values (Rel8.lit <$> rows1)) - bs <- Rel8.optional (Rel8.values (Rel8.lit <$> rows2)) - pure $ liftA2 (,) as bs - - case (rows1, rows2) of - ([], []) -> selected === [Nothing] - ([], bs) -> selected === (Nothing <$ bs) - (as, []) -> selected === (Nothing <$ as) - (as, bs) -> sort selected === sort (Just <$> liftA2 (,) as bs) - where - genRows :: PropertyT IO [TestTable Result] - genRows = forAll do - Gen.list (Range.linear 0 10) $ liftA2 TestTable genText (pure True) - - -genTestTable :: Gen (TestTable Result) -genTestTable = do - testTableColumn1 <- Gen.text (Range.linear 0 5) Gen.alphaNum - testTableColumn2 <- Gen.bool - return TestTable{..} - - -testUpdate :: IO TmpPostgres.DB -> TestTree -testUpdate = databasePropertyTest "Can UPDATE TestTable" \transaction -> do - rows <- forAll $ Gen.map (Range.linear 0 5) $ liftA2 (,) genTestTable genTestTable - - transaction do - selected <- lift do - statement () $ Rel8.run_ $ Rel8.insert Rel8.Insert - { into = testTableSchema - , rows = Rel8.values $ map Rel8.lit $ Map.keys rows - , onConflict = Rel8.DoNothing Nothing - , returning = Rel8.NoReturning - } - - statement () $ Rel8.run_ $ Rel8.update Rel8.Update - { target = testTableSchema - , from = pure () - , set = \_ r -> - let updates = map (bimap Rel8.lit Rel8.lit) $ Map.toList rows - in - foldl - ( \e (x, y) -> - Rel8.bool - e - y - ( testTableColumn1 r Rel8.==. testTableColumn1 x Rel8.&&. - testTableColumn2 r Rel8.==. testTableColumn2 x - ) - ) - r - updates - , updateWhere = \_ _ -> Rel8.lit True - , returning = Rel8.NoReturning - } - - statement () $ Rel8.run $ Rel8.select do - Rel8.each testTableSchema - - sort selected === sort (Map.elems rows) - - cover 1 "Empty" $ null rows - cover 1 "Singleton" $ null $ drop 1 $ Map.keys rows - cover 1 ">1 row" $ not $ null $ drop 1 $ Map.keys rows - - -testDelete :: IO TmpPostgres.DB -> TestTree -testDelete = databasePropertyTest "Can DELETE TestTable" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 0 5) genTestTable - - transaction do - (deleted, selected) <- lift do - statement () $ Rel8.run_ $ Rel8.insert Rel8.Insert - { into = testTableSchema - , rows = Rel8.values $ map Rel8.lit rows - , onConflict = Rel8.DoNothing Nothing - , returning = Rel8.NoReturning - } - - deleted <- statement () $ Rel8.run $ Rel8.delete Rel8.Delete - { from = testTableSchema - , using = pure () - , deleteWhere = const testTableColumn2 - , returning = Rel8.Returning id - } - - selected <- statement () $ Rel8.run $ Rel8.select do - Rel8.each testTableSchema - - pure (deleted, selected) - - sort (deleted <> selected) === sort rows - - -testWithStatement :: IO TmpPostgres.DB -> TestTree -testWithStatement genTestDatabase = - testGroup "WITH" - [ selectUnionInsert genTestDatabase - , rowsAffectedNoReturning genTestDatabase - , rowsAffectedReturing genTestDatabase - , pureQuery genTestDatabase - ] - where - selectUnionInsert = - databasePropertyTest "Can UNION results of SELECT with results of INSERT" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 0 50) genTestTable - - transaction do - rows' <- lift do - statement () $ Rel8.run $ do - values <- Rel8.select $ Rel8.values $ map Rel8.lit rows - - inserted <- Rel8.insert $ Rel8.Insert - { into = testTableSchema - , rows = values - , onConflict = Rel8.DoNothing Nothing - , returning = Rel8.Returning id - } - - pure $ values <> inserted - - sort rows' === sort (rows <> rows) - - rowsAffectedNoReturning = - databasePropertyTest "Can read rows affected from INSERT without RETURNING" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 0 50) genTestTable - - transaction do - affected <- lift do - statement () $ Rel8.runN $ do - Rel8.insert $ Rel8.Insert - { into = testTableSchema - , rows = Rel8.values $ map Rel8.lit rows - , onConflict = Rel8.DoNothing Nothing - , returning = Rel8.NoReturning - } - - length rows === fromIntegral affected - - rowsAffectedReturing = - databasePropertyTest "Can read rows affected from INSERT with RETURNING" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 0 50) genTestTable - - transaction do - affected <- lift do - statement () $ Rel8.runN $ void $ do - Rel8.insert $ Rel8.Insert - { into = testTableSchema - , rows = Rel8.values $ map Rel8.lit rows - , onConflict = Rel8.DoNothing Nothing - , returning = Rel8.Returning id - } - - length rows === fromIntegral affected - - pureQuery = - databasePropertyTest "Can read pure Query" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 0 50) genTestTable - - transaction do - rows' <- lift do - statement () $ Rel8.run $ pure do - Rel8.values $ map Rel8.lit rows - - sort rows === sort rows' - - - -data UniqueTable f = UniqueTable - { uniqueTableKey :: Rel8.Column f Text - , uniqueTableValue :: Rel8.Column f Text - } - deriving stock Generic - deriving anyclass Rel8.Rel8able - - -deriving stock instance Eq (UniqueTable Result) -deriving stock instance Ord (UniqueTable Result) -deriving stock instance Show (UniqueTable Result) - - -uniqueTableSchema :: Rel8.TableSchema (UniqueTable Rel8.Name) -uniqueTableSchema = - Rel8.TableSchema - { name = "unique_table" - , columns = UniqueTable - { uniqueTableKey = "key" - , uniqueTableValue = "value" - } - } - - -genUniqueTable :: Gen (UniqueTable Result) -genUniqueTable = do - uniqueTableKey <- Gen.text (Range.linear 0 5) Gen.alphaNum - uniqueTableValue <- Gen.text (Range.linear 0 5) Gen.alphaNum - pure UniqueTable {..} - - -testUpsert :: IO TmpPostgres.DB -> TestTree -testUpsert = databasePropertyTest "Can UPSERT UniqueTable" \transaction -> do - as <- unique $ forAll $ Gen.list (Range.linear 0 20) genUniqueTable - bs <- unique $ forAll $ Gen.list (Range.linear 0 20) genUniqueTable - - transaction do - selected <- lift do - statement () $ Rel8.run_ $ Rel8.insert Rel8.Insert - { into = uniqueTableSchema - , rows = Rel8.values $ Rel8.lit <$> as - , onConflict = Rel8.DoNothing Nothing - , returning = Rel8.NoReturning - } +-- selected' <- lift do +-- statement () $ Rel8.run $ Rel8.select do +-- x <- Rel8.values (Rel8.lit <$> ['a', 'b', 'c']) +-- y <- Rel8.values (Rel8.lit <$> ['d', 'e', 'f']) +-- z <- Rel8.evaluate (Rel8.nextval "test_seq") +-- pure ((x, y), (z, z)) - statement () $ Rel8.run_ $ Rel8.insert Rel8.Insert - { into = uniqueTableSchema - , rows = Rel8.values $ Rel8.lit <$> bs - , onConflict = Rel8.DoUpdate Rel8.Upsert - { conflict = - Rel8.OnIndex - Rel8.Index - { columns = uniqueTableKey - , predicate = Nothing - } - , set = \UniqueTable {uniqueTableValue} old -> old {uniqueTableValue} - , updateWhere = \_ _ -> Rel8.true - } - , returning = Rel8.NoReturning - } - - statement () $ Rel8.run $ Rel8.select do - Rel8.each uniqueTableSchema - - fromUniqueTables selected === fromUniqueTables bs <> fromUniqueTables as - where - unique = fmap (nubOrdOn uniqueTableKey) - fromUniqueTables = Map.fromList . map \(UniqueTable key value) -> (key, value) - - -newtype HKNestedPair f = HKNestedPair { pairOne :: (TestTable f, TestTable f) } - deriving stock Generic - deriving anyclass Rel8.Rel8able - -deriving stock instance Eq (HKNestedPair Result) -deriving stock instance Ord (HKNestedPair Result) -deriving stock instance Show (HKNestedPair Result) - - -testSelectNestedPairs :: IO TmpPostgres.DB -> TestTree -testSelectNestedPairs = databasePropertyTest "Can SELECT nested pairs" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 0 10) $ HKNestedPair <$> liftA2 (,) genTestTable genTestTable - - transaction do - selected <- lift do - statement () $ Rel8.run $ Rel8.select do - Rel8.values $ map Rel8.lit rows - - sort selected === sort rows - - -testSelectArray :: IO TmpPostgres.DB -> TestTree -testSelectArray = databasePropertyTest "Can SELECT Arrays (with aggregation)" \transaction -> do - rows <- forAll $ Gen.list (Range.linear 1 10) Gen.bool - - transaction do - selected <- lift do - statement () $ Rel8.run1 $ Rel8.select do - Rel8.many $ Rel8.values (map Rel8.lit rows) - - selected === rows - - selected' <- lift do - statement () $ Rel8.run $ Rel8.select do - a <- Rel8.catListTable =<< do - Rel8.many $ Rel8.values (map Rel8.lit rows) - b <- Rel8.catListTable =<< do - Rel8.many $ Rel8.values (map Rel8.lit rows) - pure (a, b) - - selected' === liftA2 (,) rows rows - - -data NestedMaybeTable f = NestedMaybeTable - { nmt1 :: Rel8.Column f Bool - , nmt2 :: Rel8.HMaybe f (TestTable f) - } - deriving stock Generic - deriving anyclass Rel8.Rel8able - - -deriving stock instance Eq (NestedMaybeTable Result) -deriving stock instance Ord (NestedMaybeTable Result) -deriving stock instance Show (NestedMaybeTable Result) - - -testNestedMaybeTable :: IO TmpPostgres.DB -> TestTree -testNestedMaybeTable = databasePropertyTest "Can nest MaybeTable within other tables" \transaction -> do - let example = NestedMaybeTable { nmt1 = True, nmt2 = Just (TestTable "Hi" True) } - - transaction do - selected <- lift do - statement () $ Rel8.run1 $ Rel8.select do - x <- Rel8.values [Rel8.lit example] - pure $ Rel8.maybeTable (Rel8.lit False) (\_ -> Rel8.lit True) (nmt2 x) - - selected === True - - -testEvaluate :: IO TmpPostgres.DB -> TestTree -testEvaluate = databasePropertyTest "evaluate has the evaluation order we expect" \transaction -> do - - transaction do - selected <- lift do - statement () $ Rel8.run $ Rel8.select do - x <- Rel8.values (Rel8.lit <$> ['a', 'b', 'c']) - y <- Rel8.evaluate (Rel8.nextval "test_seq") - pure (x, (y, y)) - - normalize selected === - [ ('a', (0, 0)) - , ('b', (1, 1)) - , ('c', (2, 2)) - ] - - selected' <- lift do - statement () $ Rel8.run $ Rel8.select do - x <- Rel8.values (Rel8.lit <$> ['a', 'b', 'c']) - y <- Rel8.values (Rel8.lit <$> ['d', 'e', 'f']) - z <- Rel8.evaluate (Rel8.nextval "test_seq") - pure ((x, y), (z, z)) +-- normalize selected' === +-- [ (('a', 'd'), (0, 0)) +-- , (('a', 'e'), (1, 1)) +-- , (('a', 'f'), (2, 2)) +-- , (('b', 'd'), (3, 3)) +-- , (('b', 'e'), (4, 4)) +-- , (('b', 'f'), (5, 5)) +-- , (('c', 'd'), (6, 6)) +-- , (('c', 'e'), (7, 7)) +-- , (('c', 'f'), (8, 8)) +-- ] - normalize selected' === - [ (('a', 'd'), (0, 0)) - , (('a', 'e'), (1, 1)) - , (('a', 'f'), (2, 2)) - , (('b', 'd'), (3, 3)) - , (('b', 'e'), (4, 4)) - , (('b', 'f'), (5, 5)) - , (('c', 'd'), (6, 6)) - , (('c', 'e'), (7, 7)) - , (('c', 'f'), (8, 8)) - ] +-- where +-- normalize :: [(x, (Int64, Int64))] -> [(x, (Int64, Int64))] +-- normalize [] = [] +-- normalize xs@((_, (i, _)) : _) = map (fmap (\(a, b) -> (a - i, b - i))) xs - where - normalize :: [(x, (Int64, Int64))] -> [(x, (Int64, Int64))] - normalize [] = [] - normalize xs@((_, (i, _)) : _) = map (fmap (\(a, b) -> (a - i, b - i))) xs +main = return () diff --git a/tests/Rel8/Generic/Rel8able/Test.hs b/tests/Rel8/Generic/Rel8able/Test.hs index 601c9215..5178a941 100644 --- a/tests/Rel8/Generic/Rel8able/Test.hs +++ b/tests/Rel8/Generic/Rel8able/Test.hs @@ -10,18 +10,23 @@ {-# language StandaloneDeriving #-} {-# language StandaloneKindSignatures #-} {-# language TypeApplications #-} +{-# language TemplateHaskell #-} {-# language TypeFamilies #-} {-# language TypeOperators #-} {-# language RecordWildCards #-} {-# language UndecidableInstances #-} +{-# language TypeApplications #-} +{-# language InstanceSigs #-} +{-# language PartialTypeSignatures #-} -{-# options_ghc -O0 #-} +{-# options_ghc -O0 -ddump-simpl -ddump-to-file -ddump-ds -ddump-splices #-} module Rel8.Generic.Rel8able.Test ( module Rel8.Generic.Rel8able.Test ) where +import Rel8.Generic.Rel8able -- aeson import Data.Aeson ( Value(..) ) import qualified Data.Aeson.KeyMap as Aeson @@ -87,6 +92,8 @@ import Data.Time.LocalTime , LocalTime(..) , TimeOfDay(..) ) +import Rel8 +import Rel8.TH ( deriveRel8able ) -- text import Data.Text ( Text ) @@ -110,445 +117,466 @@ makeSchema name = TableSchema } -data TableDuplicate f = TableDuplicate - { foo :: TablePair f - , bar :: TablePair f - } - deriving stock Generic - deriving anyclass Rel8able - -tableDuplicate :: TableSchema (TableDuplicate Name) -tableDuplicate = TableSchema - { name = "tableDuplicate" - , columns = namesFromLabelsWith NonEmpty.last - } - - data TableTest f = TableTest { foo :: Column f Bool , bar :: Column f (Maybe Bool) } - deriving stock Generic - deriving anyclass Rel8able + -- deriving stock Generic + -- deriving anyclass Rel8able deriving stock instance f ~ Result => Show (TableTest f) deriving stock instance f ~ Result => Eq (TableTest f) deriving stock instance f ~ Result => Ord (TableTest f) + -- deriving stock Generic + -- deriving anyclass Rel8able + +deriveRel8able ''TableTest tableTest :: TableSchema (TableTest Name) tableTest = makeSchema "tableTest" -genTableTest :: Hedgehog.MonadGen m => m (TableTest Result) -genTableTest = TableTest <$> Gen.bool <*> Gen.maybe Gen.bool +-- genTableTest :: Hedgehog.MonadGen m => m (TableTest Result) +-- genTableTest = TableTest <$> Gen.bool <*> Gen.maybe Gen.bool data TablePair f = TablePair { foo :: Column f Bool - , bars :: (Column f Text, Column f Text) + -- , bars :: (Column f Text, Column f Text) + , bars :: TableTest f } - deriving stock Generic - deriving anyclass Rel8able + -- deriving stock Generic + -- deriving anyclass Rel8able deriving stock instance f ~ Result => Show (TablePair f) deriving stock instance f ~ Result => Eq (TablePair f) deriving stock instance f ~ Result => Ord (TablePair f) +deriveRel8able ''TablePair +x :: _ +x = undefined :: FromExprs (Text, Text) + tablePair :: TableSchema (TablePair Name) tablePair = makeSchema "tablePair" -genTablePair :: Hedgehog.MonadGen m => m (TablePair Result) -genTablePair = TablePair - <$> Gen.bool - <*> liftA2 (,) (Gen.text (Range.linear 0 10) Gen.alphaNum) (Gen.text (Range.linear 0 10) Gen.alphaNum) +-- genTablePair :: Hedgehog.MonadGen m => m (TablePair Result) +-- genTablePair = TablePair +-- <$> Gen.bool +-- <*> liftA2 (,) (Gen.text (Range.linear 0 10) Gen.alphaNum) (Gen.text (Range.linear 0 10) Gen.alphaNum) + +data TableDuplicate f = TableDuplicate + { foo :: TablePair f + , bar :: TablePair f + } + -- deriving stock Generic + -- deriving anyclass Rel8able +deriveRel8able ''TableDuplicate + +tableDuplicate :: TableSchema (TableDuplicate Name) +tableDuplicate = TableSchema + { name = "tableDuplicate" + , columns = namesFromLabelsWith NonEmpty.last + } + data TableMaybe f = TableMaybe { foo :: Column f [Maybe Bool] , bars :: HMaybe f (TablePair f, TablePair f) } - deriving stock Generic - deriving anyclass Rel8able + -- deriving stock Generic + -- deriving anyclass Rel8able deriving stock instance f ~ Result => Show (TableMaybe f) deriving stock instance f ~ Result => Eq (TableMaybe f) deriving stock instance f ~ Result => Ord (TableMaybe f) +deriveRel8able ''TableMaybe + tableMaybe :: TableSchema (TableMaybe Name) tableMaybe = makeSchema "tableMaybe" -genTableMaybe :: Hedgehog.MonadGen m => m (TableMaybe Result) -genTableMaybe = TableMaybe - <$> Gen.list (Range.linear 0 10) (Gen.maybe Gen.bool) - <*> Gen.maybe (liftA2 (,) genTablePair genTablePair) +-- genTableMaybe :: Hedgehog.MonadGen m => m (TableMaybe Result) +-- genTableMaybe = TableMaybe +-- <$> Gen.list (Range.linear 0 10) (Gen.maybe Gen.bool) +-- <*> Gen.maybe (liftA2 (,) genTablePair genTablePair) + -- deriving stock Generic + -- deriving anyclass Rel8able + data TableEither f = TableEither { foo :: Column f Bool , bars :: HEither f (HMaybe f (TablePair f, TablePair f)) (Column f Char) } - deriving stock Generic - deriving anyclass Rel8able + -- deriving stock Generic + -- deriving anyclass Rel8able deriving stock instance f ~ Result => Show (TableEither f) deriving stock instance f ~ Result => Eq (TableEither f) deriving stock instance f ~ Result => Ord (TableEither f) +deriveRel8able ''TableEither + tableEither :: TableSchema (TableEither Name) tableEither = makeSchema "tableEither" -genTableEither :: Hedgehog.MonadGen m => m (TableEither Result) -genTableEither = TableEither - <$> Gen.bool - <*> Gen.either (Gen.maybe $ liftA2 (,) genTablePair genTablePair) Gen.alphaNum +-- genTableEither :: Hedgehog.MonadGen m => m (TableEither Result) +-- genTableEither = TableEither +-- <$> Gen.bool +-- <*> Gen.either (Gen.maybe $ liftA2 (,) genTablePair genTablePair) Gen.alphaNum data TableThese f = TableThese { foo :: Column f Bool , bars :: HThese f (TableMaybe f) (TableEither f) } - deriving stock Generic - deriving anyclass Rel8able + -- deriving stock Generic + -- deriving anyclass Rel8able deriving stock instance f ~ Result => Show (TableThese f) deriving stock instance f ~ Result => Eq (TableThese f) deriving stock instance f ~ Result => Ord (TableThese f) +deriveRel8able ''TableThese + tableThese :: TableSchema (TableThese Name) tableThese = makeSchema "tableThese" -genTableThese :: Hedgehog.MonadGen m => m (TableThese Result) -genTableThese = TableThese - <$> Gen.bool - <*> Gen.choice - [ This <$> genTableMaybe - , That <$> genTableEither - , These <$> genTableMaybe <*> genTableEither - ] +-- genTableThese :: Hedgehog.MonadGen m => m (TableThese Result) +-- genTableThese = TableThese +-- <$> Gen.bool +-- <*> Gen.choice +-- [ This <$> genTableMaybe +-- , That <$> genTableEither +-- , These <$> genTableMaybe <*> genTableEither +-- ] data TableList f = TableList { foo :: Column f Bool , bars :: HList f (TableThese f) } - deriving stock Generic - deriving anyclass Rel8able + -- deriving stock Generic + -- deriving anyclass Rel8able deriving stock instance f ~ Result => Show (TableList f) deriving stock instance f ~ Result => Eq (TableList f) deriving stock instance f ~ Result => Ord (TableList f) -tableList :: TableSchema (TableList Name) -tableList = makeSchema "tableList" - -genTableList :: Hedgehog.MonadGen m => m (TableList Result) -genTableList = TableList - <$> Gen.bool - <*> Gen.list (Range.linear 0 10) genTableThese - - -data TableNonEmpty f = TableNonEmpty - { foo :: Column f Bool - , bars :: HNonEmpty f (TableList f, TableMaybe f) - } - deriving stock Generic - deriving anyclass Rel8able -deriving stock instance f ~ Result => Show (TableNonEmpty f) -deriving stock instance f ~ Result => Eq (TableNonEmpty f) -deriving stock instance f ~ Result => Ord (TableNonEmpty f) - -tableNonEmpty :: TableSchema (TableNonEmpty Name) -tableNonEmpty = makeSchema "tableNonEmpty" - -genTableNonEmpty :: Hedgehog.MonadGen m => m (TableNonEmpty Result) -genTableNonEmpty = TableNonEmpty - <$> Gen.bool - <*> Gen.nonEmpty (Range.linear 0 10) (liftA2 (,) genTableList genTableMaybe) - - -data TableNest f = TableNest - { foo :: Column f Bool - , bars :: HList f (HMaybe f (TablePair f)) - } - deriving stock Generic - deriving anyclass Rel8able -deriving stock instance f ~ Result => Show (TableNest f) -deriving stock instance f ~ Result => Eq (TableNest f) -deriving stock instance f ~ Result => Ord (TableNest f) - -tableNest :: TableSchema (TableNest Name) -tableNest = makeSchema "tableNest" - -genTableNest :: Hedgehog.MonadGen m => m (TableNest Result) -genTableNest = TableNest - <$> Gen.bool - <*> Gen.list (Range.linear 0 10) (Gen.maybe genTablePair) - - -data S3Object = S3Object - { bucketName :: Text - , objectKey :: Text - } - deriving stock (Generic, Show, Eq, Ord) - - -instance x ~ HKD S3Object Expr => ToExprs x S3Object - - -data HKDSum = HKDSumA Text | HKDSumB Bool Char | HKDSumC - deriving stock (Generic, Show, Eq, Ord) - - -instance x ~ HKD HKDSum Expr => ToExprs x HKDSum - -genHKDSum :: Hedgehog.MonadGen m => m HKDSum -genHKDSum = Gen.choice - [ HKDSumA <$> Gen.text (Range.linear 0 10) Gen.alpha - , HKDSumB <$> Gen.bool <*> Gen.alpha - , pure HKDSumC - ] - -data HKDTest f = HKDTest - { s3Object :: Lift f S3Object - , hkdSum :: Lift f HKDSum - } - deriving stock Generic - deriving anyclass Rel8able -deriving stock instance f ~ Result => Show (HKDTest f) -deriving stock instance f ~ Result => Eq (HKDTest f) -deriving stock instance f ~ Result => Ord (HKDTest f) - -genHKDTest :: Hedgehog.MonadGen m => m (HKDTest Result) -genHKDTest = HKDTest - <$> liftA2 S3Object (Gen.text (Range.linear 0 10) Gen.alpha) (Gen.text (Range.linear 0 10) Gen.alpha) - <*> genHKDSum - -data NonRecord f = NonRecord - (Column f Bool) - (Column f Char) - (Column f Char) - (Column f Char) - (Column f Char) - (Column f Char) - (Column f Char) - (Column f Char) - (Column f Char) - (Column f Char) - deriving stock Generic - deriving anyclass Rel8able -deriving stock instance f ~ Result => Show (NonRecord f) -deriving stock instance f ~ Result => Eq (NonRecord f) -deriving stock instance f ~ Result => Ord (NonRecord f) - -nonRecord :: TableSchema (NonRecord Name) -nonRecord = makeSchema "nonRecord" - -genNonRecord :: Hedgehog.MonadGen m => m (NonRecord Result) -genNonRecord = NonRecord - <$> Gen.bool - <*> Gen.alpha - <*> Gen.alpha - <*> Gen.alpha - <*> Gen.alpha - <*> Gen.alpha - <*> Gen.alpha - <*> Gen.alpha - <*> Gen.alpha - <*> Gen.alpha - - -data TableSum f - = TableSumA (Column f Bool) (Column f Text) - | TableSumB - | TableSumC (Column f Text) - deriving stock Generic -deriving stock instance f ~ Result => Show (TableSum f) -deriving stock instance f ~ Result => Eq (TableSum f) -deriving stock instance f ~ Result => Ord (TableSum f) - - -genTableSum :: Hedgehog.MonadGen m => m (HADT Result TableSum) -genTableSum = Gen.choice - [ TableSumA <$> Gen.bool <*> Gen.text (Range.linear 0 10) Gen.alpha - , pure TableSumB - , TableSumC <$> Gen.text (Range.linear 0 10) Gen.alpha - ] - - -data BarbieSum f - = BarbieSumA (f Bool) (f Text) - | BarbieSumB - | BarbieSumC (f Text) - deriving stock Generic -deriving stock instance f ~ Result => Show (BarbieSum f) -deriving stock instance f ~ Result => Eq (BarbieSum f) -deriving stock instance f ~ Result => Ord (BarbieSum f) - - -genBarbieSum :: Hedgehog.MonadGen m => m (BarbieSum Result) -genBarbieSum = Gen.choice - [ BarbieSumA <$> fmap Identity Gen.bool <*> fmap Identity (Gen.text (Range.linear 0 10) Gen.alpha) - , pure BarbieSumB - , BarbieSumC <$> fmap Identity (Gen.text (Range.linear 0 10) Gen.alpha) - ] - - -data TableProduct f = TableProduct - { sum :: HADT f BarbieSum - , list :: TableList f - , foos :: HList f (HADT f TableSum, Lift f HKDSum, HKDTest f) - } - deriving stock Generic - deriving anyclass Rel8able -deriving stock instance f ~ Result => Show (TableProduct f) -deriving stock instance f ~ Result => Eq (TableProduct f) -deriving stock instance f ~ Result => Ord (TableProduct f) - -tableProduct :: TableSchema (TableProduct Name) -tableProduct = makeSchema "tableProduct" - -genTableProduct :: Hedgehog.MonadGen m => m (TableProduct Result) -genTableProduct = TableProduct - <$> genBarbieSum - <*> genTableList - <*> Gen.list (Range.linear 0 10) (liftA3 (,,) genTableSum genHKDSum genHKDTest) - --- tableProduct :: TableProduct Name +deriveRel8able ''TableList + +-- tableList :: TableSchema (TableList Name) +-- tableList = makeSchema "tableList" + +-- -- genTableList :: Hedgehog.MonadGen m => m (TableList Result) +-- -- genTableList = TableList +-- -- <$> Gen.bool +-- -- <*> Gen.list (Range.linear 0 10) genTableThese + + +-- data TableNonEmpty f = TableNonEmpty +-- { foo :: Column f Bool +-- , bars :: HNonEmpty f (TableList f, TableMaybe f) +-- } +-- deriving stock Generic +-- deriving anyclass Rel8able +-- deriving stock instance f ~ Result => Show (TableNonEmpty f) +-- deriving stock instance f ~ Result => Eq (TableNonEmpty f) +-- deriving stock instance f ~ Result => Ord (TableNonEmpty f) + +-- tableNonEmpty :: TableSchema (TableNonEmpty Name) +-- tableNonEmpty = makeSchema "tableNonEmpty" + +-- -- genTableNonEmpty :: Hedgehog.MonadGen m => m (TableNonEmpty Result) +-- -- genTableNonEmpty = TableNonEmpty +-- -- <$> Gen.bool +-- -- <*> Gen.nonEmpty (Range.linear 0 10) (liftA2 (,) genTableList genTableMaybe) + + +-- data TableNest f = TableNest +-- { foo :: Column f Bool +-- , bars :: HList f (HMaybe f (TablePair f)) +-- } +-- deriving stock Generic +-- deriving anyclass Rel8able +-- deriving stock instance f ~ Result => Show (TableNest f) +-- deriving stock instance f ~ Result => Eq (TableNest f) +-- deriving stock instance f ~ Result => Ord (TableNest f) + +-- tableNest :: TableSchema (TableNest Name) +-- tableNest = makeSchema "tableNest" + +-- -- genTableNest :: Hedgehog.MonadGen m => m (TableNest Result) +-- -- genTableNest = TableNest +-- -- <$> Gen.bool +-- -- <*> Gen.list (Range.linear 0 10) (Gen.maybe genTablePair) + + +-- data S3Object = S3Object +-- { bucketName :: Text +-- , objectKey :: Text +-- } +-- deriving stock (Generic, Show, Eq, Ord) + + +-- -- instance x ~ HKD S3Object Expr => ToExprs x S3Object + + +-- data HKDSum = HKDSumA Text | HKDSumB Bool Char | HKDSumC +-- deriving stock (Generic, Show, Eq, Ord) + + +-- -- instance x ~ HKD HKDSum Expr => ToExprs x HKDSum + +-- genHKDSum :: Hedgehog.MonadGen m => m HKDSum +-- genHKDSum = Gen.choice +-- [ HKDSumA <$> Gen.text (Range.linear 0 10) Gen.alpha +-- , HKDSumB <$> Gen.bool <*> Gen.alpha +-- , pure HKDSumC +-- ] + +-- data HKDTest f = HKDTest +-- { s3Object :: Lift f S3Object +-- , hkdSum :: Lift f HKDSum +-- } +-- deriving stock Generic +-- deriving anyclass Rel8able +-- deriving stock instance f ~ Result => Show (HKDTest f) +-- deriving stock instance f ~ Result => Eq (HKDTest f) +-- deriving stock instance f ~ Result => Ord (HKDTest f) + +-- genHKDTest :: Hedgehog.MonadGen m => m (HKDTest Result) +-- genHKDTest = HKDTest +-- <$> liftA2 S3Object (Gen.text (Range.linear 0 10) Gen.alpha) (Gen.text (Range.linear 0 10) Gen.alpha) +-- <*> genHKDSum + +-- data NonRecord f = NonRecord +-- (Column f Bool) +-- (Column f Char) +-- (Column f Char) +-- (Column f Char) +-- (Column f Char) +-- (Column f Char) +-- (Column f Char) +-- (Column f Char) +-- (Column f Char) +-- (Column f Char) +-- deriving stock Generic +-- deriving anyclass Rel8able +-- deriving stock instance f ~ Result => Show (NonRecord f) +-- deriving stock instance f ~ Result => Eq (NonRecord f) +-- deriving stock instance f ~ Result => Ord (NonRecord f) + +-- nonRecord :: TableSchema (NonRecord Name) +-- nonRecord = makeSchema "nonRecord" + +-- genNonRecord :: Hedgehog.MonadGen m => m (NonRecord Result) +-- genNonRecord = NonRecord +-- <$> Gen.bool +-- <*> Gen.alpha +-- <*> Gen.alpha +-- <*> Gen.alpha +-- <*> Gen.alpha +-- <*> Gen.alpha +-- <*> Gen.alpha +-- <*> Gen.alpha +-- <*> Gen.alpha +-- <*> Gen.alpha + + +-- data TableSum f +-- = TableSumA (Column f Bool) (Column f Text) +-- | TableSumB +-- | TableSumC (Column f Text) +-- deriving stock Generic +-- deriving stock instance f ~ Result => Show (TableSum f) +-- deriving stock instance f ~ Result => Eq (TableSum f) +-- deriving stock instance f ~ Result => Ord (TableSum f) + + +-- genTableSum :: Hedgehog.MonadGen m => m (HADT Result TableSum) +-- genTableSum = Gen.choice +-- [ TableSumA <$> Gen.bool <*> Gen.text (Range.linear 0 10) Gen.alpha +-- , pure TableSumB +-- , TableSumC <$> Gen.text (Range.linear 0 10) Gen.alpha +-- ] + + +-- data BarbieSum f +-- = BarbieSumA (f Bool) (f Text) +-- | BarbieSumB +-- | BarbieSumC (f Text) +-- deriving stock Generic +-- deriving stock instance f ~ Result => Show (BarbieSum f) +-- deriving stock instance f ~ Result => Eq (BarbieSum f) +-- deriving stock instance f ~ Result => Ord (BarbieSum f) + + +-- genBarbieSum :: Hedgehog.MonadGen m => m (BarbieSum Result) +-- genBarbieSum = Gen.choice +-- [ BarbieSumA <$> fmap Identity Gen.bool <*> fmap Identity (Gen.text (Range.linear 0 10) Gen.alpha) +-- , pure BarbieSumB +-- , BarbieSumC <$> fmap Identity (Gen.text (Range.linear 0 10) Gen.alpha) +-- ] + + +-- data TableProduct f = TableProduct +-- { sum :: HADT f BarbieSum +-- , list :: TableList f +-- , foos :: HList f (HADT f TableSum, Lift f HKDSum, HKDTest f) +-- } +-- deriving stock Generic +-- deriving anyclass Rel8able +-- deriving stock instance f ~ Result => Show (TableProduct f) +-- deriving stock instance f ~ Result => Eq (TableProduct f) +-- deriving stock instance f ~ Result => Ord (TableProduct f) + +-- tableProduct :: TableSchema (TableProduct Name) -- tableProduct = makeSchema "tableProduct" --- genTableProduct :: Hedgehog.MonadGen m => m (TableProduct Result) --- genTableProduct = TableProduct --- <$> Gen.choice --- [ BarbieSumA <$> Gen.bool <*> Gen.text (Range.linear 0 10) Gen.alpha --- , BarbieSumB --- , BarbieSumC <$> Gen.text (Range.linear 0 10) Gen.alpha +-- -- genTableProduct :: Hedgehog.MonadGen m => m (TableProduct Result) +-- -- genTableProduct = TableProduct +-- -- <$> genBarbieSum +-- -- <*> genTableList +-- -- <*> Gen.list (Range.linear 0 10) (liftA3 (,,) genTableSum genHKDSum genHKDTest) + +-- -- tableProduct :: TableProduct Name +-- -- tableProduct = makeSchema "tableProduct" + +-- -- genTableProduct :: Hedgehog.MonadGen m => m (TableProduct Result) +-- -- genTableProduct = TableProduct +-- -- <$> Gen.choice +-- -- [ BarbieSumA <$> Gen.bool <*> Gen.text (Range.linear 0 10) Gen.alpha +-- -- , BarbieSumB +-- -- , BarbieSumC <$> Gen.text (Range.linear 0 10) Gen.alpha +-- -- ] +-- -- <*> genTableList +-- -- <*> Gen.list (Range.linear 0 10) (liftA3 (,,) genTableSum) + +-- -- data TableTestB f = TableTestB +-- -- { foo :: f Bool +-- -- , bar :: f (Maybe Bool) +-- -- } +-- -- deriving stock Generic +-- -- deriving anyclass Rel8able + + +-- -- data NestedTableTestB f = NestedTableTestB +-- -- { foo :: f Bool +-- -- , bar :: f (Maybe Bool) +-- -- , baz :: Column f Char +-- -- , nest :: TableTestB f +-- -- } +-- -- deriving stock Generic +-- -- deriving anyclass Rel8able + + +-- -- newtype IdRecord a f = IdRecord { recordId :: Column f a } +-- -- deriving stock Generic + + +-- -- instance DBType a => Rel8able (IdRecord a) + + +-- type Nest :: KRel8able -> KRel8able -> KRel8able +-- data Nest t u f = Nest +-- { foo :: t f +-- , bar :: u f +-- } +-- deriving stock Generic +-- deriving anyclass Rel8able + + +-- data TableType f = TableType +-- { bool :: Column f Bool +-- , char :: Column f Char +-- , int16 :: Column f Int16 +-- , int32 :: Column f Int32 +-- , int64 :: Column f Int64 +-- , float :: Column f Float +-- , double :: Column f Double +-- , scientific :: Column f Scientific +-- , fixed :: Column f (Fixed E2) +-- , utctime :: Column f UTCTime +-- , day :: Column f Day +-- , localtime :: Column f LocalTime +-- , timeofday :: Column f TimeOfDay +-- , calendardifftime :: Column f CalendarDiffTime +-- , text :: Column f Text +-- , lazytext :: Column f LT.Text +-- , citext :: Column f (CI Text) +-- , cilazytext :: Column f (CI LT.Text) +-- , bytestring :: Column f ByteString +-- , lazybytestring :: Column f LB.ByteString +-- , uuid :: Column f UUID +-- , value :: Column f Value +-- } deriving stock (Generic) +-- deriving anyclass instance Rel8able TableType +-- deriving stock instance f ~ Result => Show (TableType f) +-- deriving stock instance f ~ Result => Eq (TableType f) +-- -- deriving stock instance f ~ Result => Ord (TableType f) + +-- tableType :: TableSchema (TableType Name) +-- tableType = makeSchema "tableType" + +-- badTableType :: TableSchema (TableProduct Name) +-- badTableType = makeSchema "tableType" + +-- genTableType :: Hedgehog.MonadGen m => m (TableType Result) +-- genTableType = do +-- bool <- Gen.bool +-- char <- Gen.alpha +-- int16 <- Gen.int16 range +-- int32 <- Gen.int32 range +-- int64 <- Gen.int64 range +-- float <- Gen.float linearFrac +-- double <- Gen.double linearFrac +-- scientific <- fromFloatDigits @Double <$> Gen.realFloat linearFrac +-- utctime <- UTCTime <$> (toEnum <$> Gen.integral range) <*> fmap secondsToDiffTime (Gen.integral range) +-- day <- toEnum <$> Gen.integral range +-- localtime <- LocalTime <$> (toEnum <$> Gen.integral range) <*> timeOfDay +-- timeofday <- timeOfDay +-- text <- Gen.text range Gen.alpha +-- lazytext <- LT.fromStrict <$> Gen.text range Gen.alpha +-- citext <- CI.mk <$> Gen.text range Gen.alpha +-- cilazytext <- CI.mk <$> LT.fromStrict <$> Gen.text range Gen.alpha +-- bytestring <- Gen.bytes range +-- lazybytestring <- LB.fromStrict <$> Gen.bytes range +-- uuid <- UUID.fromWords <$> Gen.word32 range <*> Gen.word32 range <*> Gen.word32 range <*> Gen.word32 range +-- fixed <- MkFixed <$> Gen.integral range +-- value <- Gen.choice +-- [ Object <$> Aeson.fromMapText <$> Map.fromList <$> Gen.list range (liftA2 (,) (Gen.text range Gen.alpha) (pure Null)) +-- , Array <$> Vector.fromList <$> Gen.list range (pure Null) +-- , String <$> Gen.text range Gen.alpha +-- , Number <$> fromFloatDigits @Double <$> Gen.realFloat linearFrac +-- , Bool <$> Gen.bool +-- , pure Null -- ] --- <*> genTableList --- <*> Gen.list (Range.linear 0 10) (liftA3 (,,) genTableSum) - -data TableTestB f = TableTestB - { foo :: f Bool - , bar :: f (Maybe Bool) - } - deriving stock Generic - deriving anyclass Rel8able - - -data NestedTableTestB f = NestedTableTestB - { foo :: f Bool - , bar :: f (Maybe Bool) - , baz :: Column f Char - , nest :: TableTestB f - } - deriving stock Generic - deriving anyclass Rel8able - - -newtype IdRecord a f = IdRecord { recordId :: Column f a } - deriving stock Generic - - -instance DBType a => Rel8able (IdRecord a) - - -type Nest :: KRel8able -> KRel8able -> KRel8able -data Nest t u f = Nest - { foo :: t f - , bar :: u f - } - deriving stock Generic - deriving anyclass Rel8able - - -data TableType f = TableType - { bool :: Column f Bool - , char :: Column f Char - , int16 :: Column f Int16 - , int32 :: Column f Int32 - , int64 :: Column f Int64 - , float :: Column f Float - , double :: Column f Double - , scientific :: Column f Scientific - , fixed :: Column f (Fixed E2) - , utctime :: Column f UTCTime - , day :: Column f Day - , localtime :: Column f LocalTime - , timeofday :: Column f TimeOfDay - , calendardifftime :: Column f CalendarDiffTime - , text :: Column f Text - , lazytext :: Column f LT.Text - , citext :: Column f (CI Text) - , cilazytext :: Column f (CI LT.Text) - , bytestring :: Column f ByteString - , lazybytestring :: Column f LB.ByteString - , uuid :: Column f UUID - , value :: Column f Value - } deriving stock (Generic) -deriving anyclass instance Rel8able TableType -deriving stock instance f ~ Result => Show (TableType f) -deriving stock instance f ~ Result => Eq (TableType f) --- deriving stock instance f ~ Result => Ord (TableType f) - -tableType :: TableSchema (TableType Name) -tableType = makeSchema "tableType" - -badTableType :: TableSchema (TableProduct Name) -badTableType = makeSchema "tableType" - -genTableType :: Hedgehog.MonadGen m => m (TableType Result) -genTableType = do - bool <- Gen.bool - char <- Gen.alpha - int16 <- Gen.int16 range - int32 <- Gen.int32 range - int64 <- Gen.int64 range - float <- Gen.float linearFrac - double <- Gen.double linearFrac - scientific <- fromFloatDigits @Double <$> Gen.realFloat linearFrac - utctime <- UTCTime <$> (toEnum <$> Gen.integral range) <*> fmap secondsToDiffTime (Gen.integral range) - day <- toEnum <$> Gen.integral range - localtime <- LocalTime <$> (toEnum <$> Gen.integral range) <*> timeOfDay - timeofday <- timeOfDay - text <- Gen.text range Gen.alpha - lazytext <- LT.fromStrict <$> Gen.text range Gen.alpha - citext <- CI.mk <$> Gen.text range Gen.alpha - cilazytext <- CI.mk <$> LT.fromStrict <$> Gen.text range Gen.alpha - bytestring <- Gen.bytes range - lazybytestring <- LB.fromStrict <$> Gen.bytes range - uuid <- UUID.fromWords <$> Gen.word32 range <*> Gen.word32 range <*> Gen.word32 range <*> Gen.word32 range - fixed <- MkFixed <$> Gen.integral range - value <- Gen.choice - [ Object <$> Aeson.fromMapText <$> Map.fromList <$> Gen.list range (liftA2 (,) (Gen.text range Gen.alpha) (pure Null)) - , Array <$> Vector.fromList <$> Gen.list range (pure Null) - , String <$> Gen.text range Gen.alpha - , Number <$> fromFloatDigits @Double <$> Gen.realFloat linearFrac - , Bool <$> Gen.bool - , pure Null - ] - calendardifftime <- CalendarDiffTime <$> Gen.integral range <*> (secondsToNominalDiffTime <$> Gen.realFrac_ linearFrac) - pure TableType {..} - where - timeOfDay :: Hedgehog.MonadGen m => m TimeOfDay - timeOfDay = TimeOfDay <$> Gen.integral range <*> Gen.integral range <*> Gen.realFrac_ linearFrac - - range :: Integral a => Range.Range a - range = Range.linear 0 10 - - linearFrac :: (Fractional a, Ord a) => Range.Range a - linearFrac = Range.linearFrac 0 10 - -data TableNumeric f = TableNumeric - { foo :: Column f (Fixed E2) - } deriving stock (Generic) -deriving anyclass instance Rel8able TableNumeric -deriving stock instance f ~ Result => Show (TableNumeric f) -deriving stock instance f ~ Result => Eq (TableNumeric f) - -tableNumeric :: TableSchema (TableNumeric Name) -tableNumeric = makeSchema "tableNumeric" - - -data TableChar f = TableChar - { foo :: Column f Char - } deriving stock (Generic) -deriving anyclass instance Rel8able TableChar -deriving stock instance f ~ Result => Show (TableChar f) -deriving stock instance f ~ Result => Eq (TableChar f) - -tableChar :: TableSchema (TableChar Name) -tableChar = makeSchema "tableChar" +-- calendardifftime <- CalendarDiffTime <$> Gen.integral range <*> (secondsToNominalDiffTime <$> Gen.realFrac_ linearFrac) +-- pure TableType {..} +-- where +-- timeOfDay :: Hedgehog.MonadGen m => m TimeOfDay +-- timeOfDay = TimeOfDay <$> Gen.integral range <*> Gen.integral range <*> Gen.realFrac_ linearFrac + +-- range :: Integral a => Range.Range a +-- range = Range.linear 0 10 + +-- linearFrac :: (Fractional a, Ord a) => Range.Range a +-- linearFrac = Range.linearFrac 0 10 + +-- data TableNumeric f = TableNumeric +-- { foo :: Column f (Fixed E2) +-- } deriving stock (Generic) +-- deriving anyclass instance Rel8able TableNumeric +-- deriving stock instance f ~ Result => Show (TableNumeric f) +-- deriving stock instance f ~ Result => Eq (TableNumeric f) + +-- tableNumeric :: TableSchema (TableNumeric Name) +-- tableNumeric = makeSchema "tableNumeric" + + +-- data TableChar f = TableChar +-- { foo :: Column f Char +-- } deriving stock (Generic) +-- deriving anyclass instance Rel8able TableChar +-- deriving stock instance f ~ Result => Show (TableChar f) +-- deriving stock instance f ~ Result => Eq (TableChar f) + +-- tableChar :: TableSchema (TableChar Name) +-- tableChar = makeSchema "tableChar" diff --git a/treefmt.toml b/treefmt.toml new file mode 100644 index 00000000..e0234a35 --- /dev/null +++ b/treefmt.toml @@ -0,0 +1,19 @@ +[formatter.cabal] +command = "cabal-fmt" +options = [ "--tabular", "-i" ] +includes = [ "*.cabal" ] +excludes = [] + +# A bit of a hack, but until https://github.com/numtide/treefmt/issues/77 is +# resolved we need to manually sequence our Haskell formatters. +[formatter.haskell] +command = "/bin/sh" +options = [ + "-euc", + """ +fourmolu -i "$@" +ch-hs-imports --overwrite --local-modules-from-current-dir --report-progress "$@" + """, + "format-haskell" +] +includes = [ "*.hs" ]