queryparser-0.1.0.0: src/Database/Sql/Type/Scope.hs
-- Copyright (c) 2017 Uber Technologies, Inc.
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleContexts #-}
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)
-- | A ColumnSet records the table-bindings (if any) of columns.
--
-- Can be used to represent columns that are in ambient scope,
-- which can be referenced, based on arcane and dialect specific rules.
-- The fst component will be Nothing for collections of column
-- aliases bound in a containing select statement (which thus have no
-- corresponding Tablish), or for subqueries/lateral views with no table alias.
--
-- Can also be used to represent "what stars expand into".
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 -- column alias generation (counts down from -1, unlike parse phase)
(ReaderT (ResolverInfo a)
(CatalogObjectResolver a))
(r a)
data SchemaMember = SchemaMember
{ tableType :: TableType
, persistence :: Persistence ()
, columnsList :: [UQColumnName ()]
, viewQuery :: Maybe (Query ResolvedNames ()) -- this will always be Nothing for tables
} 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 -- | nb DoesNotExist does not imply that we can't resolve to this name (defaulting)
, 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
-- returned by methods in Catalog
type CatalogObjectResolver a =
(ExceptT (ResolutionError a) -- error
(Writer [Either (ResolutionError a) (ResolutionSuccess a)])) -- warnings and successes
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]
-- R for "resolved"
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 -- TODO holding off til we have arbitrary queries
pure SchemaMember{..}
shrink (SchemaMember type_ persistence cols _) =
[ SchemaMember type_' persistence' cols' Nothing | -- TODO same
(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
]