Skip to content

Commit 7c3b963

Browse files
committed
wip
Change-Id: Ia839a09ef05f9db520764067b4a8183f6a6a6964
1 parent 031c62c commit 7c3b963

File tree

13 files changed

+8812
-200
lines changed

13 files changed

+8812
-200
lines changed

.ghci

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:set -XDeriveAnyClass -XDeriveGeneric -XTemplateHaskell

bare_shell.nix

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
let pkgs = (builtins.getFlake "nixpkgs").legacyPackages.x86_64-linux;
2+
in
3+
pkgs.mkShell { buildInputs = with pkgs; [ghc cabal-install postgresql postgresql.dev zlib
4+
pkg-config];}

cabal.project

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
packages: .
22
constraints: ansi-wl-pprint < 1.0.0
33
allow-newer: base16:base, base16:deepseq, base16:text
4+
5+
tests: true

diff

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
2+
commit b384871b242cef11e717f2671e0128f75887e3b0
3+
Author: remeike <[email protected]>
4+
Date: Mon Aug 5 09:07:59 2024 -0400
5+
6+
Fix regex match operator (#336)
7+
8+
commit e214b75565439461302342083a9bca59894cf34f
9+
Author: Shane <[email protected]>
10+
Date: Tue Jul 30 18:08:25 2024 +0100
11+
12+
Remove `Table Expr b` constraint from `materialize` (#334)
13+
14+
Not only is this not necessary, it can actually act as a barrier to optimisation. The reason I added it was because it seemed like a cheap way to stop someone writing `query' <- materialize query id` — if you return the materialized query from `materialized`, it won't work. Really we would need some sort of `runST` type trick here to do this properly, but that would be too invasive a change.
15+
16+
commit 149ec23c6f32ff677940ca39d6598cfc3a9593bb
17+
Author: Shane <[email protected]>
18+
Date: Mon Jul 15 11:54:22 2024 +0100
19+
20+
Add `aggregate{Just,Left,Right,This,That,Those,Here,There}Table{,1}` aggregators (#333)
21+
22+
These provide another way to do aggregation of `MaybeTable`, `EitherTable` and `TheseTable`s than the existing `aggregate{Maybe,Either,These}Table`.
23+
24+
commit dbda2da7bc94da0b4118be1ff689aab67f2f56ed
25+
Author: Teo Camarasu <[email protected]>
26+
Date: Tue Jul 2 16:41:09 2024 +0100
27+
28+
docs: fix haddock link (#332)
29+
30+
Previously this linked to Prelude.fromIntegral, but we mean Rel8.Num.fromIntegral
31+
32+
commit 6b0721b4ca53f228775501bbe7e56722aec27a47
33+
Author: Shane <[email protected]>
34+
Date: Mon Jul 1 12:56:48 2024 +0100
35+
36+
Expose `listOf` and `nonEmptyOf` (#330)
37+
38+
It was an oversight that these were ever not exported.
39+
40+
commit d8ca92fe85ceec0ff4cf55c9156678bd55a23fc1
41+
Author: Teo Camarasu <[email protected]>
42+
Date: Thu Jun 20 17:28:57 2024 +0100
43+
44+
Fix code block format (#329)
45+
46+
Without the indentation, this doesn't parse properly
47+
48+
commit 21b17334864a2abbeac3f953710f9f0e6ac95f10
49+
Author: Teo Camarasu <[email protected]>
50+
Date: Wed May 29 15:56:49 2024 +0100
51+
52+
docs: remove empty 'where' clause (#327)
53+
54+
This hanging where statement seems like a typo or the ghost of a proper where long gone
55+
56+
commit 6ec03667c472b102f9e447c682cc00c680f1a152
57+
Author: abigailalice <[email protected]>
58+
Date: Wed Apr 3 07:57:50 2024 -0700
59+
60+
Fix some markup in haddocks (#318)
61+
62+
Fixes #315.

result

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/nix/store/z7sskjfby4bwgamzqyx51nlzchzdszmb-ghc-shell-for-packages

src/Rel8/Schema/HTable/Label.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
{-# language TypeFamilies #-}
88

99
module Rel8.Schema.HTable.Label
10-
( HLabel, hlabel, hrelabel, hunlabel
10+
( HLabel(HLabel), hlabel, hrelabel, hunlabel
1111
, hproject
1212
)
1313
where

src/Rel8/TH.hs

Lines changed: 197 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@
44
{-# LANGUAGE BlockArguments #-}
55
{-# LANGUAGE ViewPatterns #-}
66
{-# LANGUAGE TypeFamilies #-}
7-
module Rel8.TH (deriveRel8able) where
8-
9-
import Prelude ((.), pure, (<$>), ($), fail, map, id, (==), (<>), show, last, error, otherwise)
10-
import Language.Haskell.TH (Name, Q, Dec, conT, Type (AppT, ConT, VarT, TupleT), newName, conP, varP, nameBase, conE, varE, appsE, TyVarBndr(..), varT, tupleT)
11-
import Language.Haskell.TH.Datatype (reifyDatatype, ConstructorInfo (ConstructorInfo), DatatypeInfo (DatatypeInfo), datatypeCons, constructorFields, ConstructorVariant (RecordConstructor), constructorVariant, constructorName, datatypeVars)
7+
{-# LANGUAGE PolyKinds #-}
8+
{-# LANGUAGE TypeApplications #-}
9+
{-# LANGUAGE RecordWildCards #-}
10+
module Rel8.TH (deriveRel8able, parseDatatype) where
11+
12+
import Prelude
13+
import Language.Haskell.TH (Q)
14+
import qualified Language.Haskell.TH as TH
15+
import qualified Language.Haskell.TH.Syntax as TH
16+
import Language.Haskell.TH.Datatype (reifyDatatype, ConstructorInfo (ConstructorInfo), DatatypeInfo (..), datatypeCons, constructorFields, ConstructorVariant (RecordConstructor), constructorVariant, constructorName, datatypeVars)
17+
import qualified Language.Haskell.TH.Datatype as TH.Datatype
1218
import Rel8.Generic.Rel8able ( Rel8able(..) )
1319
import Rel8.Schema.Result (Result)
1420
import Data.Foldable (foldl', toList )
@@ -21,84 +27,191 @@ import Data.Functor ( (<&>) )
2127
import Data.List.NonEmpty ( NonEmpty( (:|) ) )
2228
import Rel8.Column ( Column )
2329
import Rel8.Expr ( Expr )
24-
import Rel8.Table ( Columns )
25-
26-
deriveRel8able :: Name -> Q [Dec]
27-
deriveRel8able name = do
28-
DatatypeInfo{ datatypeVars = (last -> fBinder), datatypeCons = [ ConstructorInfo{ constructorName, constructorFields = f1:fs, constructorVariant = RecordConstructor (fieldName1:fieldNames) } ]} <- reifyDatatype name
29-
30-
let f = case fBinder of
31-
PlainTV a _ -> a
32-
KindedTV a _ _ -> a
33-
34-
contextName <- newName "context"
35-
name1 <- newName $ nameBase fieldName1
36-
names <- for fieldNames $ newName . nameBase
37-
38-
let allNames = name1 :| names
39-
40-
let
41-
unpackP =
42-
foldl'
43-
(\e n -> [p| HProduct $e (HIdentity $( varP n )) |])
44-
[p| HIdentity $( varP name1 ) |]
45-
names
46-
47-
unmk (x :| xs) =
48-
foldl'
49-
(\e n -> [| HProduct $e (HIdentity $n) |])
50-
[| HIdentity $x |]
51-
xs
52-
53-
mk xs = appsE (conE constructorName : toList xs)
54-
55-
id
56-
[d| instance Rel8able $( conT name ) where
57-
type GColumns $( conT name) =
58-
$(
59-
foldl'
60-
(\t x -> [t| HProduct $t $(unColumn f x) |])
61-
(unColumn f f1)
62-
fs
63-
)
64-
65-
type GFromExprs $( conT name ) =
66-
$( conT name ) Result
67-
68-
gfromColumns $( varP contextName ) $unpackP =
69-
case $( varE contextName ) of
70-
SExpr -> $( mk $ varE <$> allNames )
71-
SField -> $( mk $ varE <$> allNames )
72-
SName -> $( mk $ varE <$> allNames )
73-
SResult -> $( mk $ allNames <&> \x -> [| runIdentity $( varE x ) |] )
74-
75-
gtoColumns $(varP contextName) $( conP constructorName (map varP (name1:names)) ) =
76-
case $( varE contextName ) of
77-
SExpr -> $( unmk $ varE <$> allNames )
78-
SField -> $( unmk $ varE <$> allNames )
79-
SName -> $( unmk $ varE <$> allNames )
80-
SResult -> $( unmk $ allNames <&> \x -> [| Identity $( varE x ) |] )
81-
82-
gfromResult $unpackP =
83-
$( mk $ allNames <&> \x -> [| runIdentity $( varE x ) |] )
84-
85-
gtoResult $( conP constructorName (map varP (name1:names)) ) =
86-
$( unmk $ allNames <&> \x -> [| Identity $( varE x ) |] )
87-
|]
88-
89-
90-
unColumn :: Name -> Type -> Q Type
91-
unColumn _ (AppT (AppT (ConT _Column) _f) t) | _Column == ''Column = [t| HIdentity $(pure t) |]
92-
unColumn f t = [t| Columns $(instantiate t) |]
30+
import Rel8.Table (Columns, toColumns, fromColumns, fromResult, toResult, FromExprs)
31+
import Rel8.Schema.Kind (Context)
32+
import Data.List (unsnoc)
33+
import Debug.Trace
34+
import Rel8.Schema.HTable.Label (HLabel(..))
35+
import Data.Data (constrFields)
36+
import Data.Aeson (parseIndexedJSON)
37+
38+
39+
-- We derive a Rel8able instance using TH.
40+
-- At it's core a Rel8able instance is a bijection between a datatype and the the SQL columns corresponding to its fields.
41+
-- We only support datatypes with one constructor.
42+
-- The datatype must have exactly one type arg and it is the index for our HKD stuff.
43+
-- Question: Can we support multiple type args?
44+
---
45+
-- We have three types of fields:
46+
-- 1) Column f Text : Directly using Column, easy. This is just a special case of (3)
47+
-- 2) OtherType f : They embed another Rel8able type
48+
-- 3) TabledType : They embed a type with a table instance.
49+
-- eg, we might see something like (Column f Text, Column f Bool). (,) has a Table instance,
50+
-- so we know how to map this type to SQL columns.
51+
--
52+
-- We represent a vector of SQL columns with basically:
53+
-- HLabel "field label" (HIdentity Text) `HProduct` HLabel "another field" (HIdentity Bool) ...
54+
-- 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.
55+
-- This H* stuff is also used to thread around contexts if you look at the definitions of these things
56+
57+
data ParsedDatatype =
58+
ParsedDatatype
59+
{ name :: TH.Name
60+
, conName :: TH.Name
61+
, fBinder :: TH.Name
62+
, fields :: [ParsedField]
63+
}
64+
deriving (Show)
65+
66+
data ParsedField =
67+
ParsedField
68+
{ fieldSelector :: Maybe TH.Name
69+
, fieldVariant :: ParsedFieldVariant
70+
, fieldType :: TH.Type
71+
, fieldColumnType :: TH.Type
72+
, fieldFreshName :: TH.Name
73+
}
74+
deriving (Show)
75+
76+
data ParsedFieldVariant =
77+
ColumnField
78+
| Rel8ableField
79+
deriving (Show)
80+
81+
-- | 'fail' but indicate that the failure is coming from our code
82+
prettyFail :: String -> Q a
83+
prettyFail str = fail $ "deriveRel8able: " ++ str
84+
85+
parseDatatype :: DatatypeInfo -> Q ParsedDatatype
86+
parseDatatype datatypeInfo = do
87+
constructor <-
88+
-- Check that it only has one constructor
89+
case datatypeCons datatypeInfo of
90+
[cons] -> pure cons
91+
_ -> prettyFail "exepecting a datatype with exactly 1 constructor"
92+
let conName = TH.Datatype.constructorName constructor
93+
let name = datatypeName datatypeInfo
94+
fBinder <- case unsnoc $ datatypeInstTypes datatypeInfo of
95+
Just (_, candidate) -> parseFBinder candidate
96+
Nothing -> prettyFail "expecting the datatype to have a context type parameter like `data Foo f = ...`"
97+
let fieldSelectors = case constructorVariant constructor of
98+
-- Only record constructors have field names
99+
RecordConstructor names -> map Just names
100+
_ -> repeat Nothing
101+
let columnName = ''Column
102+
fields <-
103+
mapM (uncurry $ parseField columnName fBinder) $
104+
zip (constructorFields constructor) fieldSelectors
105+
-- TODO: check that we have at least one field, fail otherwise
106+
pure ParsedDatatype{..}
107+
108+
parseFBinder :: TH.Type -> Q TH.Name
109+
parseFBinder (TH.SigT x (TH.ConT kind))
110+
| kind == ''Context = parseFBinder x
111+
| otherwise = prettyFail $ "expected kind encountered for the context type argument: " ++ show kind
112+
parseFBinder (TH.VarT name) = pure name
113+
parseFBinder typ = prettyFail $ "unexpected type encountered while looking for the context type argument to the datatype: " ++ show typ
114+
115+
parseField :: TH.Name -> TH.Name -> TH.Type -> Maybe TH.Name -> Q ParsedField
116+
parseField columnName fBinder fieldType fieldSelector
117+
| (TH.ConT columnCandidate `TH.AppT` TH.VarT fBinderCandidate `TH.AppT` subType) <- fieldType
118+
, columnCandidate == columnName
119+
, fBinderCandidate == fBinder
120+
= do
121+
n <- TH.newName "x"
122+
pure $ ParsedField { fieldSelector = fieldSelector, fieldVariant = ColumnField, fieldType = subType, fieldColumnType = TH.ConT ''HIdentity `TH.AppT` subType, fieldFreshName = n}
123+
| (subType `TH.AppT` TH.VarT name ) <- fieldType
124+
, name == fBinder
125+
-- actually might want [t|Columns ($(pure subType) Expr)|]?
126+
= do
127+
n <- TH.newName "x"
128+
columnType <- [t|Columns ($(pure subType) Expr)|]
129+
pure $ ParsedField { fieldSelector = fieldSelector, fieldVariant = Rel8ableField, fieldType = subType, fieldColumnType = columnType, fieldFreshName = n}
130+
| otherwise = prettyFail $ "Field of unexpected type: " ++ show fieldType
131+
132+
generateGColumns :: ParsedDatatype -> Q TH.Type
133+
generateGColumns ParsedDatatype{..} =
134+
foldr1 (\x y -> [t|HProduct $x $y|]) $ map generateGColumn fields
93135
where
94-
instantiate = \case
95-
VarT v | v == f -> [t| Expr |]
96-
| otherwise -> varT v
97-
98-
AppT x y -> [t| $(instantiate x) $(instantiate y) |]
99-
100-
TupleT n -> tupleT n
101-
102-
ConT n -> conT n
136+
generateGColumn ParsedField{..} =
137+
[t| $(pure fieldColumnType)|]
138+
>>= labelled fieldSelector
139+
labelled Nothing x = pure x
140+
labelled (Just (TH.Name (TH.OccName fieldSelector) _)) x = [t|HLabel $(TH.litT $ TH.strTyLit fieldSelector) $(pure x)|]
141+
142+
generateColumnsE :: ParsedDatatype -> (TH.Exp -> TH.Exp) -> (TH.Type -> TH.Exp -> TH.Exp) -> TH.Exp
143+
generateColumnsE ParsedDatatype{..} f g =
144+
foldr1 (\x y -> TH.ConE 'HProduct `TH.AppE` x `TH.AppE` y) $ map generateColumnE fields
145+
where
146+
generateColumnE ParsedField{..} =
147+
labelled fieldSelector $
148+
case fieldVariant of
149+
ColumnField -> TH.ConE 'HIdentity `TH.AppE` (f $ TH.VarE fieldFreshName)
150+
Rel8ableField -> (g fieldType $ TH.VarE fieldFreshName)
151+
labelled Nothing x = x
152+
labelled (Just _) x = TH.ConE 'HLabel `TH.AppE`x
153+
154+
generateColumnsP :: ParsedDatatype -> TH.Pat
155+
generateColumnsP ParsedDatatype{..} =
156+
foldr1 (\x y -> TH.ConP 'HProduct [] [x, y]) $ map generateColumnP fields
157+
where
158+
generateColumnP ParsedField{..} =
159+
labelled fieldSelector $
160+
case fieldVariant of
161+
ColumnField -> TH.ConP 'HIdentity [] [TH.VarP fieldFreshName]
162+
Rel8ableField -> TH.VarP fieldFreshName
163+
labelled Nothing x = x
164+
labelled (Just _) x = TH.ConP 'HLabel [] [x]
165+
166+
generateConstructorE :: ParsedDatatype -> (TH.Exp -> TH.Exp) -> (TH.Type -> TH.Exp -> TH.Exp) -> Q TH.Exp
167+
generateConstructorE parsedDatatype f g =
168+
pure $ foldl' TH.AppE (TH.ConE (conName parsedDatatype)) . map generateFieldE $ fields parsedDatatype
169+
where
170+
generateFieldE ParsedField{..} =
171+
case fieldVariant of
172+
ColumnField -> f . TH.VarE $ fieldFreshName
173+
Rel8ableField -> g fieldType . TH.VarE $ fieldFreshName
103174

104-
other -> error $ show other
175+
deriveRel8able :: TH.Name -> Q [TH.Dec]
176+
deriveRel8able name = do
177+
datatypeInfo <- reifyDatatype name
178+
parsedDatatype <- parseDatatype datatypeInfo
179+
let gColumns = generateGColumns parsedDatatype
180+
let constructorE = generateConstructorE parsedDatatype
181+
let constructorP = pure $ TH.ConP (conName parsedDatatype) [] . map (TH.VarP . fieldFreshName) $ fields parsedDatatype
182+
let columnsE f g = pure $ generateColumnsE parsedDatatype f g
183+
let columnsP = pure $ generateColumnsP parsedDatatype
184+
contextName <- TH.newName "context"
185+
[d|
186+
instance Rel8able $(TH.conT name) where
187+
-- Really the Generic code substitutes Expr for f and then does stuff. Maybe we want to move closer to that?
188+
type GColumns $( TH.conT name) =
189+
$( gColumns )
190+
191+
type GFromExprs $( TH.conT name ) =
192+
$( TH.conT name ) Result
193+
194+
-- the rest of the definition is just a few functions to go back and forth between Columns and the datatype
195+
196+
gfromColumns $( TH.varP contextName ) $(columnsP)=
197+
case $( TH.varE contextName ) of
198+
SResult -> $(constructorE (\x -> TH.VarE 'runIdentity `TH.AppE` x) (\_ x -> TH.VarE 'fromColumns `TH.AppE` x))
199+
SExpr -> $(constructorE id (\_ x -> TH.VarE 'fromColumns `TH.AppE` x))
200+
SField -> $(constructorE id (\_ x -> TH.VarE 'fromColumns `TH.AppE` x))
201+
SName -> $(constructorE id (\_ x -> TH.VarE 'fromColumns `TH.AppE` x))
202+
203+
gtoColumns $(TH.varP contextName) $( constructorP ) =
204+
case $( TH.varE contextName ) of
205+
SExpr -> $(columnsE id (\_ x -> TH.VarE 'toColumns `TH.AppE` x))
206+
SField -> $(columnsE id (\_ x -> TH.VarE 'toColumns `TH.AppE` x))
207+
SName -> $(columnsE id (\_ x -> TH.VarE 'toColumns `TH.AppE` x))
208+
SResult -> $(columnsE (\x -> TH.ConE 'Identity `TH.AppE` x) (\_ x -> TH.VarE 'toColumns `TH.AppE` x))
209+
210+
gfromResult $columnsP =
211+
-- TODO: get rid of type application. Use a signature that references the generic value instead
212+
$( constructorE (\x -> TH.VarE 'runIdentity `TH.AppE` x) (\ft x -> (TH.VarE 'fromResult `TH.AppTypeE` TH.ConT ''Result `TH.AppTypeE` (ft `TH.AppT` TH.ConT ''Result) `TH.AppE` x)))
213+
214+
gtoResult $constructorP =
215+
-- TODO: get rid of type application. Use a signature that references the generic value instead
216+
$( columnsE (\x -> TH.ConE 'Identity `TH.AppE` x) (\ft x -> (TH.VarE 'toResult `TH.AppTypeE` TH.ConT ''Result `TH.AppTypeE` (ft `TH.AppT` TH.ConT ''Result) `TH.AppE` x)))
217+
|]

src/Rel8Test.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- |
2+
{-# language DuplicateRecordFields #-}
3+
4+
module Rel8Test where
5+
import Rel8 (text)
6+
7+
foo = text

0 commit comments

Comments
 (0)