{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE CPP #-}
module Database.Beam.Migrate.Serialization
(
BeamSerializedDataType(..)
, BeamSerializedConstraintDefinition(..)
, BeamSerializedConstraintAttributes(..)
, BeamSerializedConstraint(..)
, BeamSerializedMatchType(..)
, BeamSerializedReferentialAction(..)
, BeamSerializedExpression(..)
, beamSerializeJSON, serializePrecAndDecimal
, BeamDeserializers(..)
, beamDeserialize, beamDeserializeMaybe
, beamDeserializer, sql92Deserializers
, sql99DataTypeDeserializers
, sql2003BinaryAndVarBinaryDataTypeDeserializers
, sql2008BigIntDataTypeDeserializers
) where
import Database.Beam.Backend.SQL
import Database.Beam.Migrate.SQL.SQL92
import Database.Beam.Migrate.SQL.Types
import Control.Applicative
import Control.Monad
import Data.Aeson
import Data.Aeson.Types (Parser)
import qualified Data.Dependent.Map as D
import qualified Data.GADT.Compare as D
import Data.Text (Text, unpack)
import Data.Typeable (Typeable, (:~:)( Refl ), eqT, typeRep, typeOf)
import qualified Data.Vector as V
#if !MIN_VERSION_base(4, 11, 0)
import Data.Semigroup
#endif
newtype BeamSerializedDataType
= BeamSerializedDataType { fromBeamSerializedDataType :: Value }
deriving (Show, Eq)
instance IsSql92DataTypeSyntax BeamSerializedDataType where
domainType nm = BeamSerializedDataType (object [ "domain" .= nm])
charType prec collation =
BeamSerializedDataType (object [ "char" .= object [ "prec" .= prec
, "collation" .= collation ]])
varCharType prec collation =
BeamSerializedDataType (object [ "varchar" .= object [ "prec" .= prec
, "collation" .= collation ]])
nationalCharType prec =
BeamSerializedDataType (object [ "national-char" .= object [ "prec" .= prec ]])
nationalVarCharType prec =
BeamSerializedDataType (object [ "national-varchar" .= object [ "prec" .= prec ]])
bitType prec =
BeamSerializedDataType (object [ "bit" .= object [ "prec" .= prec ]])
varBitType prec =
BeamSerializedDataType (object [ "varbit" .= object [ "prec" .= prec ]])
numericType precAndDecimal =
BeamSerializedDataType (object [ "numeric" .= serializePrecAndDecimal precAndDecimal ])
decimalType precAndDecimal =
BeamSerializedDataType (object [ "decimal" .= serializePrecAndDecimal precAndDecimal ])
intType = BeamSerializedDataType "int"
smallIntType = BeamSerializedDataType "smallint"
floatType prec =
BeamSerializedDataType (object [ "float" .= object [ "prec" .= prec ] ])
doubleType = BeamSerializedDataType "double"
realType = BeamSerializedDataType "real"
dateType = BeamSerializedDataType "date"
timeType prec withTz =
BeamSerializedDataType (object [ "time" .= object [ "prec" .= prec
, "timezone" .= withTz ]])
timestampType prec withTz =
BeamSerializedDataType (object [ "timestamp" .= object [ "prec" .= prec
, "timezone" .= withTz ]])
instance IsSql99DataTypeSyntax BeamSerializedDataType where
characterLargeObjectType = BeamSerializedDataType "clob"
binaryLargeObjectType = BeamSerializedDataType "blob"
booleanType = BeamSerializedDataType "boolean"
arrayType ty count = BeamSerializedDataType (object [ "array" .= object [ "of" .= ty
, "count" .= count ]])
rowType tys = BeamSerializedDataType (object [ "row" .= tys ])
instance IsSql2003BinaryAndVarBinaryDataTypeSyntax BeamSerializedDataType where
binaryType sz = BeamSerializedDataType (object [ "binary" .= sz ])
varBinaryType sz = BeamSerializedDataType (object [ "varbinary" .= sz ])
instance IsSql2008BigIntDataTypeSyntax BeamSerializedDataType where
bigIntType = BeamSerializedDataType "bigint"
instance ToJSON BeamSerializedDataType where
toJSON = fromBeamSerializedDataType
newtype BeamSerializedConstraintDefinition
= BeamSerializedConstraintDefinition
{ fromBeamSerializedConstraintDefinition :: Value
} deriving (Show, Eq)
newtype BeamSerializedConstraintAttributes
= BeamSerializedConstraintAttributes
{ fromBeamSerializedConstraintAttributes :: [ Value ]
} deriving (Show, Eq, Monoid, Semigroup)
newtype BeamSerializedConstraint
= BeamSerializedConstraint
{ fromBeamSerializedConstraint :: Value
} deriving (Show, Eq)
newtype BeamSerializedMatchType
= BeamSerializedMatchType
{ fromBeamSerializedMatchType :: Value
} deriving (Show, Eq)
newtype BeamSerializedReferentialAction
= BeamSerializedReferentialAction
{ fromBeamSerializedReferentialAction :: Value
} deriving (Show, Eq)
newtype BeamSerializedExpression
= BeamSerializedExpression
{ fromBeamSerializedExpression :: Text
} deriving (Show, Eq)
instance IsSql92ColumnConstraintDefinitionSyntax BeamSerializedConstraintDefinition where
type Sql92ColumnConstraintDefinitionAttributesSyntax BeamSerializedConstraintDefinition =
BeamSerializedConstraintAttributes
type Sql92ColumnConstraintDefinitionConstraintSyntax BeamSerializedConstraintDefinition =
BeamSerializedConstraint
constraintDefinitionSyntax nm constraint attrs =
BeamSerializedConstraintDefinition $
object [ "name" .= nm
, "attributes" .= fmap fromBeamSerializedConstraintAttributes attrs
, "constraint" .= fromBeamSerializedConstraint constraint ]
instance IsSql92ColumnConstraintSyntax BeamSerializedConstraint where
type Sql92ColumnConstraintMatchTypeSyntax BeamSerializedConstraint =
BeamSerializedMatchType
type Sql92ColumnConstraintReferentialActionSyntax BeamSerializedConstraint =
BeamSerializedReferentialAction
type Sql92ColumnConstraintExpressionSyntax BeamSerializedConstraint =
BeamSerializedExpression
notNullConstraintSyntax = BeamSerializedConstraint "not-null"
uniqueColumnConstraintSyntax = BeamSerializedConstraint "unique"
primaryKeyColumnConstraintSyntax = BeamSerializedConstraint "primary-key"
checkColumnConstraintSyntax e = BeamSerializedConstraint (object [ "check-column" .= fromBeamSerializedExpression e])
referencesConstraintSyntax tbl fields matchType onUpdate onDelete =
BeamSerializedConstraint (object [ "references" .=
object [ "table" .= tbl, "fields" .= fields
, "match-type" .= fmap fromBeamSerializedMatchType matchType
, "on-update" .= fmap fromBeamSerializedReferentialAction onUpdate
, "on-delete" .= fmap fromBeamSerializedReferentialAction onDelete ] ])
instance IsSql92MatchTypeSyntax BeamSerializedMatchType where
fullMatchSyntax = BeamSerializedMatchType "full"
partialMatchSyntax = BeamSerializedMatchType "partial"
instance IsSql92ReferentialActionSyntax BeamSerializedReferentialAction where
referentialActionCascadeSyntax = BeamSerializedReferentialAction "cascade"
referentialActionSetNullSyntax = BeamSerializedReferentialAction "set-null"
referentialActionSetDefaultSyntax = BeamSerializedReferentialAction "set-default"
referentialActionNoActionSyntax = BeamSerializedReferentialAction "nothing"
instance IsSql92ConstraintAttributesSyntax BeamSerializedConstraintAttributes where
initiallyDeferredAttributeSyntax = BeamSerializedConstraintAttributes [ "initially-deferred" ]
initiallyImmediateAttributeSyntax = BeamSerializedConstraintAttributes [ "initially-immediate" ]
notDeferrableAttributeSyntax = BeamSerializedConstraintAttributes [ "not-deferrable" ]
deferrableAttributeSyntax = BeamSerializedConstraintAttributes [ "deferrable" ]
beamSerializeJSON :: Text -> Value -> Value
beamSerializeJSON backend v =
object [ "be-specific" .= backend
, "be-data" .= v ]
serializePrecAndDecimal :: Maybe (Word, Maybe Word) -> Value
serializePrecAndDecimal Nothing =
object []
serializePrecAndDecimal (Just (prec, Nothing)) =
object [ "prec" .= prec ]
serializePrecAndDecimal (Just (prec, Just decimal)) =
object [ "prec" .= prec
, "decimal" .= decimal ]
newtype BeamDeserializer syntax
= BeamDeserializer (forall be. BeamDeserializers be -> Value -> Parser syntax)
newtype BeamDeserializers be
= BeamDeserializers
{ beamArbitraryDeserializers :: D.DMap BeamDeserializerLabel BeamDeserializer
}
instance Semigroup (BeamDeserializer be) where
(<>) = mappend
instance Monoid (BeamDeserializer be) where
mempty = BeamDeserializer (const (const mzero))
mappend (BeamDeserializer a) (BeamDeserializer b) =
BeamDeserializer $ \d o ->
a d o <|> b d o
instance Semigroup (BeamDeserializers be) where
(<>) = mappend
instance Monoid (BeamDeserializers be) where
mempty = BeamDeserializers mempty
mappend (BeamDeserializers a) (BeamDeserializers b) =
BeamDeserializers (D.unionWithKey (const mappend) a b)
beamDeserializeMaybe :: Typeable a
=> BeamDeserializers be
-> Maybe Value
-> Parser (Maybe a)
beamDeserializeMaybe _ Nothing = pure Nothing
beamDeserializeMaybe d (Just v) =
Just <$> beamDeserialize d v
beamDeserialize :: forall a be. Typeable a
=> BeamDeserializers be -> Value
-> Parser a
beamDeserialize allD@(BeamDeserializers d) v =
case D.lookup (BeamDeserializerLabel :: BeamDeserializerLabel a) d of
Nothing -> fail ("beamDeserialize: No deserializer for " ++ show (typeOf (undefined :: a)))
Just (BeamDeserializer doParse) ->
doParse allD v
data BeamDeserializerLabel ty where
BeamDeserializerLabel :: Typeable ty
=> BeamDeserializerLabel ty
instance D.GEq BeamDeserializerLabel where
geq a b =
case D.gcompare a b of
D.GEQ -> Just Refl
_ -> Nothing
instance D.GCompare BeamDeserializerLabel where
gcompare a@(BeamDeserializerLabel :: BeamDeserializerLabel a)
b@(BeamDeserializerLabel :: BeamDeserializerLabel b) =
case eqT of
Just (Refl :: a :~: b)-> D.GEQ
Nothing ->
case compare (typeRep a) (typeRep b) of
LT -> D.GLT
GT -> D.GGT
EQ -> error "Impossible"
beamDeserializer :: Typeable ty
=> (forall be'. BeamDeserializers be' -> Value -> Parser ty)
-> BeamDeserializers be
beamDeserializer parse =
BeamDeserializers (D.singleton BeamDeserializerLabel (BeamDeserializer parse))
sql92Deserializers :: forall be
. BeamMigrateSqlBackend be
=> BeamDeserializers be
sql92Deserializers = mconcat
[ beamDeserializer deserializeSql92DataType
, beamDeserializer deserializeSql92ConstraintDefinition
, beamDeserializer deserializeSql92Constraint
, beamDeserializer deserializeSql92MatchType
, beamDeserializer deserializeSql92ReferentialAction
, beamDeserializer deserializeSql92Attributes ]
where
parseSub nm o key parse =
withObject (unpack (nm <> "." <> key)) parse =<< o .: key
deserializeSql92DataType :: BeamDeserializers be' -> Value
-> Parser (BeamSqlBackendDataTypeSyntax be)
deserializeSql92DataType _ o =
deserializeSql92DataTypeObject o <|>
deserializeSql92DataTypeScalar o
deserializeSql92DataTypeScalar "int" = pure intType
deserializeSql92DataTypeScalar "smallint" = pure smallIntType
deserializeSql92DataTypeScalar "double" = pure dateType
deserializeSql92DataTypeScalar "real" = pure realType
deserializeSql92DataTypeScalar "date" = pure dateType
deserializeSql92DataTypeScalar _ = mzero
deserializeSql92DataTypeObject =
withObject "Sql92DataType" $ \o ->
let (==>) = parseSub "Sql92DataType" o
in (domainType <$> o .: "domain") <|>
("char" ==> \v ->
charType <$> v .: "prec" <*> v .: "collation") <|>
("varchar" ==> \v ->
varCharType <$> v .: "prec" <*> v .: "collation") <|>
("national-char" ==> \v ->
nationalCharType <$> v .: "prec") <|>
("national-varchar" ==> \v ->
nationalVarCharType <$> v .: "prec") <|>
("bit" ==> \v ->
bitType <$> v .: "prec") <|>
("varbit" ==> \v ->
varBitType <$> v .: "prec") <|>
("numeric" ==> \v ->
numericType <$> deserializePrecAndDecimal v) <|>
("decimal" ==> \v ->
decimalType <$> deserializePrecAndDecimal v) <|>
("float" ==> \v ->
floatType <$> v .: "prec") <|>
("time" ==> \v ->
timeType <$> v .: "prec" <*> v .: "timezone") <|>
("timestamp" ==> \v ->
timestampType <$> v .: "prec" <*> v .: "timezone")
deserializePrecAndDecimal o =
Just <$> (((,) <$> o .: "prec" <*> (Just <$> o .: "decimal")) <|>
((,Nothing) <$> o .: "prec")) <|>
pure Nothing
deserializeSql92ConstraintDefinition :: BeamDeserializers be' -> Value
-> Parser (BeamSqlBackendColumnConstraintDefinitionSyntax be)
deserializeSql92ConstraintDefinition d =
withObject "Sql92ColumnConstraintDefinition" $ \o ->
constraintDefinitionSyntax <$> o .: "name"
<*> (beamDeserialize d =<< o .: "constraint")
<*> (beamDeserializeMaybe d =<< o .: "attributes")
deserializeSql92Constraint :: BeamDeserializers be' -> Value
-> Parser (BeamSqlBackendConstraintSyntax be)
deserializeSql92Constraint d o =
case o of
"not-null" -> pure notNullConstraintSyntax
"unique" -> pure uniqueColumnConstraintSyntax
_ -> withObject "Sql92ColumnConstraint" parseObject o
where
parseObject v =
let (==>) = parseSub "Sql92ColumnConstraint" v
in checkColumnConstraintSyntax <$> (beamDeserialize d =<< v .: "check-column") <|>
("references" ==> \v' ->
referencesConstraintSyntax <$> v' .: "table" <*> v' .: "fields"
<*> (beamDeserializeMaybe d =<< v' .: "match-type")
<*> (beamDeserializeMaybe d =<< v' .: "on-update")
<*> (beamDeserializeMaybe d =<< v' .: "on-delete"))
deserializeSql92MatchType :: BeamDeserializers be' -> Value
-> Parser (BeamSqlBackendMatchTypeSyntax be)
deserializeSql92MatchType _ v =
case v of
"full" -> pure fullMatchSyntax
"partial" -> pure partialMatchSyntax
_ -> mzero
deserializeSql92ReferentialAction :: BeamDeserializers be' -> Value
-> Parser (BeamSqlBackendReferentialActionSyntax be)
deserializeSql92ReferentialAction _ v =
case v of
"cascade" -> pure referentialActionCascadeSyntax
"set-null" -> pure referentialActionSetNullSyntax
"set-default" -> pure referentialActionSetDefaultSyntax
"nothing" -> pure referentialActionNoActionSyntax
_ -> mzero
deserializeSql92Attributes :: BeamDeserializers be' -> Value
-> Parser (BeamSqlBackendConstraintAttributesSyntax be)
deserializeSql92Attributes _ =
withArray "Sql92Attributes" $ \a ->
pure (foldr (\o accum ->
case o of
"initially-deferred" -> initiallyDeferredAttributeSyntax <> accum
"initially-immediate" -> initiallyImmediateAttributeSyntax <> accum
"not-deferrable" -> notDeferrableAttributeSyntax <> accum
"deferrable" -> deferrableAttributeSyntax <> accum
_ -> accum
) mempty a)
sql99DataTypeDeserializers
:: forall be
. BeamMigrateSql99Backend be
=> BeamDeserializers be
sql99DataTypeDeserializers =
beamDeserializer $ \d v ->
fmap (id @(BeamSqlBackendDataTypeSyntax be)) $
case v of
"clob" -> pure characterLargeObjectType
"blob" -> pure binaryLargeObjectType
_ -> withObject "Sql99DataType" (parseObject d) v
where
parseObject d v =
arrayType <$> (beamDeserialize d =<< v .: "of") <*> v .: "count" <|>
rowType <$> (do rowTypes <- v .: "row"
let parseArray a =
forM (V.toList a) $ \a' -> do
(nm, a'') <- parseJSON a'
(nm,) <$> beamDeserialize d a''
withArray "Sql99DataType.rowType" parseArray rowTypes)
sql2003BinaryAndVarBinaryDataTypeDeserializers
:: forall be
. ( BeamMigrateSqlBackend be, BeamSqlT021Backend be )
=> BeamDeserializers be
sql2003BinaryAndVarBinaryDataTypeDeserializers =
beamDeserializer $ \_ v ->
fmap (id @(BeamSqlBackendDataTypeSyntax be)) $
withObject "Sql2003DataType"
(\o -> (binaryType <$> o .: "binary") <|>
(varBinaryType <$> o .: "varbinary"))
v
sql2008BigIntDataTypeDeserializers
:: forall be
. ( BeamMigrateSqlBackend be, BeamSqlT071Backend be )
=> BeamDeserializers be
sql2008BigIntDataTypeDeserializers =
beamDeserializer $ \_ v ->
fmap (id @(BeamSqlBackendDataTypeSyntax be)) $
case v of
"bigint" -> pure bigIntType
_ -> fail "Sql2008DataType.bigint: expected 'bigint'"