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
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)) ()
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 ()
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
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
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