module Database.Sql.Type.Scope where
import Database.Sql.Type.Names
import Database.Sql.Type.TableProps
import Database.Sql.Type.Unused
import Database.Sql.Type.Query
import Control.Monad.Identity
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.Except
import Control.Monad.Writer
import Data.Aeson
import qualified Data.HashMap.Strict as HMS
import Data.HashMap.Strict (HashMap)
import Data.List (subsequences)
import Data.Hashable (Hashable)
import Test.QuickCheck
import Data.Data (Data)
import GHC.Generics (Generic)
type ColumnSet a = [(Maybe (RTableRef a), [RColumnRef a])]
data Bindings a = Bindings
{ boundCTEs :: [(TableAlias a, [RColumnRef a])]
, boundColumns :: ColumnSet a
}
emptyBindings :: Bindings a
emptyBindings = Bindings [] []
data SelectScope a = SelectScope
{ bindForHaving :: forall r m . MonadReader (ResolverInfo a) m => m r -> m r
, bindForWhere :: forall r m . MonadReader (ResolverInfo a) m => m r -> m r
, bindForOrder :: forall r m . MonadReader (ResolverInfo a) m => m r -> m r
, bindForGroup :: forall r m . MonadReader (ResolverInfo a) m => m r -> m r
, bindForNamedWindow :: forall r m . MonadReader (ResolverInfo a) m => m r -> m r
}
type FromColumns a = ColumnSet a
type SelectionAliases a = [RColumnRef a]
data ResolverInfo a = ResolverInfo
{ catalog :: Catalog
, onCTECollision :: forall x . (x -> x) -> (x -> x)
, bindings :: Bindings a
, selectScope :: FromColumns a -> SelectionAliases a -> SelectScope a
, lcolumnsAreVisibleInLateralViews :: Bool
}
mapBindings :: (Bindings a -> Bindings a) -> ResolverInfo a -> ResolverInfo a
mapBindings f ResolverInfo{..} = ResolverInfo{bindings = f bindings, ..}
bindColumns :: MonadReader (ResolverInfo a) m => ColumnSet a -> m r -> m r
bindColumns columns = local (mapBindings $ \ Bindings{..} -> Bindings{boundColumns = columns ++ boundColumns, ..})
bindFromColumns :: MonadReader (ResolverInfo a) m => FromColumns a -> m r -> m r
bindFromColumns = bindColumns
bindAliasedColumns :: MonadReader (ResolverInfo a) m => SelectionAliases a -> m r -> m r
bindAliasedColumns selectionAliases = bindColumns [(Nothing, selectionAliases)]
bindBothColumns :: MonadReader (ResolverInfo a) m => FromColumns a -> SelectionAliases a -> m r -> m r
bindBothColumns fromColumns selectionAliases = bindColumns $ (Nothing, onlyNewAliases selectionAliases) : fromColumns
where
onlyNewAliases = filter $ \case
alias@(RColumnAlias _) -> not $ inFromColumns alias
RColumnRef _ -> False
inFromColumns alias =
let alias' = void alias
cols = map void (snd =<< fromColumns)
in alias' `elem` cols
data RawNames
deriving instance Data RawNames
instance Resolution RawNames where
type TableRef RawNames = OQTableName
type TableName RawNames = OQTableName
type CreateTableName RawNames = OQTableName
type DropTableName RawNames = OQTableName
type SchemaName RawNames = OQSchemaName
type CreateSchemaName RawNames = OQSchemaName
type ColumnRef RawNames = OQColumnName
type NaturalColumns RawNames = Unused
type UsingColumn RawNames = UQColumnName
type StarReferents RawNames = Unused
type PositionExpr RawNames = Unused
type ComposedQueryColumns RawNames = Unused
data ResolvedNames
deriving instance Data ResolvedNames
newtype StarColumnNames a = StarColumnNames [RColumnRef a]
deriving (Generic, Data, Eq, Ord, Show, Functor)
newtype ColumnAliasList a = ColumnAliasList [ColumnAlias a]
deriving (Generic, Data, Eq, Ord, Show, Functor)
instance Resolution ResolvedNames where
type TableRef ResolvedNames = RTableRef
type TableName ResolvedNames = RTableName
type CreateTableName ResolvedNames = RCreateTableName
type DropTableName ResolvedNames = RDropTableName
type SchemaName ResolvedNames = FQSchemaName
type CreateSchemaName ResolvedNames = RCreateSchemaName
type ColumnRef ResolvedNames = RColumnRef
type NaturalColumns ResolvedNames = RNaturalColumns
type UsingColumn ResolvedNames = RUsingColumn
type StarReferents ResolvedNames = StarColumnNames
type PositionExpr ResolvedNames = Expr ResolvedNames
type ComposedQueryColumns ResolvedNames = ColumnAliasList
type Resolver r a =
StateT Integer
(ReaderT (ResolverInfo a)
(CatalogObjectResolver a))
(r a)
data SchemaMember = SchemaMember
{ tableType :: TableType
, persistence :: Persistence ()
, columnsList :: [UQColumnName ()]
, viewQuery :: Maybe (Query ResolvedNames ())
} deriving (Generic, Data, Eq, Ord, Show)
persistentTable :: [UQColumnName ()] -> SchemaMember
persistentTable cols = SchemaMember Table Persistent cols Nothing
type SchemaMap = HashMap (UQTableName ()) SchemaMember
type DatabaseMap = HashMap (UQSchemaName ()) SchemaMap
type CatalogMap = HashMap (DatabaseName ()) DatabaseMap
type Path = [UQSchemaName ()]
type CurrentDatabase = DatabaseName ()
data Catalog = Catalog
{ catalogResolveSchemaName :: forall a . OQSchemaName a -> CatalogObjectResolver a (FQSchemaName a)
, catalogResolveTableName :: forall a . OQTableName a -> CatalogObjectResolver a (RTableName a)
, catalogHasDatabase :: DatabaseName () -> Existence
, catalogHasSchema :: UQSchemaName () -> Existence
, catalogHasTable :: UQTableName () -> Existence
, catalogResolveTableRef :: forall a . [(TableAlias a, [RColumnRef a])] -> OQTableName a -> CatalogObjectResolver a (WithColumns RTableRef a)
, catalogResolveCreateSchemaName :: forall a . OQSchemaName a -> CatalogObjectResolver a (RCreateSchemaName a)
, catalogResolveCreateTableName :: forall a . OQTableName a -> CatalogObjectResolver a (RCreateTableName a)
, catalogResolveColumnName :: forall a . [(Maybe (RTableRef a), [RColumnRef a])] -> OQColumnName a -> CatalogObjectResolver a (RColumnRef a)
, overCatalogMap :: forall a . (CatalogMap -> (CatalogMap, a)) -> (Catalog, a)
, catalogMap :: !CatalogMap
, catalogWithPath :: Path -> Catalog
, catalogWithDatabase :: CurrentDatabase -> Catalog
}
instance Eq Catalog where
x == y = catalogMap x == catalogMap y
instance Show Catalog where
show = show . catalogMap
type CatalogObjectResolver a =
(ExceptT (ResolutionError a)
(Writer [Either (ResolutionError a) (ResolutionSuccess a)]))
data ResolutionError a
= MissingDatabase (DatabaseName a)
| MissingSchema (OQSchemaName a)
| MissingTable (OQTableName a)
| AmbiguousTable (OQTableName a)
| MissingColumn (OQColumnName a)
| AmbiguousColumn (OQColumnName a)
| UnintroducedTable (OQTableName a)
| UnexpectedTable (FQTableName a)
| UnexpectedSchema (FQSchemaName a)
| BadPositionalReference a Int
deriving (Eq, Show, Functor)
data ResolutionSuccess a
= TableNameResolved (OQTableName a) (RTableName a)
| TableNameDefaulted (OQTableName a) (RTableName a)
| CreateTableNameResolved (OQTableName a) (RCreateTableName a)
| CreateSchemaNameResolved (OQSchemaName a) (RCreateSchemaName a)
| TableRefResolved (OQTableName a) (RTableRef a)
| TableRefDefaulted (OQTableName a) (RTableRef a)
| ColumnRefResolved (OQColumnName a) (RColumnRef a)
| ColumnRefDefaulted (OQColumnName a) (RColumnRef a)
deriving (Eq, Show, Functor)
isGuess :: ResolutionSuccess a -> Bool
isGuess (TableNameResolved _ _) = False
isGuess (TableNameDefaulted _ _) = True
isGuess (CreateTableNameResolved _ _) = False
isGuess (CreateSchemaNameResolved _ _) = False
isGuess (TableRefResolved _ _) = False
isGuess (TableRefDefaulted _ _) = True
isGuess (ColumnRefResolved _ _) = False
isGuess (ColumnRefDefaulted _ _) = True
isCertain :: ResolutionSuccess a -> Bool
isCertain = not . isGuess
data WithColumns r a = WithColumns
{ withColumnsValue :: r a
, withColumnsColumns :: ColumnSet a
}
data WithColumnsAndOrders r a = WithColumnsAndOrders (r a) (ColumnSet a) [Order ResolvedNames a]
data RTableRef a
= RTableRef (FQTableName a) SchemaMember
| RTableAlias (TableAlias a)
deriving (Generic, Data, Show, Eq, Ord, Functor, Foldable, Traversable)
resolvedTableHasName :: QTableName f a -> RTableRef a -> Bool
resolvedTableHasName (QTableName _ _ name) (RTableAlias (TableAlias _ name' _)) = name' == name
resolvedTableHasName (QTableName _ _ name) (RTableRef (QTableName _ _ name') _) = name' == name
resolvedTableHasSchema :: QSchemaName f a -> RTableRef a -> Bool
resolvedTableHasSchema _ (RTableAlias _) = False
resolvedTableHasSchema (QSchemaName _ _ name schemaType) (RTableRef (QTableName _ (Identity (QSchemaName _ _ name' schemaType')) _) _) =
name == name' && schemaType == schemaType'
resolvedTableHasDatabase :: DatabaseName a -> RTableRef a -> Bool
resolvedTableHasDatabase _ (RTableAlias _) = False
resolvedTableHasDatabase (DatabaseName _ name) (RTableRef (QTableName _ (Identity (QSchemaName _ (Identity (DatabaseName _ name')) _ _)) _) _) = name' == name
data RTableName a = RTableName (FQTableName a) SchemaMember
deriving (Generic, Data, Eq, Ord, Show, Functor, Foldable, Traversable)
data RDropTableName a
= RDropExistingTableName (FQTableName a) SchemaMember
| RDropMissingTableName (OQTableName a)
deriving (Generic, Data, Eq, Ord, Show, Functor, Foldable, Traversable)
data RCreateTableName a = RCreateTableName (FQTableName a) Existence
deriving (Generic, Data, Eq, Ord, Show, Functor, Foldable, Traversable)
data RCreateSchemaName a = RCreateSchemaName (FQSchemaName a) Existence
deriving (Generic, Data, Eq, Ord, Show, Functor, Foldable, Traversable)
instance Arbitrary SchemaMember where
arbitrary = do
tableType <- arbitrary
persistence <- arbitrary
columnsList <- arbitrary
viewQuery <- pure Nothing
pure SchemaMember{..}
shrink (SchemaMember type_ persistence cols _) =
[ SchemaMember type_' persistence' cols' Nothing |
(type_', persistence', cols') <- shrink (type_, persistence, cols) ]
shrinkHashMap :: (Eq k, Hashable k) => forall v. HashMap k v -> [HashMap k v]
shrinkHashMap = map HMS.fromList . subsequences . HMS.toList
instance Arbitrary SchemaMap where
arbitrary = HMS.fromList <$> arbitrary
shrink = shrinkHashMap
instance Arbitrary DatabaseMap where
arbitrary = HMS.fromList <$> arbitrary
shrink = shrinkHashMap
instance Arbitrary CatalogMap where
arbitrary = HMS.fromList <$> arbitrary
shrink = shrinkHashMap
instance ToJSON a => ToJSON (RTableRef a) where
toJSON (RTableRef fqtn _) = object
[ "tag" .= String "RTableRef"
, "fqtn" .= fqtn
]
toJSON (RTableAlias alias) = object
[ "tag" .= String "RTableAlias"
, "alias" .= alias
]
instance ToJSON a => ToJSON (RTableName a) where
toJSON (RTableName fqtn _) = object
[ "tag" .= String "RTableName"
, "fqtn" .= fqtn
]
instance ToJSON a => ToJSON (RDropTableName a) where
toJSON (RDropExistingTableName fqtn _) = object
[ "tag" .= String "RDropExistingTableName"
, "fqtn" .= fqtn
]
toJSON (RDropMissingTableName oqtn) = object
[ "tag" .= String "RDropMissingTableName"
, "oqtn" .= oqtn
]
instance ToJSON a => ToJSON (RCreateTableName a) where
toJSON (RCreateTableName fqtn existence) = object
[ "tag" .= String "RCreateTableName"
, "fqtn" .= fqtn
, "existence" .= existence
]
instance ToJSON a => ToJSON (RCreateSchemaName a) where
toJSON (RCreateSchemaName fqsn existence) = object
[ "tag" .= String "RCreateSchemaName"
, "fqsn" .= fqsn
, "existence" .= existence
]
instance ToJSON a => ToJSON (StarColumnNames a) where
toJSON (StarColumnNames cols) = object
[ "tag" .= String "StarColumnNames"
, "cols" .= cols
]
instance ToJSON a => ToJSON (ColumnAliasList a) where
toJSON (ColumnAliasList cols) = object
[ "tag" .= String "ColumnAliasList"
, "cols" .= cols
]