-- 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 FlexibleContexts #-} module Database.Sql.Util.Tables where import Data.Foldable import Data.Set (Set) import qualified Data.Set as S import Data.Map (Map) import qualified Data.Map as M import Data.Ord import Data.Monoid import Data.Maybe (catMaybes) import Data.List.NonEmpty (NonEmpty(..)) import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL import Control.Monad.Identity import Control.Monad.Writer import Control.Monad.Reader import Control.Monad.State import Database.Sql.Type import Database.Sql.Position -- This describes the usage of a particular table, whether it was read from or -- written to. Note that if a table is used in both modes, it will show up -- twice as each mode. data UsageMode = ReadData | ReadMeta | WriteData | WriteMeta | Unknown deriving (Show, Eq, Ord) data TableUse = TableUse UsageMode FullyQualifiedTableName deriving (Show, Eq, Ord) getTables :: HasTables q => q -> Set FullyQualifiedTableName getTables = S.map tableFromUsage . getUsages where tableFromUsage (TableUse _ t) = t getUsages :: HasTables q => q -> Set TableUse getUsages = execWriter . flip runReaderT Unknown . goTables class HasTables q where goTables :: q -> ReaderT UsageMode (Writer (Set TableUse)) () -- Note that vertica and hive statements have their own table usage instances -- in their type file. Changes made here should also reflect there. instance HasTables (Statement d ResolvedNames a) where goTables (QueryStmt q) = goTables q goTables (InsertStmt i) = goTables i goTables (UpdateStmt u) = goTables u goTables (DeleteStmt d) = goTables d goTables (TruncateStmt t) = goTables t goTables (CreateTableStmt c) = goTables c goTables (AlterTableStmt a) = goTables a goTables (DropTableStmt d) = goTables d goTables (CreateViewStmt c) = goTables c goTables (DropViewStmt d) = goTables d goTables (CreateSchemaStmt _) = return () goTables (GrantStmt _) = return () goTables (RevokeStmt _) = return () goTables (BeginStmt _) = return () goTables (CommitStmt _) = return () goTables (RollbackStmt _) = return () goTables (ExplainStmt _ s) = goTables s goTables (EmptyStmt _) = return () instance HasTables (Query ResolvedNames a) where goTables (QuerySelect _ select) = goTables select goTables (QueryExcept _ _ lhs rhs) = mapM_ goTables [lhs, rhs] goTables (QueryUnion _ _ _ lhs rhs) = mapM_ goTables [lhs, rhs] goTables (QueryIntersect _ _ lhs rhs) = mapM_ goTables [lhs, rhs] goTables (QueryWith _ ctes query) = mapM_ goTables $ query : map cteQuery ctes goTables (QueryOrder _ orders query) = mapM_ goTables orders >> goTables query goTables (QueryLimit _ _ query) = goTables query goTables (QueryOffset _ _ query) = goTables query instance HasTables (Select ResolvedNames a) where goTables (Select {..}) = sequence_ [ goTables selectCols , maybe (return ()) goTables selectFrom , maybe (return ()) goTables selectWhere , maybe (return ()) goTables selectTimeseries , maybe (return ()) goTables selectGroup , maybe (return ()) goTables selectHaving , maybe (return ()) goTables selectNamedWindow ] emitTable :: (MonadWriter (Set TableUse) m, MonadReader UsageMode m) => FQTableName a -> m () emitTable t = do r <- ask tell . S.singleton $ TableUse r $ fqtnToFQTN t instance (HasTables a, HasTables b) => HasTables (a, b) where goTables (a, b) = goTables a >> goTables b instance HasTables (RColumnRef a) where goTables (RColumnRef fqcn) = emitTable . runIdentity $ columnNameTable fqcn goTables (RColumnAlias _) = return () -- the table that introduced it will get iterated over anyway. (shrug) instance HasTables (FQTableName a) where goTables fqtn = emitTable fqtn instance HasTables (RTableName a) where goTables (RTableName table _) = emitTable table instance HasTables (SelectColumns ResolvedNames a) where goTables (SelectColumns _ columns) = mapM_ goTables columns instance HasTables (SelectFrom ResolvedNames a) where goTables (SelectFrom _ tablishes) = local (\_ -> ReadData) $ mapM_ goTables tablishes instance HasTables (SelectWhere ResolvedNames a) where goTables (SelectWhere _ condition) = goTables condition instance HasTables (SelectTimeseries ResolvedNames a) where goTables (SelectTimeseries _ _ _ partition expr) = do goTables partition goTables expr instance HasTables (PositionOrExpr ResolvedNames a) where goTables (PositionOrExprPosition _ _ _) = return () goTables (PositionOrExprExpr expr) = goTables expr instance HasTables (GroupingElement ResolvedNames a) where goTables (GroupingElementExpr _ posOrExpr) = goTables posOrExpr goTables (GroupingElementSet _ exprs) = mapM_ goTables exprs instance HasTables (SelectGroup ResolvedNames a) where goTables (SelectGroup _ groupingElements) = mapM_ goTables groupingElements instance HasTables (SelectHaving ResolvedNames a) where goTables (SelectHaving _ havings) = mapM_ goTables havings instance HasTables (SelectNamedWindow ResolvedNames a) where goTables (SelectNamedWindow _ windows) = mapM_ goTables windows instance HasTables (NamedWindowExpr ResolvedNames a) where goTables (NamedWindowExpr _ _ windowExpr) = goTables windowExpr goTables (NamedPartialWindowExpr _ _ partial) = goTables partial instance HasTables (WindowExpr ResolvedNames a) where goTables (WindowExpr _ mPartition orders _) = do goTables mPartition mapM_ goTables orders instance HasTables (PartialWindowExpr ResolvedNames a) where goTables (PartialWindowExpr _ _ mPartition orders _) = do goTables mPartition mapM_ goTables orders instance HasTables (Selection ResolvedNames a) where goTables (SelectStar _ _ _) = return () goTables (SelectExpr _ _ expr) = goTables expr instance HasTables (Insert ResolvedNames a) where goTables Insert{..} = do local (\_ -> WriteData) $ goTables insertTable goTables insertValues instance HasTables (InsertValues ResolvedNames a) where goTables (InsertExprValues _ e) = goTables e goTables (InsertSelectValues q) = goTables q goTables (InsertDefaultValues _) = return () goTables (InsertDataFromFile _ _) = return () instance HasTables (DefaultExpr ResolvedNames a) where goTables (DefaultValue _) = return () goTables (ExprValue e) = goTables e instance HasTables a => HasTables (NonEmpty a) where goTables ne = mapM_ goTables ne instance HasTables a => HasTables (Maybe a) where goTables Nothing = return () goTables (Just a) = goTables a instance HasTables (Update ResolvedNames a) where goTables Update{..} = do local (\_ -> WriteData) $ goTables updateTable local (\_ -> ReadData) $ goTables updateSetExprs local (\_ -> ReadData) $ goTables updateFrom goTables updateWhere instance HasTables (Delete ResolvedNames a) where goTables (Delete _ table expr) = do local (\_ -> WriteData) $ goTables table goTables expr instance HasTables (CreateTable d ResolvedNames a) where goTables CreateTable{createTableName = RCreateTableName table _, ..} = do -- TODO handle createTableExtra, and the dialect instances local (\_ -> WriteData) $ emitTable table goTables createTableDefinition instance HasTables (TableDefinition d ResolvedNames a) where goTables (TableColumns _ s) = goTables s goTables (TableLike _ table) = goTables table goTables (TableAs _ _ query) = goTables query goTables (TableNoColumnInfo _) = return () instance HasTables (ColumnOrConstraint d ResolvedNames a) where goTables (ColumnOrConstraintColumn c) = goTables c goTables (ColumnOrConstraintConstraint _) = return () instance HasTables (ColumnDefinition d ResolvedNames a) where goTables ColumnDefinition{..} = goTables columnDefinitionDefault instance HasTables (Truncate ResolvedNames a) where goTables (Truncate _ tn) = local (\_ -> WriteData) $ goTables tn instance HasTables (AlterTable ResolvedNames a) where goTables (AlterTableRenameTable _ tl tr) = do -- Since renaming table both reads and writes (drops) the source, -- we report twice. local (\_ -> ReadData) $ goTables tl local (\_ -> WriteData) $ goTables tl local (\_ -> WriteData) $ goTables tr goTables (AlterTableRenameColumn _ t _ _) = local (\_ -> WriteMeta) $ goTables t goTables (AlterTableAddColumns _ t _) = local (\_ -> WriteData) $ goTables t instance HasTables (DropTable ResolvedNames a) where goTables DropTable{dropTableNames = tables} = mapM_ (\case RDropExistingTableName table _ -> local (\_ -> WriteData) $ emitTable table RDropMissingTableName _ -> pure () ) tables instance HasTables (CreateView ResolvedNames a) where goTables CreateView{createViewName = RCreateTableName view _, ..} = do local (\_ -> WriteMeta) $ emitTable view goTables createViewQuery instance HasTables (DropView ResolvedNames a) where goTables DropView{dropViewName = RDropExistingTableName view _} = local (\_ -> WriteMeta) $ emitTable view goTables DropView{dropViewName = RDropMissingTableName _} = pure () instance HasTables (Tablish ResolvedNames a) where goTables (TablishTable _ _ (RTableRef fqtn _)) = emitTable fqtn goTables (TablishTable _ _ (RTableAlias _)) = return () goTables (TablishSubQuery _ _ query) = goTables query goTables (TablishLateralView _ LateralView{..} lhs) = goTables lhs >> mapM_ goTables lateralViewExprs goTables (TablishJoin _ _ cond outer inner) = do case cond of JoinOn expr -> goTables expr JoinNatural _ _ -> return () JoinUsing _ _ -> return () goTables outer goTables inner instance HasTables (Expr ResolvedNames a) where goTables (BinOpExpr _ _ lhs rhs) = mapM_ goTables [lhs, rhs] goTables (CaseExpr _ whens else_) = let whens' = foldl' (\ xs (x, y) -> x:y:xs) [] whens in mapM_ goTables (maybe id (:) else_ whens') goTables (UnOpExpr _ _ operand) = goTables operand goTables (LikeExpr _ _ escape pattern expr) = mapM_ goTables $ catMaybes [ fmap escapeExpr escape , Just (patternExpr pattern) , Just expr ] goTables (ConstantExpr _ _) = return () goTables (ColumnExpr _ _) = return () goTables (InListExpr _ exprs expr) = mapM_ goTables $ expr : exprs goTables (InSubqueryExpr _ query expr) = do goTables expr goTables query goTables (BetweenExpr _ expr start end) = mapM_ goTables [expr, start, end] goTables (OverlapsExpr _ (s1, e1) (s2, e2)) = mapM_ goTables [s1, e1, s2, e2] goTables (FunctionExpr _ _ _ args params mFilter mOver) = do mapM_ goTables $ args ++ map snd params maybe (return ()) goTables mFilter maybe (return ()) goTables mOver goTables (AtTimeZoneExpr _ expr tz) = mapM_ goTables [expr, tz] goTables (SubqueryExpr _ query) = goTables query goTables (ArrayExpr _ values) = mapM_ goTables values goTables (ExistsExpr _ query) = goTables query goTables (FieldAccessExpr _ expr _) = goTables expr goTables (ArrayAccessExpr _ expr subscript) = do goTables expr goTables subscript goTables (TypeCastExpr _ _ expr _) = goTables expr goTables (VariableSubstitutionExpr _) = return () instance HasTables (Filter ResolvedNames a) where goTables (Filter _ expr) = goTables expr instance HasTables (OverSubExpr ResolvedNames a) where goTables (OverWindowExpr _ windowExpr) = goTables windowExpr goTables (OverWindowName _ _) = return () goTables (OverPartialWindowExpr _ partial) = goTables partial instance HasTables (Partition ResolvedNames a) where goTables (PartitionBy _ exprs) = mapM_ goTables exprs goTables (PartitionBest _) = return () goTables (PartitionNodes _) = return () instance HasTables (Order ResolvedNames a) where goTables (Order _ posOrExpr _ _) = goTables posOrExpr data Open = Open { openRange :: Range , openNumber :: RangeNumber } deriving (Eq, Show) data Close = Close { closeRange :: Range , closeNumber :: RangeNumber } deriving (Eq, Show) class Positioned a where position :: a -> Position instance Ord Open where compare = comparing (start . openRange) <> flip (comparing $ end . openRange) instance Positioned Open where position = start . openRange instance Ord Close where compare = comparing (end . closeRange) <> flip (comparing $ start . closeRange) instance Positioned Close where position = end . closeRange newtype RangeNumber = RangeNumber Integer deriving (Eq, Ord, Show) newtype NodeNumber = NodeNumber Integer deriving (Eq, Ord, Show) getRanges :: Query RawNames Range -> ( Query RawNames NodeNumber , Set Open, Set Close , Map NodeNumber RangeNumber ) getRanges query = let (query', (_, m)) = runState (mapM numberNodes query) (0, M.empty) (ranges, (_, mapping)) = runState (mapM numberRanges m) (0, M.empty) (opens, closes) = let makeOpenAndClose r rnum = ( S.singleton (Open r rnum) , S.singleton (Close r rnum) ) in M.foldMapWithKey makeOpenAndClose ranges in (query', opens, closes, mapping) where numberNodes r = state $ \ (i, m) -> let m' = M.insertWith S.union r (S.singleton $ NodeNumber i) m in (NodeNumber i, (i+1, m')) numberRanges s = state $ \ (i, m) -> let m' = M.union (M.fromSet (const $ RangeNumber i) s) m in (RangeNumber i, (i+1, m')) spliceMarkers :: Monoid a => (Open -> a) -> (Close -> a) -> (Text -> a) -> Set Open -> Set Close -> Text -> a spliceMarkers renderOpen renderClose renderText = go 0 where go offset opens closes text = case (S.minView opens, S.minView closes) of (Just (o, opens'), Just (c, closes')) | offset == positionOffset (position c) -> renderClose c <> go offset opens closes' text | offset == positionOffset (position o) -> renderOpen o <> go offset opens' closes text | otherwise -> let offset' = min (positionOffset $ position c) (positionOffset $ position o) (chunk, rest) = TL.splitAt (offset' - offset) text in renderText chunk <> go offset' opens closes rest (Just _, Nothing) -> error $ unwords [ "remaining opens when all closes are exhausted" , "- this should not be possible" ] (Nothing, Just (c, closes')) | offset == positionOffset (position c) -> renderClose c <> go offset opens closes' text | otherwise -> let offset' = positionOffset $ position c (chunk, rest) = TL.splitAt (offset' - offset) text in renderText chunk <> go offset' opens closes rest (Nothing, Nothing) -> renderText text