sqel-0.0.1.0: lib/Sqel/PgType.hs
module Sqel.PgType where
import Data.List.NonEmpty ((<|))
import qualified Data.Map.Strict as Map
import qualified Data.Text as Text
import qualified Exon
import Exon (exon)
import Lens.Micro (_1, _2, _3, _4, (^.))
import Lens.Micro.Extras (view)
import Sqel.Class.MatchView (MatchProjection)
import Sqel.Data.Codec (Codec (Codec), FullCodec)
import Sqel.Data.Dd (Dd, DdK, DdType)
import Sqel.Data.PgType (
ColumnType (ColumnComp, ColumnPrim),
PgColumn (PgColumn),
PgColumnName (PgColumnName),
PgColumns (PgColumns),
PgComposite (PgComposite),
PgStructure (PgStructure),
PgTable (PgTable),
PgTypeRef,
StructureType (StructureComp, StructurePrim),
TableSelectors (TableSelectors),
TableValues (TableValues),
pgCompRef,
)
import Sqel.Data.PgTypeName (PgTableName, pgCompName, pgTableName)
import qualified Sqel.Data.Projection as Projection
import Sqel.Data.Projection (Projection (Projection))
import Sqel.Data.ProjectionWitness (ProjectionWitness (ProjectionWitness))
import Sqel.Data.Selector (Selector (Selector))
import Sqel.Data.Sql (Sql (Sql), sql)
import qualified Sqel.Data.TableSchema as TableSchema
import Sqel.Data.TableSchema (TableSchema (TableSchema))
import Sqel.Data.Term (Comp, CompInc (Merge), DdTerm (DdTerm), Struct (Comp, Prim))
import Sqel.ReifyCodec (ReifyCodec (reifyCodec))
import Sqel.ReifyDd (ReifyDd (reifyDd))
import Sqel.SOP.Error (Quoted)
import Sqel.Sql.Prepared (dollar)
import Sqel.Text.Quote (dquote)
import Type.Errors (ErrorMessage)
pgColumn ::
DdTerm ->
([PgColumn], [(PgColumnName, StructureType)], Map PgTypeRef PgComposite, [NonEmpty PgColumnName])
pgColumn = \case
DdTerm name _ unique constr (Prim t) ->
([PgColumn name (ColumnPrim t unique constr)], [(name, StructurePrim t unique constr)], mempty, [pure name])
DdTerm name _ unique constr (Comp typeName c i sub) ->
case comp typeName c i sub of
(compType@(PgComposite cname _), struct, types, False, sels) ->
(colType, structType, Map.insert ref compType types, (name <|) <$> sels)
where
colType = [PgColumn name (ColumnComp ref unique constr)]
structType = [(name, StructureComp cname struct unique constr)]
ref = pgCompRef cname
(PgComposite _ (PgColumns columns), PgStructure struct, types, True, sels) ->
(columns, struct, types, sels)
comp ::
Text ->
Comp ->
CompInc ->
[DdTerm] ->
(PgComposite, PgStructure, Map PgTypeRef PgComposite, Bool, [NonEmpty PgColumnName])
comp typeName _ i sub =
(compType, structType, Map.unions (view _3 <$> cols), i == Merge, view _4 =<< cols)
where
compType = PgComposite compName (PgColumns (view _1 =<< cols))
structType = PgStructure (view _2 =<< cols)
compName = pgCompName typeName
cols = pgColumn <$> sub
-- TODO this used to dquote the @names@ as well but it appears to fail for the sum index field
mkSelector :: NonEmpty PgColumnName -> Selector
mkSelector =
Selector . Sql . \case
[PgColumnName name] -> dquote name
root :| names -> [exon|(##{dquote root}).##{Text.intercalate "." (coerce names)}|]
-- TODO use CommaSep
mkValues :: PgStructure -> [Sql]
mkValues (PgStructure base) =
snd (mapAccumL mkCol (1 :: Int) base)
where
mkCol (n :: Int) = \case
(_, StructurePrim _ _ _) -> (n + 1, [sql|##{dollar n}|])
(_, StructureComp _ (PgStructure cols) _ _) ->
(newN, [sql|row(#{Exon.intercalate ", " sub})|])
where
(newN, sub) =
mapAccumL mkCol n cols
mkTable ::
PgColumnName ->
Maybe PgTableName ->
PgColumns ->
Map PgTypeRef PgComposite ->
[NonEmpty PgColumnName] ->
PgStructure ->
PgTable a
mkTable (PgColumnName name) tableName cols types selectors struct =
PgTable (fromMaybe (pgTableName name) tableName) cols types (TableSelectors (mkSelector <$> selectors)) values struct
where
values = TableValues (mkValues struct)
toTable :: DdTerm -> PgTable a
toTable = \case
DdTerm name tableName unique constr (Prim t) ->
mkTable name tableName cols [] [pure name] struct
where
cols = PgColumns [PgColumn name (ColumnPrim t unique constr)]
struct = PgStructure [(name, StructurePrim t unique constr)]
DdTerm name tableName _ _ (Comp typeName c i sub) ->
mkTable name tableName cols types paths struct
where
(PgComposite _ cols, struct, types, _, paths) = comp typeName c i sub
pgTable ::
∀ s .
ReifyDd s =>
Dd s ->
PgTable (DdType s)
pgTable dd =
toTable (reifyDd dd)
type MkTableSchema :: DdK -> Constraint
class MkTableSchema table where
tableSchema :: Dd table -> TableSchema (DdType table)
instance (
ReifyDd table,
ReifyCodec FullCodec table (DdType table)
) => MkTableSchema table where
tableSchema tab =
TableSchema (pgTable tab) (row ^. #decodeValue) (params ^. #encodeValue)
where
Codec params row = reifyCodec @FullCodec tab
class CheckedProjection' (check :: Maybe Void) (s :: DdK) where
checkedProjection' :: Dd s -> ProjectionWitness (DdType s) table
instance CheckedProjection' 'Nothing s where
checkedProjection' _ = ProjectionWitness
class CheckedProjection (proj :: DdK) (table :: DdK) where
checkedProjection :: Dd proj -> ProjectionWitness (DdType proj) (DdType table)
type CheckProjectionStuck :: ErrorMessage
type CheckProjectionStuck =
"Could not validate projection fields since there is not enough type information available." %
"You are most likely missing a constraint for " <> Quoted "CheckedProjection" <> "."
instance (
MatchProjection proj table match,
CheckedProjection' match proj
) => CheckedProjection proj table where
checkedProjection = checkedProjection' @match
-- TODO check that the table name matches, otherwise a query using the projection will use the wrong name.
-- also possible to automatically set it, but that might be incompatible with the db view interpreter feature, since
-- the name there can't be propagated here. but it would be possible to check only there and do it automatically here.
projectionWitness ::
∀ proj table .
CheckedProjection proj table =>
Dd proj ->
Dd table ->
ProjectionWitness (DdType proj) (DdType table)
projectionWitness proj _ =
checkedProjection @proj @table proj
projection ::
MkTableSchema proj =>
MkTableSchema table =>
CheckedProjection proj table =>
Dd proj ->
Dd table ->
Projection (DdType proj) (DdType table)
projection ddProj ddTable =
Projection {..}
where
table = tableSchema ddTable
TableSchema {..} = tableSchema ddProj
witness = projectionWitness ddProj ddTable
fullProjection ::
MkTableSchema table =>
CheckedProjection table table =>
Dd table ->
Projection (DdType table) (DdType table)
fullProjection dd =
projection dd dd
toFullProjection :: TableSchema table -> Projection table table
toFullProjection table@TableSchema {..} =
Projection {table, witness = ProjectionWitness, ..}