module Database.Sql.Type.Names where
import Data.Hashable
import Data.Text.Lazy (Text, pack)
import Data.Aeson
import Data.Semigroup
import Data.String
import Data.Functor.Identity
import Data.Data (Data, Typeable)
import qualified Data.Map as M
import Data.Map (Map)
import GHC.Exts (Constraint)
import GHC.Generics
import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)
import Data.Proxy
import Control.Applicative (Alternative (..))
import Control.Monad (void)
import Test.QuickCheck
type ConstrainSNames (c :: * -> Constraint) r a =
( c a
, c (TableRef r a)
, c (TableName r a)
, c (CreateTableName r a)
, c (DropTableName r a)
, c (SchemaName r a)
, c (CreateSchemaName r a)
, c (ColumnRef r a)
, c (NaturalColumns r a)
, c (UsingColumn r a)
, c (StarReferents r a)
, c (PositionExpr r a)
, c (ComposedQueryColumns r a)
)
type ConstrainSASNames (c :: (* -> *) -> Constraint) r =
( c (TableRef r)
, c (TableName r)
, c (CreateTableName r)
, c (DropTableName r)
, c (SchemaName r)
, c (CreateSchemaName r)
, c (ColumnRef r)
, c (NaturalColumns r)
, c (UsingColumn r)
, c (StarReferents r)
, c (PositionExpr r)
, c (ComposedQueryColumns r)
)
class Resolution r where
type TableRef r :: * -> *
type TableName r :: * -> *
type CreateTableName r :: * -> *
type DropTableName r :: * -> *
type SchemaName r :: * -> *
type CreateSchemaName r :: * -> *
type ColumnRef r :: * -> *
type NaturalColumns r :: * -> *
type UsingColumn r :: * -> *
type StarReferents r :: * -> *
type PositionExpr r :: * -> *
type ComposedQueryColumns r :: * -> *
type FQCN = FullyQualifiedColumnName
data FullyQualifiedColumnName = FullyQualifiedColumnName
{ fqcnDatabaseName :: Text
, fqcnSchemaName :: Text
, fqcnTableName :: Text
, fqcnColumnName :: Text
} deriving (Data, Generic, Ord, Eq, Show)
type FQTN = FullyQualifiedTableName
data FullyQualifiedTableName = FullyQualifiedTableName
{ fqtnDatabaseName :: Text
, fqtnSchemaName :: Text
, fqtnTableName :: Text
} deriving (Data, Generic, Eq, Ord, Show)
qualifyColumnName :: FQTableName a -> UQColumnName b -> FQColumnName ()
qualifyColumnName fqtn uqcn = uqcn{columnNameInfo = (), columnNameTable = pure $ void fqtn}
fqcnToFQCN :: FQColumnName a -> FullyQualifiedColumnName
fqcnToFQCN (QColumnName _ (Identity (QTableName _ (Identity (QSchemaName _ (Identity (DatabaseName _ database)) schema _)) table)) column) =
FullyQualifiedColumnName database schema table column
fqtnToFQTN :: FQTableName a -> FullyQualifiedTableName
fqtnToFQTN (QTableName _ (Identity (QSchemaName _ (Identity (DatabaseName _ database)) schema _)) table) =
FullyQualifiedTableName database schema table
data DatabaseName a = DatabaseName a Text
deriving (Data, Generic, Read, Show, Eq, Ord, Functor, Foldable, Traversable)
data SchemaType = NormalSchema | SessionSchema
deriving (Data, Generic, Read, Show, Eq, Ord)
data QSchemaName f a = QSchemaName
{ schemaNameInfo :: a
, schemaNameDatabase :: f (DatabaseName a)
, schemaNameName :: Text
, schemaNameType :: SchemaType
} deriving (Generic, Functor, Foldable, Traversable)
deriving instance (Data (f (DatabaseName a)), Data a, Typeable f, Typeable a) => Data (QSchemaName f a)
deriving instance (Eq a, Eq (f (DatabaseName a))) => Eq (QSchemaName f a)
deriving instance (Ord a, Ord (f (DatabaseName a))) => Ord (QSchemaName f a)
deriving instance (Read a, Read (f (DatabaseName a))) => Read (QSchemaName f a)
deriving instance (Show a, Show (f (DatabaseName a))) => Show (QSchemaName f a)
type UQSchemaName = QSchemaName No
type OQSchemaName = QSchemaName Maybe
type FQSchemaName = QSchemaName Identity
mkNormalSchema :: Alternative f => Text -> a -> QSchemaName f a
mkNormalSchema name info = QSchemaName info empty name NormalSchema
instance Hashable (DatabaseName a) where
hashWithSalt salt (DatabaseName _ database) = salt `hashWithSalt` database
instance Arbitrary a => Arbitrary (DatabaseName a) where
arbitrary = do
Identifier name :: Identifier '["fooDatabase", "barDatabase"] <- arbitrary
DatabaseName <$> arbitrary <*> pure name
shrink (DatabaseName info name) =
[DatabaseName info name' | Identifier name' <- shrink (Identifier name :: Identifier '["fooDatabase", "barDatabase"])]
instance Hashable SchemaType
instance Hashable (f (DatabaseName a)) => Hashable (QSchemaName f a) where
hashWithSalt salt (QSchemaName _ database schema schemaType) = salt `hashWithSalt` database `hashWithSalt` schema `hashWithSalt` schemaType
instance (Arbitrary (f (DatabaseName a)), Arbitrary a) => Arbitrary (QSchemaName f a) where
arbitrary = oneof
[ do
Identifier name :: Identifier '["public", "fooSchema"] <- arbitrary
QSchemaName <$> arbitrary <*> arbitrary <*> pure name <*> pure NormalSchema
, do
Identifier name :: Identifier '["session-asdf", "session-hjkl"] <- arbitrary
QSchemaName <$> arbitrary <*> arbitrary <*> pure name <*> pure SessionSchema
]
shrink (QSchemaName info database name _) =
[QSchemaName info database' name' NormalSchema | (database', Identifier name') <- shrink (database, Identifier name :: Identifier '["public", "fooSchema"])]
arbitraryUnquotedIdentifier :: Gen Text
arbitraryUnquotedIdentifier = do
c <- elements openingChars
tailLength <- growingElements [1..31]
cs <- vectorOf tailLength $ elements subsequentChars
pure $ pack $ c:cs
where
openingChars = ['a'..'z'] ++ ['A'..'Z'] ++ ['_']
subsequentChars = openingChars ++ ['$', 'ñ', 'á']
arbitraryQuotedIdentifier :: Gen Text
arbitraryQuotedIdentifier = do
length' <- growingElements [1..32]
pack <$> vectorOf length' arbitrary
arbitraryIdentifier :: Gen Text
arbitraryIdentifier = frequency
[(3, arbitraryUnquotedIdentifier),
(1, arbitraryQuotedIdentifier)]
data Identifier (ids :: [Symbol]) = Identifier Text deriving Eq
class KnownSymbols (xs :: [Symbol]) where
symbolVals :: proxy xs -> [String]
instance KnownSymbols '[] where
symbolVals _ = []
instance (KnownSymbol x, KnownSymbols xs) => KnownSymbols (x ': xs) where
symbolVals _ = symbolVal (Proxy :: Proxy x) : symbolVals (Proxy :: Proxy xs)
instance KnownSymbols ids => Arbitrary (Identifier ids) where
arbitrary = do
arb <- Identifier <$> arbitraryIdentifier
growingElements $ ids ++ [arb]
where
ids = Identifier . pack <$> symbolVals (Proxy :: Proxy ids)
shrink i = takeWhile (/= i) ids
where
ids = Identifier . pack <$> symbolVals (Proxy :: Proxy ids)
data QTableName f a = QTableName
{ tableNameInfo :: a
, tableNameSchema :: f (QSchemaName f a)
, tableNameName :: Text
} deriving (Generic, Functor, Foldable, Traversable)
deriving instance (Data a, Data (f (QSchemaName f a)), Typeable f, Typeable a) => Data (QTableName f a)
deriving instance (Eq a, Eq (f (QSchemaName f a))) => Eq (QTableName f a)
deriving instance (Ord a, Ord (f (QSchemaName f a))) => Ord (QTableName f a)
deriving instance (Read a, Read (f (QSchemaName f a))) => Read (QTableName f a)
deriving instance (Show a, Show (f (QSchemaName f a))) => Show (QTableName f a)
data No a = None deriving (Data, Generic, Eq, Show, Read, Ord, Functor, Foldable, Traversable)
instance Applicative No where
pure = const None
None <*> None = None
instance Arbitrary (No a) where
arbitrary = pure None
instance Hashable (No a) where
hashWithSalt salt _ = hashWithSalt salt ()
instance ToJSON (No a) where
toJSON _ = Null
instance FromJSON (No a) where
parseJSON _ = pure None
instance Alternative No where
empty = None
None <|> None = None
type UQTableName = QTableName No
type OQTableName = QTableName Maybe
type FQTableName = QTableName Identity
newtype TableAliasId
= TableAliasId Integer
deriving (Data, Generic, Read, Show, Eq, Ord)
data TableAlias a
= TableAlias a Text TableAliasId
deriving ( Data, Generic
, Read, Show, Eq, Ord
, Functor, Foldable, Traversable)
tableAliasName :: TableAlias a -> UQTableName a
tableAliasName (TableAlias info name _) = QTableName info None name
data RNaturalColumns a = RNaturalColumns [RUsingColumn a]
deriving ( Data, Generic
, Read, Show, Eq, Ord
, Functor, Foldable, Traversable)
data RUsingColumn a = RUsingColumn (RColumnRef a) (RColumnRef a)
deriving ( Data, Generic
, Read, Show, Eq, Ord
, Functor, Foldable, Traversable)
instance Hashable (f (QSchemaName f a)) => Hashable (QTableName f a) where
hashWithSalt salt (QTableName _ schema table) = salt `hashWithSalt` schema `hashWithSalt` table
instance (Arbitrary (f (QSchemaName f a)), Arbitrary a) => Arbitrary (QTableName f a) where
arbitrary = do
Identifier name :: Identifier '["fooTable", "barTable"] <- arbitrary
QTableName <$> arbitrary <*> arbitrary <*> pure name
shrink (QTableName info schema name) =
[QTableName info schema' name' | (schema', Identifier name') <- shrink (schema, Identifier name :: Identifier '["fooTable", "barTable"])]
data QFunctionName f a = QFunctionName
{ functionNameInfo :: a
, functionNameSchema :: f (QSchemaName f a)
, functionNameName :: Text
} deriving (Generic, Functor, Foldable, Traversable)
deriving instance (Data a, Data (f (QSchemaName f a)), Typeable f, Typeable a) => Data (QFunctionName f a)
deriving instance (Eq a, Eq (f (QSchemaName f a))) => Eq (QFunctionName f a)
deriving instance (Ord a, Ord (f (QSchemaName f a))) => Ord (QFunctionName f a)
deriving instance (Read a, Read (f (QSchemaName f a))) => Read (QFunctionName f a)
deriving instance (Show a, Show (f (QSchemaName f a))) => Show (QFunctionName f a)
type FunctionName = QFunctionName Maybe
instance ( Arbitrary (f (QSchemaName f a))
, Arbitrary a
, Eq (f SchemaType)
, Applicative f
) => Arbitrary (QFunctionName f a) where
arbitrary = do
Identifier name :: Identifier '["fooFunc", "barFunc"] <- arbitrary
QFunctionName <$> arbitrary <*> arbitraryNormalSchema <*> pure name
where
isSessionSchema :: f (QSchemaName f a) -> Bool
isSessionSchema schema = fmap schemaNameType schema == pure SessionSchema
arbitraryNormalSchema = arbitrary `suchThat` (not . isSessionSchema)
shrink (QFunctionName info schema name) =
[QFunctionName info schema' name' | (schema', Identifier name') <- shrink (schema, Identifier name :: Identifier '["fooName", "barName"])]
data QColumnName f a = QColumnName
{ columnNameInfo :: a
, columnNameTable :: f (QTableName f a)
, columnNameName :: Text
} deriving (Generic, Functor, Foldable, Traversable)
deriving instance (Data (f (QTableName f a)), Data a, Typeable f, Typeable a) => Data (QColumnName f a)
deriving instance (Eq (f (QTableName f a)), Eq a) => Eq (QColumnName f a)
deriving instance (Ord (f (QTableName f a)), Ord a) => Ord (QColumnName f a)
deriving instance (Read (f (QTableName f a)), Read a) => Read (QColumnName f a)
deriving instance (Show (f (QTableName f a)), Show a) => Show (QColumnName f a)
type UQColumnName = QColumnName No
type OQColumnName = QColumnName Maybe
type FQColumnName = QColumnName Identity
instance IsString (UQColumnName ()) where
fromString s = QColumnName{..}
where
columnNameTable = None
columnNameName = fromString s
columnNameInfo = ()
newtype ColumnAliasId
= ColumnAliasId Integer
deriving (Data, Generic, Read, Show, Eq, Ord)
instance (Arbitrary (f (QTableName f a)), Arbitrary a) => Arbitrary (QColumnName f a) where
arbitrary = do
Identifier name :: Identifier '["fooColumn", "barColumn"] <- arbitrary
QColumnName <$> arbitrary <*> arbitrary <*> pure name
shrink (QColumnName info table name) =
[QColumnName info table' name' | (table', Identifier name') <- shrink (table, Identifier name :: Identifier '["fooColumn", "barColumn"])]
data ColumnAlias a
= ColumnAlias a Text ColumnAliasId
deriving ( Data, Generic
, Read, Show, Eq, Ord
, Functor, Foldable, Traversable)
columnAliasName :: ColumnAlias a -> UQColumnName a
columnAliasName (ColumnAlias info name _) = QColumnName info None name
data RColumnRef a
= RColumnRef (FQColumnName a)
| RColumnAlias (ColumnAlias a)
deriving ( Data, Generic
, Read, Show, Eq, Ord
, Functor, Foldable, Traversable)
data StructFieldName a = StructFieldName a Text
deriving (Data, Generic, Eq, Ord, Show, Functor, Foldable, Traversable)
newtype FieldChain = FieldChain (Map (StructFieldName ()) FieldChain)
deriving (Eq, Ord, Show)
instance Semigroup FieldChain where
FieldChain m <> FieldChain n
| M.null m || M.null n = FieldChain M.empty
| otherwise = FieldChain $ M.unionWith (<>) m n
data ParamName a
= ParamName a Text
deriving (Data, Generic, Eq, Ord, Read, Show, Functor, Foldable, Traversable)
instance Arbitrary a => Arbitrary (ParamName a) where
arbitrary = ParamName <$> arbitrary <*> arbitraryUnquotedIdentifier
shrink (ParamName info name) = [ ParamName info name' | Identifier name' <- shrink (Identifier name :: Identifier '["my_param_name"]) ]
instance ToJSON a => ToJSON (DatabaseName a) where
toJSON (DatabaseName info database) = object
[ "tag" .= String "DatabaseName"
, "info" .= info
, "database" .= database
]
instance ToJSON SchemaType
instance (ToJSON (f (DatabaseName a)), ToJSON a) => ToJSON (QSchemaName f a) where
toJSON (QSchemaName info database schema schemaType) = object
[ "tag" .= String "QSchemaName"
, "info" .= info
, "database" .= database
, "schema" .= schema
, "schemaType" .= schemaType
]
instance (ToJSON (f (QSchemaName f a)), ToJSON a) => ToJSON (QTableName f a) where
toJSON (QTableName info schema table) = object
[ "tag" .= String "QTableName"
, "info" .= info
, "schema" .= schema
, "table" .= table
]
instance ToJSON a => ToJSON (TableAlias a) where
toJSON (TableAlias info name (TableAliasId ident)) = object
[ "tag" .= String "TableAlias"
, "info" .= info
, "name" .= name
, "ident" .= ident
]
instance ToJSON a => ToJSON (RNaturalColumns a) where
toJSON (RNaturalColumns cols) = object
[ "tag" .= String "RNaturalColumns"
, "cols" .= cols
]
instance ToJSON a => ToJSON (RUsingColumn a) where
toJSON (RUsingColumn left right) = object
[ "tag" .= String "RUsingColumn"
, "left" .= left
, "right" .= right
]
instance (ToJSON (f (QSchemaName f a)), ToJSON a) => ToJSON (QFunctionName f a) where
toJSON (QFunctionName info schema fn) = object
[ "tag" .= String "QFunctionName"
, "info" .= info
, "schema" .= schema
, "function" .= fn
]
instance (ToJSON (f (QTableName f a)), ToJSON a) => ToJSON (QColumnName f a) where
toJSON (QColumnName info table column) = object
[ "tag" .= String "QColumnName"
, "info" .= info
, "table" .= table
, "column" .= column
]
instance ToJSON a => ToJSON (RColumnRef a) where
toJSON (RColumnRef column) = object
[ "tag" .= String "RColumnRef"
, "column" .= column
]
toJSON (RColumnAlias alias) = object
[ "tag" .= String "RColumnAlias"
, "alias" .= alias
]
instance ToJSON a => ToJSON (ColumnAlias a) where
toJSON (ColumnAlias info name (ColumnAliasId ident)) = object
[ "tag" .= String "ColumnAlias"
, "info" .= info
, "name" .= name
, "ident" .= ident
]
instance ToJSON a => ToJSON (StructFieldName a) where
toJSON (StructFieldName info name) = object
[ "tag" .= String "StructFieldName"
, "info" .= info
, "name" .= name
]
instance ToJSON a => ToJSON (ParamName a) where
toJSON (ParamName info param) = object
[ "tag" .= String "ParamName"
, "info" .= info
, "param" .= param
]
instance FromJSON a => FromJSON (DatabaseName a) where
parseJSON (Object o) = do
String "DatabaseName" <- o .: "tag"
DatabaseName <$> o .: "info" <*> o .: "database"
parseJSON v = fail $ unwords
[ "don't know how to parse as DatabaseName:"
, show v
]
instance FromJSON SchemaType
instance (FromJSON (f (DatabaseName a)), FromJSON a) => FromJSON (QSchemaName f a) where
parseJSON (Object o) = do
String "QSchemaName" <- o .: "tag"
QSchemaName <$> o .: "info" <*> o .: "database" <*> o .: "schema" <*> o .: "schemaType"
parseJSON v = fail $ unwords
[ "don't know how to parse as QSchemaName:"
, show v
]
instance (FromJSON (f (QSchemaName f a)), FromJSON a) => FromJSON (QTableName f a) where
parseJSON (Object o) = do
String "QTableName" <- o .: "tag"
QTableName <$> o .: "info" <*> o .: "schema" <*> o .: "table"
parseJSON v = fail $ unwords
[ "don't know how to parse as QTableName:"
, show v
]
instance FromJSON a => FromJSON (TableAlias a) where
parseJSON (Object o) = do
String "TableAlias" <- o .: "tag"
TableAlias <$> o .: "info" <*> o .: "name" <*> (TableAliasId <$> o .: "ident")
parseJSON v = fail $ unwords
[ "don't know how to parse as TableAlias:"
, show v
]
instance (FromJSON (f (QSchemaName f a)), FromJSON a) => FromJSON (QFunctionName f a) where
parseJSON (Object o) = do
String "QFunctionName" <- o .: "tag"
QFunctionName <$> o .: "info" <*> o .: "schema" <*> o .: "function"
parseJSON v = fail $ unwords
[ "don't know how to parse as QFunctionName:"
, show v
]
instance (FromJSON (f (QTableName f a)), FromJSON a) => FromJSON (QColumnName f a) where
parseJSON (Object o) = do
String "QColumnName" <- o .: "tag"
QColumnName <$> o .: "info" <*> o .: "table" <*> o .: "column"
parseJSON v = fail $ unwords
[ "don't know how to parse as QColumnName:"
, show v
]
instance FromJSON a => FromJSON (RColumnRef a) where
parseJSON (Object o) = do
o .: "tag" >>= \case
String "RColumnRef" -> RColumnRef <$> o .: "table"
String "RColumnAlias" -> RColumnAlias <$> o .: "alias"
String tag -> fail $ "unrecognized tag for RColumnRef object: " ++ show tag
_ -> fail $ "unexpected value type for tag on RColumnRef object"
parseJSON v = fail $ unwords
[ "don't know how to parse as RColumnRef:"
, show v
]
instance FromJSON a => FromJSON (ColumnAlias a) where
parseJSON (Object o) = do
String "ColumnAlias" <- o .: "tag"
ColumnAlias <$> o .: "info" <*> o .: "name" <*> (ColumnAliasId <$> o .: "ident")
parseJSON v = fail $ unwords
[ "don't know how to parse as ColumnAlias:"
, show v
]
instance FromJSON a => FromJSON (StructFieldName a) where
parseJSON (Object o) = do
String "StructFieldName" <- o .: "tag"
StructFieldName
<$> o .: "info"
<*> o .: "name"
parseJSON v = fail $ unwords
[ "don't know how to parse as StructFieldName:"
, show v
]
instance FromJSON a => FromJSON (ParamName a) where
parseJSON (Object o) = do
String "ParamName" <- o .: "tag"
ParamName <$> o .: "info" <*> o .: "param"
parseJSON v = fail $ unwords
[ "don't know how to parse as ParamName:"
, show v
]