packages feed

sqel-0.0.1.0: lib/Sqel/Data/PgType.hs

module Sqel.Data.PgType where

import Data.Aeson (FromJSON, ToJSON)
import qualified Data.Map.Strict as Map
import qualified Exon
import Lens.Micro.Extras (view)
import Prettyprinter (Pretty (pretty), nest, sep, vsep, (<+>))
import Sqel.SOP.Constraint (symbolText)
import Sqel.Text.DbIdentifier (dbIdentifierT, dbSymbol)

import Sqel.Data.PgTypeName (PgCompName, PgTableName, pattern PgTypeName)
import Sqel.Data.Selector (Selector (unSelector), assign, nameSelector)
import Sqel.Data.Sql (Sql, ToSql (toSql), sql, sqlQuote)
import Sqel.Data.SqlFragment (
  CommaSep (CommaSep),
  Create (Create),
  Delete (Delete),
  From (From),
  Insert (Insert),
  Into (Into),
  Returning (Returning),
  Select (Select),
  Update (Update),
  )

newtype PgPrimName =
  PgPrimName { unPgPrimName :: Text }
  deriving stock (Eq, Show, Generic)
  deriving newtype (IsString, Ord, Semigroup, Monoid, ToJSON, FromJSON)

instance Pretty PgPrimName where
  pretty (PgPrimName n) = pretty n

pgPrimName ::
  ∀ name .
  KnownSymbol name =>
  PgPrimName
pgPrimName =
  PgPrimName (dbSymbol @name)

newtype PgProdName =
  PgProdName { unPgProdName :: Text }
  deriving stock (Eq, Show, Generic)
  deriving newtype (IsString, Ord)

newtype PgColumnName =
  PgColumnName { unPgColumnName :: Text }
  deriving stock (Eq, Show, Generic)
  deriving newtype (Ord, ToJSON, FromJSON)

instance Pretty PgColumnName where
  pretty (PgColumnName n) = pretty n

instance ToSql PgColumnName where
  toSql =
    sqlQuote . unPgColumnName

pgColumnName ::
  Text ->
  PgColumnName
pgColumnName n =
  PgColumnName (dbIdentifierT n)

instance IsString PgColumnName where
  fromString = pgColumnName . fromString

newtype PgTypeRef =
  PgTypeRef { unPgTypeRef :: Text }
  deriving stock (Eq, Show, Generic)
  deriving newtype (IsString, Ord, ToJSON, FromJSON)

instance Pretty PgTypeRef where
  pretty (PgTypeRef n) = pretty n

instance ToSql PgTypeRef where
  toSql = sqlQuote . unPgTypeRef

pgTypeRef ::
  Text ->
  PgTypeRef
pgTypeRef n =
  PgTypeRef (dbIdentifierT n)

pgCompRef :: PgCompName -> PgTypeRef
pgCompRef (PgTypeName n) =
  PgTypeRef n

pgTypeRefSym ::
  ∀ tname .
  KnownSymbol tname =>
  PgTypeRef
pgTypeRefSym =
  pgTypeRef (symbolText @tname)

data ColumnType =
  ColumnPrim { name :: PgPrimName, unique :: Bool, constraints :: [Sql] }
  |
  ColumnComp { pgType :: PgTypeRef, unique :: Bool, constraints :: [Sql] }
  deriving stock (Eq, Show, Generic)
  deriving anyclass (ToJSON, FromJSON)

data PgColumn =
  PgColumn {
    name :: PgColumnName,
    pgType :: ColumnType
  }
  deriving stock (Eq, Show, Generic)
  deriving anyclass (FromJSON, ToJSON)

instance Pretty PgColumn where
  pretty = \case
    PgColumn n (ColumnPrim t _ opt) -> "*" <+> pretty n <+> pretty t <+> sep (pretty <$> opt)
    PgColumn n (ColumnComp t _ opt) -> "+" <+> pretty n <+> pretty t <+> sep (pretty <$> opt)

