-- 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 NamedFieldPuns #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} module Database.Sql.Util.Eval where import Database.Sql.Type.Query import Database.Sql.Type.Names import Database.Sql.Type.Scope import Database.Sql.Position import Database.Sql.Util.Scope (selectionNames) import qualified Data.Text.Lazy as TL import Data.Foldable import Data.List.NonEmpty (NonEmpty (..)) import Data.Map (Map) import qualified Data.Map as M import Control.Monad.Except import Control.Monad.Identity import Control.Monad.Reader import Control.Monad.State import Control.Monad.Writer import Data.Proxy data RecordSet e = RecordSet { recordSetLabels :: [RColumnRef ()] , recordSetItems :: EvalRow e [EvalValue e] } data EvalContext e = EvalContext { evalAliasMap :: Map TableAliasId (RecordSet e) , evalFromTable :: RTableName Range -> Maybe (RecordSet e) , evalRow :: Map (RColumnRef ()) (EvalValue e) } data ContextType = ExprContext | TableContext exprToTable :: Evaluation e => EvalT e 'ExprContext (EvalMonad e) a -> Map (RColumnRef ()) (EvalValue e) -> EvalT e 'TableContext (EvalMonad e) a exprToTable (EvalT e) r = EvalT $ local (\ EvalContext{..} -> EvalContext{evalRow = M.union evalRow r, ..}) e tableToExpr :: Evaluation e => EvalT e 'TableContext (EvalMonad e) a -> EvalT e 'ExprContext (EvalMonad e) a tableToExpr (EvalT e) = EvalT e newtype EvalT e (t :: ContextType) m a = EvalT (ReaderT (EvalContext e) (ExceptT String m) a) deriving (Functor, Applicative, Monad, MonadReader (EvalContext e), MonadError String, MonadWriter w, MonadState s) type Eval e t = EvalT e t Identity runEval :: Evaluation e => Eval e t a -> (RTableName Range -> Maybe (RecordSet e)) -> Either String a runEval = (runIdentity .) . runEvalT runEvalT :: Evaluation e => EvalT e t m a -> (RTableName Range -> Maybe (RecordSet e)) -> m (Either String a) runEvalT (EvalT e) evalFromTable = runExceptT $ runReaderT e $ EvalContext{..} where evalAliasMap = M.empty evalRow = M.empty class Evaluate e q where type EvalResult e q :: * eval :: Proxy e -> q -> EvalResult e q introduceAlias :: Evaluation e => Proxy e -> TableAlias () -> RecordSet e -> EvalT e 'TableContext (EvalMonad e) a -> EvalT e 'TableContext (EvalMonad e) a introduceAlias _ (TableAlias _ _ alias) tbl = local $ \ EvalContext{..} -> EvalContext{evalAliasMap = M.insert alias tbl evalAliasMap, ..} makeRecordSet :: (Evaluation e, Foldable (EvalRow e)) => Proxy e -> [RColumnRef ()] -> EvalRow e [EvalValue e] -> RecordSet e makeRecordSet _ cols rows = let numColumns = length cols in if any ((/= numColumns) . length) rows then error "wrong number of columns in record when constructing RecordSet" else RecordSet cols rows emptyRecordSet :: Evaluation e => Proxy e -> RecordSet e emptyRecordSet p = makeRecordSet p [] $ pure [] class (Monad (EvalRow e), Monad (EvalMonad e), Traversable (EvalRow e)) => Evaluation e where type EvalValue e :: * type EvalRow e :: * -> * type EvalMonad e :: * -> * addItems :: Proxy e -> EvalRow e [EvalValue e] -> EvalRow e [EvalValue e] -> EvalT e 'TableContext (EvalMonad e) (EvalRow e [EvalValue e]) removeItems :: Proxy e -> EvalRow e [EvalValue e] -> EvalRow e [EvalValue e] -> EvalT e 'TableContext (EvalMonad e) (EvalRow e [EvalValue e]) unionItems :: Proxy e -> EvalRow e [EvalValue e] -> EvalRow e [EvalValue e] -> EvalT e 'TableContext (EvalMonad e) (EvalRow e [EvalValue e]) intersectItems :: Proxy e -> EvalRow e [EvalValue e] -> EvalRow e [EvalValue e] -> EvalT e 'TableContext (EvalMonad e) (EvalRow e [EvalValue e]) distinctItems :: Proxy e -> EvalRow e [EvalValue e] -> EvalRow e [EvalValue e] offsetItems :: Proxy e -> Int -> RecordSet e -> RecordSet e limitItems :: Proxy e -> Int -> RecordSet e -> RecordSet e filterBy :: Expr ResolvedNames Range -> RecordSet e -> EvalT e 'TableContext (EvalMonad e) (RecordSet e) inList :: EvalValue e -> [EvalValue e] -> EvalT e 'ExprContext (EvalMonad e) (EvalValue e) inSubquery :: EvalValue e -> EvalRow e [EvalValue e] -> EvalT e 'ExprContext (EvalMonad e) (EvalValue e) existsSubquery :: EvalRow e [EvalValue e] -> EvalT e 'ExprContext (EvalMonad e) (EvalValue e) atTimeZone :: EvalValue e -> EvalValue e -> EvalT e 'ExprContext (EvalMonad e) (EvalValue e) handleConstant :: Proxy e -> Constant a -> EvalT e 'ExprContext (EvalMonad e) (EvalValue e) handleCases :: Proxy e -> [(Expr ResolvedNames Range, Expr ResolvedNames Range)] -> Maybe (Expr ResolvedNames Range) -> EvalT e 'ExprContext (EvalMonad e) (EvalValue e) handleFunction :: Proxy e -> FunctionName Range -> Distinct -> [Expr ResolvedNames Range] -> [(ParamName Range, Expr ResolvedNames Range)] -> Maybe (Filter ResolvedNames Range) -> Maybe (OverSubExpr ResolvedNames Range) -> EvalT e 'ExprContext (EvalMonad e) (EvalValue e) handleGroups :: [RColumnRef ()] -> EvalRow e ([EvalValue e], EvalRow e [EvalValue e]) -> EvalRow e (RecordSet e) handleLike :: Proxy e -> Operator a -> Maybe (Escape ResolvedNames Range) -> Pattern ResolvedNames Range -> Expr ResolvedNames Range -> EvalT e 'ExprContext (EvalMonad e) (EvalValue e) handleOrder :: Proxy e -> [Order ResolvedNames Range] -> RecordSet e -> EvalT e 'TableContext (EvalMonad e) (RecordSet e) handleSubquery :: EvalRow e [EvalValue e] -> EvalT e 'ExprContext (EvalMonad e) (EvalValue e) handleJoin :: Proxy e -> JoinType a -> JoinCondition ResolvedNames Range -> RecordSet e -> RecordSet e -> EvalT e 'TableContext (EvalMonad e) (RecordSet e) handleStructField :: Expr ResolvedNames Range -> StructFieldName a -> EvalT e 'ExprContext (EvalMonad e) (EvalValue e) handleTypeCast :: CastFailureAction -> Expr ResolvedNames Range -> DataType a -> EvalT e 'ExprContext (EvalMonad e) (EvalValue e) binop :: Proxy e -> TL.Text -> Maybe (EvalValue e -> EvalValue e -> EvalT e 'ExprContext (EvalMonad e) (EvalValue e)) unop :: Proxy e -> TL.Text -> Maybe (EvalValue e -> EvalT e 'ExprContext (EvalMonad e) (EvalValue e)) instance Evaluation e => Evaluate e (Query ResolvedNames Range) where type EvalResult e (Query ResolvedNames Range) = EvalT e 'TableContext (EvalMonad e) (RecordSet e) eval p (QuerySelect _ select) = eval p select eval p (QueryExcept _ (ColumnAliasList cs) lhs rhs) = do exclude <- recordSetItems <$> eval p rhs RecordSet{recordSetItems = unfiltered, ..} <- eval p lhs let labels = map (RColumnAlias . void) cs makeRecordSet p labels <$> removeItems p exclude unfiltered eval p (QueryUnion _ (Distinct False) (ColumnAliasList cs) lhs rhs) = do RecordSet{recordSetItems = lhsRows, ..} <- eval p lhs RecordSet{recordSetItems = rhsRows} <- eval p rhs let labels = map (RColumnAlias . void) cs makeRecordSet p labels <$> unionItems p lhsRows rhsRows eval p (QueryUnion info (Distinct True) cs lhs rhs) = do result@RecordSet{recordSetItems} <- eval p (QueryUnion info (Distinct False) cs lhs rhs) pure $ result{recordSetItems = distinctItems p recordSetItems} eval p (QueryIntersect _ (ColumnAliasList cs) lhs rhs) = do RecordSet{recordSetItems = litems, ..} <- eval p lhs ritems <- recordSetItems <$> eval p rhs let labels = map (RColumnAlias . void) cs makeRecordSet p labels <$> intersectItems p litems ritems eval p (QueryWith _ [] query) = eval p query eval p (QueryWith info (CTE{..}:ctes) query) = do RecordSet{..} <- eval p cteQuery columns <- override cteColumns recordSetLabels let result = makeRecordSet p columns recordSetItems introduceAlias p (void cteAlias) result $ eval p $ QueryWith info ctes query where override [] ys = pure ys override (alias:xs) (_:ys) = do ys' <- override xs ys pure $ (RColumnAlias $ void alias) : ys' override _ [] = throwError "more aliases than columns in CTE" eval p (QueryLimit _ limit query) = eval p limit <$> eval p query eval p (QueryOffset _ offset query) = eval p offset <$> eval p query eval p (QueryOrder _ orders query) = eval p query >>= handleOrder p orders instance Evaluation e => Evaluate e (Select ResolvedNames Range) where type EvalResult e (Select ResolvedNames Range) = EvalT e 'TableContext (EvalMonad e) (RecordSet e) eval p Select{..} = do -- nb. if we handle named windows at resolution time (T637160) -- then we shouldn't need to do anything with them here unfiltered <- maybe (pure $ emptyRecordSet p) (eval p) selectFrom filtered <- maybe pure (eval p) selectWhere unfiltered interpolated <- maybe pure (eval p) selectTimeseries filtered groups <- maybe (const $ pure . pure) (eval p) selectGroup selectCols interpolated having <- maybe pure (eval p) selectHaving groups records <- mapM (eval p selectCols) having let rows = recordSetItems =<< records labels = map void $ selectionNames =<< selectColumnsList selectCols indistinct = makeRecordSet p labels rows pure $ case selectDistinct of Distinct True -> indistinct { recordSetItems = distinctItems p $ recordSetItems indistinct } Distinct False -> indistinct instance Evaluation e => Evaluate e (SelectFrom ResolvedNames Range) where type EvalResult e (SelectFrom ResolvedNames Range) = EvalT e 'TableContext (EvalMonad e) (RecordSet e) eval p (SelectFrom _ []) = pure $ emptyRecordSet p eval p (SelectFrom info (t:ts)) = do RecordSet rcols rrows <- eval p $ SelectFrom info ts RecordSet lcols lrows <- eval p t pure $ makeRecordSet p (lcols ++ rcols) $ (++) <$> lrows <*> rrows appendRecordSets :: Evaluation e => Proxy e -> NonEmpty (RecordSet e) -> EvalT e 'TableContext (EvalMonad e) (RecordSet e) appendRecordSets p (RecordSet cs rs :| sets) = makeRecordSet p cs <$> foldM (addItems p) rs (map recordSetItems sets) instance Evaluation e => Evaluate e (Tablish ResolvedNames Range) where type EvalResult e (Tablish ResolvedNames Range) = EvalT e 'TableContext (EvalMonad e) (RecordSet e) eval _ (TablishTable _ _ (RTableRef tableName table)) = asks evalFromTable <*> pure (RTableName tableName table) >>= \case Nothing -> throwError $ "missing table: " ++ show (void tableName) Just result -> pure result eval _ (TablishTable _ _ (RTableAlias (TableAlias _ aliasName alias))) = asks (M.lookup alias . evalAliasMap) >>= \case Nothing -> throwError $ "missing table alias: " ++ show aliasName Just result -> pure result eval p (TablishSubQuery _ _ query) = eval p query eval p (TablishJoin _ joinType cond lhs rhs) = do x <- eval p lhs y <- eval p rhs handleJoin p joinType cond x y eval _ (TablishLateralView _ _ _) = error "lateral view not yet supported" instance Evaluation e => Evaluate e (JoinCondition ResolvedNames Range) where type EvalResult e (JoinCondition ResolvedNames Range) = RecordSet e -> RecordSet e -> EvalT e 'TableContext (EvalMonad e) (RecordSet e) eval p (JoinOn expr) = \ (RecordSet lcols lrows) (RecordSet rcols rrows) -> do filterBy expr $ makeRecordSet p (lcols ++ rcols) $ (++) <$> lrows <*> rrows eval p (JoinUsing info columns) = \ (RecordSet lcols lrows) (RecordSet rcols rrows) -> do fmap (adjust columns) $ filterBy (mkExpr columns) $ makeRecordSet p (lcols ++ rcols) $ (++) <$> lrows <*> rrows where mkExpr :: [RUsingColumn Range] -> Expr ResolvedNames Range mkExpr [] = ConstantExpr info $ BooleanConstant info True mkExpr (RUsingColumn l r : cs) = BinOpExpr info "AND" (BinOpExpr info ("=") (ColumnExpr info l) (ColumnExpr info r)) (mkExpr cs) adjust :: [RUsingColumn Range] -> RecordSet e -> RecordSet e adjust [] = id adjust (c:cs) = adjust cs . adjust' c adjust' (RUsingColumn l r) = \ RecordSet{..} -> RecordSet { recordSetLabels = map fst . remove r . skip l . zip recordSetLabels $ repeat () , recordSetItems = fmap (map snd . remove r . skip l . zip recordSetLabels) recordSetItems } match column (label, _) = label == void column skip column = break $ match column remove column (skipped, next:rest) = case break (match column) rest of (skipped2, _:rest2) -> skipped ++ [next] ++ skipped2 ++ rest2 (_, []) -> error "failed to find rhs using column" remove _ (_, []) = error "failed to find lhs using column" eval p (JoinNatural info (RNaturalColumns columns)) = eval p (JoinUsing info columns :: JoinCondition ResolvedNames Range) makeRowMap :: [RColumnRef ()] -> [a] -> Map (RColumnRef ()) a makeRowMap = (M.fromList .) . zip -- | SelectColumns tells us how to map from the records provided by the -- FROM to (unfiltered, &c) records provided by our select. Evaluating it -- gives us that function. instance Evaluation e => Evaluate e (SelectColumns ResolvedNames Range) where type EvalResult e (SelectColumns ResolvedNames Range) = RecordSet e -> EvalT e 'TableContext (EvalMonad e) (RecordSet e) eval p (SelectColumns _ columns) (RecordSet cs rs) = do let cs' = map void $ selectionNames =<< columns rs' <- forM rs $ \ r -> do r' <- forM columns $ \ column -> exprToTable (eval p column) $ makeRowMap cs r pure $ concat r' pure $ makeRecordSet p cs' rs' instance Evaluation e => Evaluate e (SelectWhere ResolvedNames Range) where type EvalResult e (SelectWhere ResolvedNames Range) = RecordSet e -> EvalT e 'TableContext (EvalMonad e) (RecordSet e) eval _ (SelectWhere _ expr) = filterBy expr instance Evaluation e => Evaluate e (SelectGroup ResolvedNames Range) where type EvalResult e (SelectGroup ResolvedNames Range) = SelectColumns ResolvedNames Range -> RecordSet e -> EvalT e 'TableContext (EvalMonad e) (EvalRow e (RecordSet e)) eval _ (SelectGroup _ elts) columns (RecordSet cs rs) = do gs <- forM rs $ \ r -> do RecordSet{..} <- eval Proxy columns $ RecordSet cs $ pure r let selectMap = makeRowMap recordSetLabels (head $ toList recordSetItems) g <- exprToTable (mapM (eval Proxy) (eltToExprs =<< elts)) $ M.union selectMap $ makeRowMap cs r pure (g, pure r) pure $ handleGroups cs gs where eltToExprs (GroupingElementSet _ exprs) = exprs eltToExprs (GroupingElementExpr _ (PositionOrExprExpr expr)) = [expr] eltToExprs (GroupingElementExpr _ (PositionOrExprPosition _ _ expr)) = [expr] instance Evaluation e => Evaluate e (SelectHaving ResolvedNames Range) where type EvalResult e (SelectHaving ResolvedNames Range) = EvalRow e (RecordSet e) -> EvalT e 'TableContext (EvalMonad e) (EvalRow e (RecordSet e)) eval _ (SelectHaving _ exprs) = foldl (>=>) pure $ map (mapM . filterBy) exprs instance Evaluation e => Evaluate e (SelectTimeseries ResolvedNames Range) where type EvalResult e (SelectTimeseries ResolvedNames Range) = RecordSet e -> EvalT e 'TableContext (EvalMonad e) (RecordSet e) eval _ (SelectTimeseries _ sliceName interval partition over) = error "timeseries not yet supported" sliceName interval partition over data Direction a = Ascending a | Descending a deriving Eq instance Ord a => Ord (Direction a) where compare (Descending x) (Descending y) = compare y x compare (Ascending x) (Ascending y) = compare x y compare _ _ = error "comparing ascending to descending - this shouldn't happen" instance Evaluation e => Evaluate e (Limit a) where type EvalResult e (Limit a) = RecordSet e -> RecordSet e eval p (Limit _ limit) = limitItems p (read $ TL.unpack limit) instance Evaluation e => Evaluate e (Offset a) where type EvalResult e (Offset a) = RecordSet e -> RecordSet e eval p (Offset _ offset) = offsetItems p (read $ TL.unpack offset) instance Evaluation e => Evaluate e (Selection ResolvedNames Range) where type EvalResult e (Selection ResolvedNames Range) = EvalT e 'ExprContext (EvalMonad e) [EvalValue e] eval p (SelectExpr _ _ expr) = pure <$> eval p expr eval p (SelectStar info _ (StarColumnNames cols)) = forM cols $ \ col -> let expr :: Expr ResolvedNames Range expr = ColumnExpr info col in eval p expr instance Evaluation e => Evaluate e (Expr ResolvedNames Range) where type EvalResult e (Expr ResolvedNames Range) = EvalT e 'ExprContext (EvalMonad e) (EvalValue e) eval p (BinOpExpr _ (Operator op) lhs rhs) = do x <- eval p lhs y <- eval p rhs case binop p op of Nothing -> throwError $ "unhandled operator: " ++ show op Just f -> f x y eval p (CaseExpr _ cases else_) = handleCases p cases else_ eval p (UnOpExpr _ (Operator op) expr) = do x <- eval p expr case unop p op of Nothing -> throwError $ "unhandled operator: " ++ show op Just f -> f x eval p (LikeExpr _ op escape pattern expr) = handleLike p op escape pattern expr eval p (ConstantExpr _ expr) = eval p expr eval _ (ColumnExpr _ col) = do row <- asks evalRow case M.lookup (void col) row of Just x -> pure x Nothing -> throwError $ "failure looking up column: " ++ show (void col) ++ " in " ++ show (M.keys row) eval p (InListExpr _ list expr) = do x <- eval p expr xs <- mapM (eval p) list inList x xs eval p (InSubqueryExpr _ query expr) = do x <- eval p expr RecordSet{..} <- tableToExpr $ eval p query case length recordSetLabels of 1 -> inSubquery x recordSetItems 0 -> throwError "no columns returned from subquery for IN" _ -> throwError "multiple columns returned from subquery for IN" eval p (BetweenExpr info expr start end) = eval p $ BinOpExpr info (Operator "AND") (BinOpExpr info (Operator "<=") start expr) (BinOpExpr info (Operator "<=") expr end) eval p (OverlapsExpr info (lstart, lend) (rstart, rend)) = eval p $ BinOpExpr info (Operator "AND") (BinOpExpr info (Operator "<") lstart rend) (BinOpExpr info (Operator "<") rstart lend) eval p (FunctionExpr _ fn isDistinct args parms filter' over) = handleFunction p fn isDistinct args parms filter' over eval p (AtTimeZoneExpr _ expr tz) = join $ atTimeZone <$> eval p expr <*> eval p tz eval p (SubqueryExpr _ query) = do RecordSet{..} <- tableToExpr $ eval p query handleSubquery recordSetItems eval p (ArrayExpr _ exprs) = error "array expression not yet supported" <$> mapM (eval p) exprs -- T636472 eval p (ExistsExpr _ query) = do RecordSet{..} <- tableToExpr (eval p query) existsSubquery recordSetItems eval _ (FieldAccessExpr _ struct field) = handleStructField struct field eval _ (ArrayAccessExpr _ array idx) = error "array indexing not yet supported" array idx -- T636558 eval _ (TypeCastExpr _ onFail expr type_) = handleTypeCast onFail expr type_ eval _ (VariableSubstitutionExpr _) = throwError "no way to evaluate unsubstituted variable" instance Evaluation e => Evaluate e (Constant a) where type EvalResult e (Constant a) = EvalT e 'ExprContext (EvalMonad e) (EvalValue e) eval p constant = handleConstant p constant