instance ToSql (Create PgColumn) where
  toSql (Create PgColumn {..}) =
    case pgType of
      ColumnPrim (PgPrimName tpe) _ (Exon.intercalate " " -> params) ->
        [sql|##{name} ##{tpe} #{params}|]
      ColumnComp (PgTypeRef tpe) _ (Exon.intercalate " " -> params) ->
        [sql|##{name} ##{tpe} #{params}|]

newtype PgColumns =
  PgColumns { unPgColumns :: [PgColumn] }
  deriving stock (Eq, Show)
  deriving newtype (FromJSON, ToJSON)

data StructureType =
  StructurePrim { name :: PgPrimName, unique :: Bool, constraints :: [Sql] }
  |
  StructureComp { compName :: PgCompName, struct :: PgStructure, unique :: Bool, constraints :: [Sql] }
  deriving stock (Eq, Show, Generic)
  deriving anyclass (FromJSON, ToJSON)

structureToColumn :: StructureType -> ColumnType
structureToColumn = \case
  StructurePrim {..} -> ColumnPrim {..}
  StructureComp (PgTypeName ref) _ unique constr -> ColumnComp (PgTypeRef ref) unique constr

instance Pretty PgColumns where
  pretty (PgColumns cs) =
    vsep (pretty <$> cs)

instance ToSql (CommaSep PgColumns) where
  toSql (CommaSep (PgColumns cols)) =
    toSql (CommaSep (view #name <$> cols))

instance ToSql (Create PgColumns) where
  toSql (Create (PgColumns cols)) =
    [sql|(##{CommaSep (Create <$> cols)})|]

newtype PgStructure =
  PgStructure { unPgColumns :: [(PgColumnName, StructureType)] }
  deriving stock (Eq, Show)
  deriving newtype (FromJSON, ToJSON)

structureToColumns :: PgStructure -> PgColumns
structureToColumns (PgStructure cols) =
  PgColumns (uncurry PgColumn . second structureToColumn <$> cols)

data PgComposite =
  PgComposite {
    name :: PgCompName,
    columns :: PgColumns
  }
  deriving stock (Eq, Show, Generic)
  deriving anyclass (FromJSON, ToJSON)

instance Pretty PgComposite where
  pretty PgComposite {..} =
    nest 2 (vsep ["type" <+> pretty name, pretty columns])

newtype TableSelectors =
  TableSelectors { unTableSelectors :: [Selector] }
  deriving stock (Eq, Show, Generic)

instance ToSql (CommaSep TableSelectors) where
  toSql (CommaSep (TableSelectors s)) =
    toSql (CommaSep (unSelector <$> s))

instance ToSql (Select TableSelectors) where
  toSql (Select s) =
    "select " <> toSql (CommaSep s)

newtype TableValues =
  TableValues { unTableValues :: [Sql] }
  deriving stock (Eq, Show, Generic)

data PgTable a =
  PgTable {
    name :: PgTableName,
    columns :: PgColumns,
    types :: Map PgTypeRef PgComposite,
    selectors :: TableSelectors,
    values :: TableValues,
    structure :: PgStructure
  }
  deriving stock (Show, Generic)

instance Pretty (PgTable a) where
  pretty PgTable {..} =
    nest 2 (vsep (("table" <+> pretty name) : pretty columns : (pretty <$> Map.elems types)))

instance ToSql (Create (PgTable a)) where
  toSql (Create PgTable {name, columns}) =
    [sql|create table ##{name} ##{Create columns}|]

instance ToSql (Select (PgTable a)) where
  toSql (Select PgTable {name, selectors}) =
    [sql|##{Select selectors} ##{From name}|]

instance ToSql (Update (PgTable a)) where
  toSql (Update PgTable {columns = PgColumns columns, values = TableValues values}) =
    [sql|update set ##{CommaSep assigns}|]
    where
      assigns = zipWith assign colNames values
      colNames = columns <&> \ (PgColumn (PgColumnName name) _) -> nameSelector name

instance ToSql (Returning (PgTable a)) where
  toSql (Returning (PgTable {selectors})) =
    [sql|returning ##{CommaSep selectors}|]

instance ToSql (Insert (PgTable a)) where
  toSql (Insert PgTable {name, columns, values = TableValues values}) =
    [sql|insert ##{Into name} (##{CommaSep columns}) values (##{CommaSep values})|]

instance ToSql (Delete (PgTable a)) where
  toSql (Delete PgTable {name}) =
    [sql|delete ##{From name}|]