diff --git a/.gitignore b/.gitignore index 8c8534a..93582dd 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ queries/ schemas/ results/ + +/out +*.iml diff --git a/dialects/hive/src/Database/Sql/Hive/Parser.hs b/dialects/hive/src/Database/Sql/Hive/Parser.hs index 926a67a..9fb218c 100644 --- a/dialects/hive/src/Database/Sql/Hive/Parser.hs +++ b/dialects/hive/src/Database/Sql/Hive/Parser.hs @@ -1223,6 +1223,10 @@ tablishToTableAlias (TablishSubQuery _ aliases _) = case aliases of TablishAliasesNone -> error "shouldn't happen in hive" TablishAliasesT (TableAlias _ name _) -> S.singleton name TablishAliasesTC _ _ -> error "shouldn't happen in hive" +tablishToTableAlias (TablishParenthesizedRelation _ aliases relation) = case aliases of + TablishAliasesNone -> tablishToTableAlias relation + TablishAliasesT _ -> error "shouldn't happen in hive" + TablishAliasesTC _ _ -> error "shouldn't happen in hive" tablishToTableAlias (TablishLateralView _ LateralView{..} _) = case lateralViewAliases of TablishAliasesNone -> error "shouldn't happen in hive" TablishAliasesT (TableAlias _ name _) -> S.singleton name diff --git a/dialects/presto/src/Database/Sql/Presto/Parser.hs b/dialects/presto/src/Database/Sql/Presto/Parser.hs index 120e71d..84485fc 100644 --- a/dialects/presto/src/Database/Sql/Presto/Parser.hs +++ b/dialects/presto/src/Database/Sql/Presto/Parser.hs @@ -41,7 +41,7 @@ import Data.Char (isDigit) import Data.Foldable (fold) import qualified Data.List as L import Data.List.NonEmpty (NonEmpty ((:|))) -import Data.Maybe (catMaybes) +import Data.Maybe (catMaybes, fromMaybe) import Data.Monoid (Endo (..)) import Data.Semigroup (Semigroup (..), sconcat) import Data.Set (Set) @@ -63,6 +63,7 @@ statementParser = do , PrestoUnhandledStatement <$> showP , PrestoUnhandledStatement <$> callP , PrestoUnhandledStatement <$> describeP + , PrestoUnhandledStatement <$> setP ] case maybeStmt of Just stmt -> terminator >> return stmt @@ -126,6 +127,7 @@ statementP = choice , GrantStmt <$> grantP , RevokeStmt <$> revokeP , InsertStmt <$> insertP + , CreateTableStmt <$> createTableP ] queryP :: Parser (Query RawNames Range) @@ -230,13 +232,14 @@ selectP = do let selectTimeseries = Nothing selectGroup <- optionMaybe $ local (introduceAliases aliases) groupP selectHaving <- optionMaybe $ local (introduceAliases aliases) havingP - let selectNamedWindow = Nothing - Just selectInfo = sconcat $ Just r :| + selectNamedWindow <- optionMaybe $ local (introduceAliases aliases) namedWindowP + let Just selectInfo = sconcat $ Just r :| [ Just $ getInfo selectCols , getInfo <$> selectFrom , getInfo <$> selectWhere , getInfo <$> selectGroup , getInfo <$> selectHaving + , getInfo <$> selectNamedWindow ] pure Select{..} @@ -248,6 +251,38 @@ selectP = do from <- optionMaybe fromP return $ tableAliases from + namedWindowP = + do + r <- Tok.windowP + windows <- (flip sepBy1) Tok.commaP $ do + name <- windowNameP + _ <- Tok.asP + s <- Tok.openP + window <- choice + [ do + partition <- optionMaybe partitionP + order <- option [] orderInWindowClauseP + frame <- optionMaybe frameP + e <- Tok.closeP + let info = s <> e + return $ Left $ WindowExpr info partition order frame + , do + inherit <- windowNameP + partition <- optionMaybe partitionP + order <- option [] orderInWindowClauseP + frame <- optionMaybe frameP + e <- Tok.closeP + let info = s <> e + return $ Right $ PartialWindowExpr info inherit partition order frame + ] + + let infof = (getInfo name <>) + return $ case window of + Left w -> NamedWindowExpr (infof $ getInfo w) name w + Right pw -> NamedPartialWindowExpr (infof $ getInfo pw) name pw + let info = L.foldl' (<>) r $ fmap getInfo windows + return $ SelectNamedWindow info windows + distinctP :: Parser Distinct distinctP = choice $ @@ -272,6 +307,10 @@ tableAliases from = TablishAliasesNone -> S.empty TablishAliasesT (TableAlias _ name _) -> S.singleton name TablishAliasesTC (TableAlias _ name _) _ -> S.singleton name + tablishToTableAlias (TablishParenthesizedRelation _ aliases _) = case aliases of + TablishAliasesNone -> S.empty + TablishAliasesT (TableAlias _ name _) -> S.singleton name + TablishAliasesTC (TableAlias _ name _) _ -> S.singleton name tablishToTableAlias (TablishLateralView _ LateralView{..} _) = case lateralViewAliases of TablishAliasesNone -> S.empty TablishAliasesT (TableAlias _ name _) -> S.singleton name @@ -359,7 +398,9 @@ sampledRelationP = do return $ TablishLateralView lateralViewInfo LateralView{..} Nothing , P.between Tok.openP Tok.closeP $ choice - [ relationP + [ try $ do + r <- relationP + return $ TablishParenthesizedRelation (getInfo r) placeholder r , do q <- queryP return $ TablishSubQuery (getInfo q) placeholder q @@ -371,6 +412,7 @@ sampledRelationP = do TablishSubQuery info _ query -> TablishSubQuery info as query TablishJoin _ _ _ _ _ -> error "shouldn't happen" TablishLateralView info LateralView{..} lhs -> TablishLateralView info LateralView{lateralViewAliases = as, ..} lhs + TablishParenthesizedRelation info _ relation -> TablishParenthesizedRelation info as relation return withAliases tablishAliasesP :: Parser (TablishAliases Range) @@ -397,6 +439,11 @@ columnAliasP = do (name, r) <- Tok.columnNameP makeColumnAlias r name +lambdaParamP :: Parser (LambdaParam Range) +lambdaParamP = do + (name, r) <- Tok.lambdaParamP + makeLambdaParam r name + joinP :: Parser (Tablish RawNames Range -> Tablish RawNames Range) joinP = crossJoinP <|> regularJoinP <|> naturalJoinP where @@ -655,7 +702,9 @@ selectionP idx = try selectStarP <|> do (name, r) <- Tok.columnNameP makeColumnAlias r name - , makeExprAlias expr idx' + , case expr of + LambdaExpr {} -> fail "Lambda expression should always be used inside a function" + _ -> makeExprAlias expr idx' ] countingSepBy1 :: (Integer -> Parser b) -> Parser c -> Parser [b] @@ -679,6 +728,9 @@ makeTableAlias r alias = TableAlias r alias . TableAliasId <$> getNextCounter makeColumnAlias :: Range -> Text -> Parser (ColumnAlias Range) makeColumnAlias r alias = ColumnAlias r alias . ColumnAliasId <$> getNextCounter +makeLambdaParam :: Range -> Text -> Parser (LambdaParam Range) +makeLambdaParam r name = LambdaParam r name . LambdaParamId <$> getNextCounter + makeDummyAlias :: Range -> Integer -> Parser (ColumnAlias Range) makeDummyAlias r idx = makeColumnAlias r $ TL.pack $ "_col" ++ show idx @@ -702,6 +754,8 @@ makeExprAlias (FieldAccessExpr info _ _) idx = makeDummyAlias info idx makeExprAlias (ArrayAccessExpr info _ _) idx = makeDummyAlias info idx makeExprAlias (TypeCastExpr _ _ expr _) idx = makeExprAlias expr idx makeExprAlias (VariableSubstitutionExpr _) _ = fail "Unsupported variable substitution in Presto: unused expr-type in this dialect" +makeExprAlias LambdaParamExpr {} _ = error "Unreachable, unresolved expr can not be lambda param" +makeExprAlias LambdaExpr {} _ = error "Unreachable, selection parser should reject lambda expression" unOpP :: Text -> Parser (Expr RawNames Range -> Expr RawNames Range) @@ -815,6 +869,7 @@ primaryExprP = foldl (flip ($)) <$> baseP <*> many (arrayAccessP <|> structAcces baseP = choice [ extractPrimaryExprP , normalizePrimaryExprP + , try lambdaP , try substringPrimaryExprP -- try is because `substring` is both a special-form function and a regular function , try positionPrimaryExprP -- try is because `position` could be a column name / UDF function name , bareFuncPrimaryExprP @@ -893,10 +948,63 @@ arrayPrimaryExprP :: Parser (Expr RawNames Range) arrayPrimaryExprP = do r <- Tok.arrayP _ <- Tok.openBracketP - exprs <- exprP `sepBy1` Tok.commaP + exprs <- exprP `sepBy` Tok.commaP r' <- Tok.closeBracketP return $ ArrayExpr (r <> r') exprs +frameP :: Parser (Frame Range) +frameP = do + ftype <- choice + [ RowFrame <$> Tok.rowsP + , RangeFrame <$> Tok.rangeP + ] + + choice + [ do + _ <- Tok.betweenP + start <- frameBoundP + _ <- Tok.andP + end <- frameBoundP + + let r = getInfo ftype <> getInfo end + return $ Frame r ftype start (Just end) + + , do + start <- frameBoundP + + let r = getInfo ftype <> getInfo start + return $ Frame r ftype start Nothing + ] + +frameBoundP :: Parser (FrameBound Range) +frameBoundP = choice + [ fmap Unbounded $ (<>) + <$> Tok.unboundedP + <*> choice [ Tok.precedingP, Tok.followingP ] + + , fmap CurrentRow $ (<>) <$> Tok.currentP <*> Tok.rowP + , constantP >>= \ expr -> choice + [ Tok.precedingP >>= \ r -> + return $ Preceding (getInfo expr <> r) expr + + , Tok.followingP >>= \ r -> + return $ Following (getInfo expr <> r) expr + ] + ] + +windowNameP :: Parser (WindowName Range) +windowNameP = + do + (name, r) <- Tok.windowNameP + return $ WindowName r name + +partitionP :: Parser (Partition RawNames Range) +partitionP = do + r <- Tok.partitionP + _ <- Tok.byP + exprs <- exprP `sepBy1` Tok.commaP + return $ PartitionBy (sconcat $ r :| map getInfo exprs) exprs + dataTypeP :: Parser (DataType Range) dataTypeP = foldl (flip ($)) <$> typeP <*> many arraySuffixP where @@ -1031,64 +1139,72 @@ functionCallPrimaryExprP = do r' <- Tok.closeP return $ Filter (r <> r') expr +lambdaP :: Parser (Expr RawNames Range) +lambdaP = do + (params, start) <- choice + [ do + s <- Tok.openP + params <- lambdaParamP `sepBy` Tok.commaP + _ <- Tok.closeP + return (params, s) + , do + p <- lambdaParamP + return ([p], getInfo p) + ] + _ <- Tok.symbolP "->" + body <- exprP + return $ LambdaExpr (start <> getInfo body) params body + overP :: Parser (OverSubExpr RawNames Range) overP = do start <- Tok.overP - _ <- Tok.openP + subExpr <- choice + [ Left <$> windowP + , Right <$> windowNameP + ] + return $ case subExpr of + Left w -> mergeWindowInfo start w + Right wn -> OverWindowName (start <> getInfo wn) wn + where + windowP :: Parser (OverSubExpr RawNames Range) + windowP = do + start' <- Tok.openP + expr <- choice + [ Left <$> windowExprP start' + , Right <$> partialWindowExprP start' + ] + return $ case expr of + Left w -> OverWindowExpr (start' <> getInfo w) w + Right pw -> OverPartialWindowExpr (start' <> getInfo pw) pw + + mergeWindowInfo :: Range -> OverSubExpr RawNames Range -> OverSubExpr RawNames Range + mergeWindowInfo r = \case + OverWindowExpr r' WindowExpr{..} -> + OverWindowExpr (r <> r') $ WindowExpr { windowExprInfo = windowExprInfo <> r , ..} + OverWindowName r' n -> OverWindowName (r <> r') n + OverPartialWindowExpr r' PartialWindowExpr{..} -> + OverPartialWindowExpr (r <> r') $ PartialWindowExpr { partWindowExprInfo = partWindowExprInfo <> r , ..} + +windowExprP :: Range -> Parser (WindowExpr RawNames Range) +windowExprP start = + do partition <- optionMaybe partitionP order <- option [] orderInWindowClauseP frame <- optionMaybe frameP end <- Tok.closeP let info = start <> end - return $ OverWindowExpr info $ WindowExpr info partition order frame - where - partitionP :: Parser (Partition RawNames Range) - partitionP = do - r <- Tok.partitionP - _ <- Tok.byP - exprs <- exprP `sepBy1` Tok.commaP - return $ PartitionBy (sconcat $ r :| map getInfo exprs) exprs - - frameP :: Parser (Frame Range) - frameP = do - ftype <- choice - [ RowFrame <$> Tok.rowsP - , RangeFrame <$> Tok.rangeP - ] - - choice - [ do - _ <- Tok.betweenP - start <- frameBoundP - _ <- Tok.andP - end <- frameBoundP - - let r = getInfo ftype <> getInfo end - return $ Frame r ftype start (Just end) - - , do - start <- frameBoundP - - let r = getInfo ftype <> getInfo start - return $ Frame r ftype start Nothing - ] - - frameBoundP :: Parser (FrameBound Range) - frameBoundP = choice - [ fmap Unbounded $ (<>) - <$> Tok.unboundedP - <*> choice [ Tok.precedingP, Tok.followingP ] + return (WindowExpr info partition order frame) - , fmap CurrentRow $ (<>) <$> Tok.currentP <*> Tok.rowP - - , numberConstantP >>= \ expr -> choice - [ Tok.precedingP >>= \ r -> - return $ Preceding (getInfo expr <> r) expr - - , Tok.followingP >>= \ r -> - return $ Following (getInfo expr <> r) expr - ] - ] +partialWindowExprP :: Range -> Parser (PartialWindowExpr RawNames Range) +partialWindowExprP start = + do + inherit <- windowNameP + partition <- optionMaybe partitionP + order <- option [] orderInWindowClauseP + frame <- optionMaybe frameP + end <- Tok.closeP + let info = start <> end + return (PartialWindowExpr info inherit partition order frame) orderTopLevelP :: Parser (Range, [Order RawNames Range]) orderTopLevelP = orderExprP False True @@ -1551,3 +1667,109 @@ describeP = do s <- Tok.describeP e <- P.many1 Tok.notSemicolonP return $ s <> last e + +setP :: Parser Range +setP = do + s <- Tok.setP + _ <- choice [Tok.roleP, Tok.sessionP, Tok.timezoneP] + ts <- P.many Tok.notSemicolonP + pure $ case reverse ts of + [] -> s + e:_ -> s <> e + +constantP :: Parser (Constant Range) +constantP = choice + [ uncurry (flip StringConstant) + <$> (try (optional Tok.timestampP) >> Tok.stringP) + + , uncurry (flip NumericConstant) <$> Tok.numberP + , NullConstant <$> Tok.nullP + , uncurry (flip BooleanConstant) <$> choice + [ Tok.trueP >>= \ r -> return (True, r) + , Tok.falseP >>= \ r -> return (False, r) + ] + ] + +-- TODO: support create table with column definitions +createTableP :: Parser (CreateTable Presto RawNames Range) +createTableP = do + s <- Tok.createP + _ <- Tok.tableP + + let createTablePersistence = Persistent + createTableExternality = Internal + createTableExtra = Nothing + + createTableIfNotExists <- ifNotExistsP + + createTableName <- tableNameP + columns <- optionMaybe columnListP + _ <- optional commentP + _ <- optional propertiesP + createTableDefinition <- choice [createTableAsP columns] + + let e = getInfo createTableDefinition + createTableInfo = s <> e + pure CreateTable{..} + + where + createTableAsP columns = do + s <- Tok.asP + (query, qInfo) <- choice + [ do + s' <- Tok.openP + q <- queryP + e <- Tok.closeP + return (q, s' <> e) + , do + q <- queryP + return (q, getInfo q) + ] + withData <- optionMaybe withDataP + let e = fromMaybe qInfo withData + return $ TableAs (s <> e) columns query + + columnListP :: Parser (NonEmpty (UQColumnName Range)) + columnListP = do + _ <- Tok.openP + c:cs <- (`sepBy1` Tok.commaP) $ do + (name, r) <- Tok.columnNameP + pure $ QColumnName r None name + _ <- Tok.closeP + pure (c:|cs) + + +commentP :: Parser Range +commentP = do + s <- Tok.commentP + (_, e) <- Tok.stringP + return $ s <> e + +propertiesP :: Parser Range +propertiesP = do + s <- Tok.withP + _ <- Tok.openP + _ <- propertyP `sepBy1` Tok.commaP + e <- Tok.closeP + return $ s <> e + +propertyP :: Parser Range +propertyP = do + (_, s) <- Tok.propertyNameP + _ <- Tok.equalP + e <- exprP + return $ s <> getInfo e + +withDataP :: Parser Range +withDataP = do + s <- Tok.withP + _ <- optionMaybe Tok.noP + e <- Tok.dataP + return $ s <> e + +ifNotExistsP :: Parser (Maybe Range) +ifNotExistsP = optionMaybe $ do + s <- Tok.ifP + _ <- Tok.notP + e <- Tok.existsP + pure $ s <> e diff --git a/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs b/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs index 7420a66..08d835e 100644 --- a/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs +++ b/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs @@ -121,6 +121,9 @@ closeAngleP = symbolP ">" questionMarkP :: Parser Range questionMarkP = symbolP "?" +equalP :: Parser Range +equalP = symbolP "=" + stringP :: Parser (ByteString, Range) stringP = P.tokenPrim showTok posFromTok testTok where @@ -308,6 +311,26 @@ columnNameP = P.tokenPrim showTok posFromTok testTok _ -> Nothing +lambdaParamP :: Parser (Text, Range) +lambdaParamP = P.tokenPrim showTok posFromTok testTok + where + testTok (tok, s, e) = case tok of + TokWord True name -> Just (name, Range s e) + TokWord False name + | wordCanBeColumnName (wordInfo name) -> Just (name, Range s e) + + _ -> Nothing + +propertyNameP :: Parser (Text, Range) +propertyNameP = P.tokenPrim showTok posFromTok testTok + where + testTok (tok, s, e) = case tok of + TokWord True name -> Just (name, Range s e) + TokWord False name + | wordCanBeColumnName (wordInfo name) -> Just (name, Range s e) + + _ -> Nothing + structFieldNameP :: Parser (Text, Range) structFieldNameP = P.tokenPrim showTok posFromTok testTok where @@ -635,3 +658,30 @@ whereP = keywordP "where" withP :: Parser Range withP = keywordP "with" + +setP :: Parser Range +setP = keywordP "set" + +roleP :: Parser Range +roleP = keywordP "role" + +sessionP :: Parser Range +sessionP = keywordP "session" + +windowP :: Parser Range +windowP = keywordP "window" + +windowNameP :: Parser (Text, Range) +windowNameP = P.tokenPrim showTok posFromTok testNameTok + +createP :: Parser Range +createP = keywordP "create" + +commentP :: Parser Range +commentP = keywordP "comment" + +noP :: Parser Range +noP = keywordP "no" + +dataP :: Parser Range +dataP = keywordP "data" diff --git a/dialects/presto/src/Database/Sql/Presto/Scanner.hs b/dialects/presto/src/Database/Sql/Presto/Scanner.hs index e611e90..38d8d2a 100644 --- a/dialects/presto/src/Database/Sql/Presto/Scanner.hs +++ b/dialects/presto/src/Database/Sql/Presto/Scanner.hs @@ -57,6 +57,7 @@ operators = sortBy (flip compare) , "||" , "(", ")", "[", "]", ",", ";" , "?" + , "->" ] isOperator :: Char -> Bool diff --git a/dialects/presto/src/Database/Sql/Presto/Token.hs b/dialects/presto/src/Database/Sql/Presto/Token.hs index 2dee594..115b94f 100644 --- a/dialects/presto/src/Database/Sql/Presto/Token.hs +++ b/dialects/presto/src/Database/Sql/Presto/Token.hs @@ -122,4 +122,7 @@ wordInfo word = maybe (WordInfo True True True True) id $ M.lookup word $ M.from , ("when", WordInfo False False False False) , ("where", WordInfo False False False False) , ("with", WordInfo False False False False) + , ("window", WordInfo False False False False) + , ("no", WordInfo True True True True) + , ("data", WordInfo True True True True) ] diff --git a/dialects/presto/src/Database/Sql/Presto/Type.hs b/dialects/presto/src/Database/Sql/Presto/Type.hs index f392343..0a72212 100644 --- a/dialects/presto/src/Database/Sql/Presto/Type.hs +++ b/dialects/presto/src/Database/Sql/Presto/Type.hs @@ -65,7 +65,7 @@ instance Dialect Presto where , bindForGroup = bindFromColumns fromColumns , bindForHaving = bindFromColumns fromColumns , bindForOrder = bindBothColumns fromColumns selectionAliases - , bindForNamedWindow = bindColumns [] -- Presto doesn't have this language feature + , bindForNamedWindow = bindColumns fromColumns } areLcolumnsVisibleInLateralViews _ = True diff --git a/dialects/vertica/src/Database/Sql/Vertica/Parser.hs b/dialects/vertica/src/Database/Sql/Vertica/Parser.hs index e875b23..9ad7394 100644 --- a/dialects/vertica/src/Database/Sql/Vertica/Parser.hs +++ b/dialects/vertica/src/Database/Sql/Vertica/Parser.hs @@ -1032,7 +1032,8 @@ makeExprAlias (FieldAccessExpr _ _ _) = fail "Unsupported struct access in Verti makeExprAlias (ArrayAccessExpr _ _ _) = fail "Unsupported array access in Vertica: unused datatype in this dialect" makeExprAlias (TypeCastExpr _ _ expr _) = makeExprAlias expr makeExprAlias (VariableSubstitutionExpr _) = fail "Unsupported variable substitution in Vertica: unused datatype in this dialect" - +makeExprAlias LambdaParamExpr {} = error "Unreachable, vertica does not support lambda" +makeExprAlias LambdaExpr {} = error "Unreachable, vertica does not support lambda" aliasP :: Expr RawNames Range -> Parser (ColumnAlias Range) aliasP expr = choice diff --git a/src/Database/Sql/Info.hs b/src/Database/Sql/Info.hs index 6fbedf2..ad9032b 100644 --- a/src/Database/Sql/Info.hs +++ b/src/Database/Sql/Info.hs @@ -198,6 +198,8 @@ instance HasInfo (Expr r a) where getInfo (ArrayAccessExpr info _ _) = info getInfo (TypeCastExpr info _ _ _) = info getInfo (VariableSubstitutionExpr info) = info + getInfo (LambdaParamExpr info _) = info + getInfo (LambdaExpr info _ _) = info instance HasInfo (Filter r a) where type Info (Filter r a) = a @@ -350,6 +352,7 @@ instance HasInfo (Tablish r a) where type Info (Tablish r a) = a getInfo (TablishTable info _ _) = info getInfo (TablishSubQuery info _ _) = info + getInfo (TablishParenthesizedRelation info _ _) = info getInfo (TablishJoin info _ _ _ _) = info getInfo (TablishLateralView info _ _) = info @@ -386,3 +389,7 @@ instance HasInfo (Escape r a) where instance HasInfo (Pattern r a) where type Info (Pattern r a) = a getInfo = getInfo . patternExpr + +instance HasInfo (LambdaParam a) where + type Info (LambdaParam a) = a + getInfo (LambdaParam info _ _) = info diff --git a/src/Database/Sql/Type/Names.hs b/src/Database/Sql/Type/Names.hs index 0197af2..511774b 100644 --- a/src/Database/Sql/Type/Names.hs +++ b/src/Database/Sql/Type/Names.hs @@ -386,6 +386,10 @@ newtype ColumnAliasId = ColumnAliasId Integer deriving (Data, Generic, Read, Show, Eq, Ord) +newtype LambdaParamId + = LambdaParamId Integer + deriving (Data, Generic, Read, Show, Eq, Ord) + instance (Arbitrary (f (QTableName f a)), Arbitrary a) => Arbitrary (QColumnName f a) where arbitrary = do Identifier name :: Identifier '["fooColumn", "barColumn"] <- arbitrary @@ -399,6 +403,12 @@ data ColumnAlias a , Read, Show, Eq, Ord , Functor, Foldable, Traversable) +data LambdaParam a + = LambdaParam a Text LambdaParamId + deriving ( Data, Generic + , Read, Show, Eq, Ord + , Functor, Foldable, Traversable) + columnAliasName :: ColumnAlias a -> UQColumnName a columnAliasName (ColumnAlias info name _) = QColumnName info None name @@ -517,6 +527,14 @@ instance ToJSON a => ToJSON (ColumnAlias a) where , "ident" .= ident ] +instance ToJSON a => ToJSON (LambdaParam a) where + toJSON (LambdaParam info name (LambdaParamId ident)) = object + [ "tag" .= String "LambdaParam" + , "info" .= info + , "name" .= name + , "ident" .= ident + ] + instance ToJSON a => ToJSON (StructFieldName a) where toJSON (StructFieldName info name) = object @@ -622,6 +640,16 @@ instance FromJSON a => FromJSON (ColumnAlias a) where , show v ] +instance FromJSON a => FromJSON (LambdaParam a) where + parseJSON (Object o) = do + String "LambdaParam" <- o .: "tag" + LambdaParam <$> o .: "info" <*> o .: "name" <*> (LambdaParamId <$> o .: "ident") + + parseJSON v = fail $ unwords + [ "don't know how to parse as LambdaParam:" + , show v + ] + instance FromJSON a => FromJSON (StructFieldName a) where parseJSON (Object o) = do String "StructFieldName" <- o .: "tag" diff --git a/src/Database/Sql/Type/Query.hs b/src/Database/Sql/Type/Query.hs index 629ab81..014d877 100644 --- a/src/Database/Sql/Type/Query.hs +++ b/src/Database/Sql/Type/Query.hs @@ -156,6 +156,7 @@ data Tablish r a | TablishJoin a (JoinType a) (JoinCondition r a) (Tablish r a) (Tablish r a) | TablishLateralView a (LateralView r a) (Maybe (Tablish r a)) + | TablishParenthesizedRelation a (TablishAliases a) (Tablish r a) deriving instance (ConstrainSNames Data r a, Data r) => Data (Tablish r a) deriving instance Generic (Tablish r a) @@ -417,6 +418,8 @@ data Expr r a | ArrayAccessExpr a (Expr r a) (Expr r a) | TypeCastExpr a CastFailureAction (Expr r a) (DataType a) | VariableSubstitutionExpr a + | LambdaParamExpr a (LambdaParam a) + | LambdaExpr a [LambdaParam a] (Expr r a) deriving instance (ConstrainSNames Data r a, Data r) => Data (Expr r a) deriving instance Generic (Expr r a) @@ -947,6 +950,18 @@ instance ConstrainSNames ToJSON r a => ToJSON (Expr r a) where , "info" .= info ] + toJSON (LambdaParamExpr info param) = object + [ "tag" .= String "LambdaParamExpr" + , "info" .= info + , "param" .= param + ] + + toJSON (LambdaExpr info params body) = object + [ "tag" .= String "LambdaExpr" + , "info" .= info + , "params" .= params + , "body" .= body + ] instance ToJSON a => ToJSON (ArrayIndex a) where toJSON (ArrayIndex info value) = object @@ -1227,6 +1242,13 @@ instance ConstrainSNames ToJSON r a => ToJSON (Tablish r a) where , "query" .= query ] + toJSON (TablishParenthesizedRelation info alias relation) = object + [ "tag" .= String "TablishParenthesizedRelation" + , "info" .= info + , "alias" .= alias + , "realtion" .= relation + ] + toJSON (TablishJoin info join condition outer inner) = object [ "tag" .= String "TablishJoin" , "info" .= info diff --git a/src/Database/Sql/Type/Scope.hs b/src/Database/Sql/Type/Scope.hs index d94be53..7240ae6 100644 --- a/src/Database/Sql/Type/Scope.hs +++ b/src/Database/Sql/Type/Scope.hs @@ -90,6 +90,7 @@ data ResolverInfo a = ResolverInfo { catalog :: Catalog , onCTECollision :: forall x . (x -> x) -> (x -> x) , bindings :: Bindings a + , lambdaScope :: [[LambdaParam a]] , selectScope :: FromColumns a -> SelectionAliases a -> SelectScope a , lcolumnsAreVisibleInLateralViews :: Bool } @@ -101,6 +102,8 @@ 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, ..}) +bindLambdaParams :: MonadReader (ResolverInfo a) m => [LambdaParam a] -> m r -> m r +bindLambdaParams params = local (\ResolverInfo{..} -> ResolverInfo{lambdaScope = params:lambdaScope, ..}) bindFromColumns :: MonadReader (ResolverInfo a) m => FromColumns a -> m r -> m r bindFromColumns = bindColumns diff --git a/src/Database/Sql/Util/Columns.hs b/src/Database/Sql/Util/Columns.hs index 6fd8de6..50089f5 100644 --- a/src/Database/Sql/Util/Columns.hs +++ b/src/Database/Sql/Util/Columns.hs @@ -38,7 +38,7 @@ import Control.Monad.Reader import Control.Monad.Writer import Database.Sql.Type -import Database.Sql.Util.Scope (queryColumnNames) +import Database.Sql.Util.Scope (queryColumnNames, tablishColumnNames) type Clause = Text -- SELECT, WHERE, GROUPBY, etc... for nested clauses, -- report the innermost clause. @@ -358,6 +358,16 @@ instance HasColumns (Tablish ResolvedNames a) where TablishAliasesTC _ cAliases -> tell $ zipWith aliasObservation cAliases (queryColumnDeps query) + goColumns (TablishParenthesizedRelation _ tablishAliases relation) = do + goColumns relation + + case tablishAliases of + TablishAliasesNone -> return () + TablishAliasesT _ -> return () + TablishAliasesTC _ cAliases -> + let cRefSets = map S.singleton $ tablishColumnNames relation + in tell $ zipWith aliasObservation cAliases cRefSets + goColumns (TablishJoin _ _ cond lhs rhs) = do bindClause "JOIN" $ goColumns cond goColumns lhs @@ -454,6 +464,8 @@ instance HasColumns (Expr ResolvedNames a) where goColumns (ArrayAccessExpr _ expr idx) = mapM_ goColumns [expr, idx] -- NB we aren't emitting any special info about array access (for now) goColumns (TypeCastExpr _ _ expr _) = goColumns expr goColumns (VariableSubstitutionExpr _) = return () + goColumns (LambdaParamExpr _ _) = return () + goColumns (LambdaExpr _ _ body) = goColumns body instance HasColumns (Escape ResolvedNames a) where goColumns (Escape expr) = goColumns expr diff --git a/src/Database/Sql/Util/Eval.hs b/src/Database/Sql/Util/Eval.hs index 27eb0d4..d32dc88 100644 --- a/src/Database/Sql/Util/Eval.hs +++ b/src/Database/Sql/Util/Eval.hs @@ -121,6 +121,8 @@ class (Monad (EvalRow e), Monad (EvalMonad e), Traversable (EvalRow e)) => Evalu 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) + handleLambdaParam :: Proxy e -> LambdaParam Range -> EvalT e 'ExprContext (EvalMonad e) (EvalValue e) + handleLambda :: Proxy e -> [LambdaParam Range] -> Expr 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) @@ -215,6 +217,7 @@ instance Evaluation e => Evaluate e (Tablish ResolvedNames Range) where Just result -> pure result eval p (TablishSubQuery _ _ query) = eval p query + eval p (TablishParenthesizedRelation _ _ relation) = eval p relation eval p (TablishJoin _ joinType cond lhs rhs) = do x <- eval p lhs y <- eval p rhs @@ -386,6 +389,8 @@ instance Evaluation e => Evaluate e (Expr ResolvedNames Range) where 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" + eval p (LambdaParamExpr _ param) = handleLambdaParam p param + eval p (LambdaExpr _ params body) = handleLambda p params body instance Evaluation e => Evaluate e (Constant a) where type EvalResult e (Constant a) = EvalT e 'ExprContext (EvalMonad e) (EvalValue e) diff --git a/src/Database/Sql/Util/Eval/Concrete.hs b/src/Database/Sql/Util/Eval/Concrete.hs index c31fce9..4904488 100644 --- a/src/Database/Sql/Util/Eval/Concrete.hs +++ b/src/Database/Sql/Util/Eval/Concrete.hs @@ -116,6 +116,10 @@ instance Evaluation Concrete where handleFunction _ _ _ _ _ _ _ = throwError "function exprs not yet supported" + handleLambdaParam _ _ = error "unreachable, lambda should be handled inside a function" + + handleLambda _ _ _ = error "unreachable, lambda should be handled inside a function" + handleLike _ _ _ _ _ = throwError "concrete evaluation for LIKE expressions not yet supported" handleOrder p orders (RecordSet cs rs) = do diff --git a/src/Database/Sql/Util/Joins.hs b/src/Database/Sql/Util/Joins.hs index 3913398..d6788b5 100644 --- a/src/Database/Sql/Util/Joins.hs +++ b/src/Database/Sql/Util/Joins.hs @@ -320,6 +320,8 @@ getJoinsExpr (FieldAccessExpr _ expr field) = go expr $ FieldChain $ M.singleton getJoinsExpr (ArrayAccessExpr _ expr index) = M.unionsWith (<>) <$> mapM getJoinsExpr [expr, index] getJoinsExpr (TypeCastExpr _ _ expr _) = getJoinsExpr expr getJoinsExpr (VariableSubstitutionExpr _) = return M.empty +getJoinsExpr (LambdaParamExpr _ _) = return M.empty +getJoinsExpr (LambdaExpr _ _ body) = getJoinsExpr body getJoinsFilter :: Filter ResolvedNames a -> Scoped () getJoinsFilter (Filter _ expr) = void $ getJoinsExpr expr @@ -354,6 +356,7 @@ getJoinsTablish (TablishLateralView _ LateralView{..} lhs) = do mapM_ getJoinsExpr lateralViewExprs getJoinsTablish (TablishSubQuery _ _ query) = getJoinsQuery query +getJoinsTablish (TablishParenthesizedRelation _ _ relation) = getJoinsTablish relation getJoinsTablish (TablishJoin _ _ (JoinNatural _ (RNaturalColumns columns)) lhs rhs) = do getJoinsTablish lhs getJoinsTablish rhs diff --git a/src/Database/Sql/Util/Lineage/ColumnPlus.hs b/src/Database/Sql/Util/Lineage/ColumnPlus.hs index 6d1b093..fa178d9 100644 --- a/src/Database/Sql/Util/Lineage/ColumnPlus.hs +++ b/src/Database/Sql/Util/Lineage/ColumnPlus.hs @@ -152,6 +152,10 @@ instance Evaluation ColumnLineage where ++ map filterExpr (maybeToList filter') ) + handleLambdaParam _ _ = pure emptyColumnPlusSet + + handleLambda p _ body = eval p body + handleGroups cs gs = pure $ RecordSet cs $ do (g, rs) <- gs diff --git a/src/Database/Sql/Util/Scope.hs b/src/Database/Sql/Util/Scope.hs index 5a44751..bb03d8a 100644 --- a/src/Database/Sql/Util/Scope.hs +++ b/src/Database/Sql/Util/Scope.hs @@ -26,7 +26,7 @@ module Database.Sql.Util.Scope ( runResolverWarn, runResolverWError, runResolverNoWarn , WithColumns (..) - , queryColumnNames + , queryColumnNames, tablishColumnNames , resolveStatement, resolveQuery, resolveQueryWithColumns, resolveSelectAndOrders, resolveCTE, resolveInsert , resolveInsertValues, resolveDefaultExpr, resolveDelete, resolveTruncate , resolveCreateTable, resolveTableDefinition, resolveColumnOrConstraint @@ -42,6 +42,7 @@ module Database.Sql.Util.Scope import Data.Maybe (mapMaybe) import Data.Either (lefts, rights) +import Data.List (find) import Database.Sql.Type import qualified Data.List.NonEmpty as NonEmpty @@ -67,6 +68,7 @@ makeResolverInfo dialect catalog = ResolverInfo if shouldCTEsShadowTables dialect then \ f x -> f x else \ _ x -> x + , lambdaScope = [] , selectScope = getSelectScope dialect , lcolumnsAreVisibleInLateralViews = areLcolumnsVisibleInLateralViews dialect , .. @@ -237,6 +239,37 @@ queryColumnNames (QueryOrder _ _ query) = queryColumnNames query queryColumnNames (QueryLimit _ _ query) = queryColumnNames query queryColumnNames (QueryOffset _ _ query) = queryColumnNames query + +tablishColumnNames :: Tablish ResolvedNames a -> [RColumnRef a] +tablishColumnNames (TablishTable _ tablishAliases tableRef) = + case tablishAliases of + TablishAliasesNone -> getColumnList tableRef + TablishAliasesT _ -> getColumnList tableRef + TablishAliasesTC _ cAliases -> map RColumnAlias cAliases + +tablishColumnNames (TablishSubQuery _ tablishAliases query) = + case tablishAliases of + TablishAliasesNone -> queryColumnNames query + TablishAliasesT _ -> queryColumnNames query + TablishAliasesTC _ cAliases -> map RColumnAlias cAliases + +tablishColumnNames (TablishParenthesizedRelation _ tablishAliases relation) = + case tablishAliases of + TablishAliasesNone -> tablishColumnNames relation + TablishAliasesT _ -> tablishColumnNames relation + TablishAliasesTC _ cAliases -> map RColumnAlias cAliases + +tablishColumnNames (TablishJoin _ _ _ lhs rhs) = + tablishColumnNames lhs ++ tablishColumnNames rhs + +tablishColumnNames (TablishLateralView _ LateralView{..} lhs) = + let cols = maybe [] tablishColumnNames lhs in + case lateralViewAliases of + TablishAliasesNone -> cols + TablishAliasesT _ -> cols + TablishAliasesTC _ cAliases -> cols ++ map RColumnAlias cAliases + + resolveSelectAndOrders :: Select RawNames a -> [Order RawNames a] -> Resolver (WithColumnsAndOrders (Select ResolvedNames)) a resolveSelectAndOrders Select{..} orders = do (selectFrom', columns) <- traverse resolveSelectFrom selectFrom >>= \case @@ -531,7 +564,7 @@ resolveExpr (LikeExpr info op escape pattern expr) = do pure $ LikeExpr info op escape' pattern' expr' resolveExpr (ConstantExpr info constant) = pure $ ConstantExpr info constant -resolveExpr (ColumnExpr info column) = ColumnExpr info <$> resolveColumnName column +resolveExpr (ColumnExpr info column) = resolveLambdaParamOrColumnName info column resolveExpr (InListExpr info list expr) = InListExpr info <$> mapM resolveExpr list <*> resolveExpr expr resolveExpr (InSubqueryExpr info query expr) = do query' <- resolveQuery query @@ -567,6 +600,10 @@ resolveExpr (FieldAccessExpr info expr field) = FieldAccessExpr info <$> resolve resolveExpr (ArrayAccessExpr info expr idx) = ArrayAccessExpr info <$> resolveExpr expr <*> resolveExpr idx resolveExpr (TypeCastExpr info onFail expr type_) = TypeCastExpr info onFail <$> resolveExpr expr <*> pure type_ resolveExpr (VariableSubstitutionExpr info) = pure $ VariableSubstitutionExpr info +resolveExpr (LambdaParamExpr info param) = pure $ LambdaParamExpr info param +resolveExpr (LambdaExpr info params body) = do + expr <- bindLambdaParams params $ resolveExpr body + pure $ LambdaExpr info params expr resolveOrder :: [Expr ResolvedNames a] -> Order RawNames a @@ -647,12 +684,22 @@ resolveTableRef tableName = do ResolverInfo{catalog = Catalog{..}, bindings = Bindings{..}} <- ask lift $ lift $ catalogResolveTableRef boundCTEs tableName - resolveColumnName :: forall a . OQColumnName a -> Resolver RColumnRef a resolveColumnName columnName = do (Catalog{..}, Bindings{..}) <- asks (catalog &&& bindings) lift $ lift $ catalogResolveColumnName boundColumns columnName +resolveLambdaParamOrColumnName :: forall a . a -> OQColumnName a -> Resolver (Expr ResolvedNames) a +resolveLambdaParamOrColumnName info columnName = do + params <- asks lambdaScope + case isParam params columnName of + Just name -> pure $ LambdaParamExpr info name + Nothing -> ColumnExpr info <$> resolveColumnName columnName + where + isParam :: [[LambdaParam a]] -> OQColumnName a -> Maybe (LambdaParam a) + isParam _ (QColumnName _ (Just _) _) = Nothing + isParam params (QColumnName _ Nothing name) = find (\(LambdaParam _ pname _) -> pname == name) $ concat params + resolvePartition :: Partition RawNames a -> Resolver (Partition ResolvedNames) a resolvePartition (PartitionBy info exprs) = PartitionBy info <$> mapM resolveExpr exprs @@ -689,6 +736,16 @@ resolveTablish (TablishSubQuery info aliases query) = do pure $ WithColumns (TablishSubQuery info aliases query') [(tAlias, cAliases)] +resolveTablish (TablishParenthesizedRelation info aliases relation) = do + WithColumns relation' columns <- resolveTablish relation + let colRefs = concatMap snd columns + columns' = case aliases of + TablishAliasesNone -> columns + TablishAliasesT t -> map (first $ const $ Just $ RTableAlias t colRefs) columns + TablishAliasesTC t cs -> [(Just $ RTableAlias t colRefs, map RColumnAlias cs)] + + pure $ WithColumns (TablishParenthesizedRelation info aliases relation') columns' + resolveTablish (TablishJoin info joinType cond lhs rhs) = do WithColumns lhs' lcolumns <- resolveTablish lhs diff --git a/src/Database/Sql/Util/Tables.hs b/src/Database/Sql/Util/Tables.hs index 2dc8524..ec6629d 100644 --- a/src/Database/Sql/Util/Tables.hs +++ b/src/Database/Sql/Util/Tables.hs @@ -269,6 +269,7 @@ instance HasTables (Tablish ResolvedNames a) where goTables (TablishTable _ _ (RTableRef fqtn _)) = emitTable fqtn goTables (TablishTable _ _ (RTableAlias _ _)) = return () goTables (TablishSubQuery _ _ query) = goTables query + goTables (TablishParenthesizedRelation _ _ relation) = goTables relation goTables (TablishLateralView _ LateralView{..} lhs) = goTables lhs >> mapM_ goTables lateralViewExprs goTables (TablishJoin _ _ cond outer inner) = do case cond of @@ -320,6 +321,8 @@ instance HasTables (Expr ResolvedNames a) where goTables subscript goTables (TypeCastExpr _ _ expr _) = goTables expr goTables (VariableSubstitutionExpr _) = return () + goTables (LambdaParamExpr _ _) = return () + goTables (LambdaExpr _ _ body) = goTables body instance HasTables (Filter ResolvedNames a) where goTables (Filter _ expr) = goTables expr diff --git a/test/Database/Sql/Presto/Parser/Test.hs b/test/Database/Sql/Presto/Parser/Test.hs index 40a822a..4b92f61 100644 --- a/test/Database/Sql/Presto/Parser/Test.hs +++ b/test/Database/Sql/Presto/Parser/Test.hs @@ -119,6 +119,7 @@ testParser = test , "SELECT CURRENT_TIME(1);" , "SELECT CURRENT_DATE;" , "SELECT ARRAY[1+1, LOCALTIME];" + , "SELECT ARRAY[];" , "SELECT CAST(NULL AS BIGINT);" , "SELECT TRY_CAST(NULL AS BIGINT);" , "SELECT TRY_CAST(NULL AS BIGINT ARRAY);" @@ -327,6 +328,103 @@ testParser = test -- test DROP VIEW , "DROP VIEW foo;" , "DROP VIEW IF EXISTS foo;" + + -- test parenthesized relation + , "SELECT * FROM (foo CROSS JOIN bar);" + , "SELECT a,b FROM (foo CROSS JOIN bar);" + , "SELECT a,b FROM (foo CROSS JOIN bar) a;" + , "SELECT a,b FROM (foo CROSS JOIN bar) a (a,b);" + + -- test set statement + , "SET ROLE NONE;" + , "SET SESSION optimize_hash_generation = true;" + , "SET SESSION data.optimize_locality_enabled = false;" + , "SET TIME ZONE LOCAL;" + , "SET TIME ZONE '-08:00';" + , "SET TIME ZONE INTERVAL '10' HOUR;" + , "SET TIME ZONE INTERVAL -'08:00' HOUR TO MINUTE;" + , "SET TIME ZONE 'America/Los_Angeles';" + , "SET TIME ZONE concat_ws('/', 'America', 'Los_Angeles');" + + -- Named windows can substitute for window exprs in OVER clauses + , "SELECT RANK() OVER x FROM potato WINDOW x AS (PARTITION BY a ORDER BY b ASC);" + -- They can also be inherited, as long as orderby is not double-defined + , TL.unlines + [ "SELECT RANK() OVER (x ORDER BY b ASC)," + , "DENSE_RANK() OVER (x ORDER BY b DESC)" + , "FROM potato WINDOW x AS (PARTITION BY a);" + ] + , TL.unlines + [ "SELECT RANK() OVER (x ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)" + , "FROM potato WINDOW x AS (PARTITION BY a ORDER BY b ASC);" + ] + -- Unlike vertica, hive can have anything in its named window clause + -- Which is vomitous, since frame clauses mean nothing without order + , "SELECT RANK() OVER (x) FROM potato WINDOW x AS (PARTITION BY a);" + , "SELECT RANK() OVER (x) FROM potato WINDOW x AS (ORDER BY a);" + , "SELECT RANK() OVER (x) FROM potato WINDOW x AS (ROWS UNBOUNDED PRECEDING);" + , "SELECT RANK() OVER (x) FROM potato WINDOW x AS ();" + , "SELECT RANK() OVER (x) FROM potato WINDOW x AS (PARTITION BY a ORDER BY a ROWS UNBOUNDED PRECEDING);" + -- Named windows can also inherit from each other, + -- similar to WITH clauses + , TL.unlines + [ "SELECT RANK() OVER (w1 ORDER BY sal DESC)," + , "RANK() OVER w2" + , "FROM EMP" + , "WINDOW w1 AS (PARTITION BY deptno), w2 AS (w1 ORDER BY sal);" + ] + -- In hive, they can inherit in all their glory. + -- Ambiguous definitions? Frames without orders? It's got it. + , TL.unlines + [ "SELECT RANK() OVER (w2) FROM EMP" + , "WINDOW w1 AS (PARTITION BY deptno)," + , "w2 AS (w1 PARTITION BY alt ORDER BY sal);" + ] + , TL.unlines + [ "SELECT RANK() OVER (w2) FROM EMP" + , "WINDOW w1 AS (PARTITION BY deptno)," + , "w2 AS (w1 ORDER BY sal ROWS UNBOUNDED PRECEDING);" + ] + , TL.unlines + [ "SELECT RANK() OVER (w2 PARTITION BY foo) FROM EMP" + , "WINDOW w1 AS (PARTITION BY deptno)," + , "w2 AS (w1 PARTITION BY sal ROWS UNBOUNDED PRECEDING);" + ] + , TL.unlines + [ "SELECT RANK() OVER (w2 PARTITION BY a ORDER BY b ROWS UNBOUNDED PRECEDING)" + , "FROM EMP" + , "WINDOW w1 AS (PARTITION BY c ORDER BY d ROWS UNBOUNDED PRECEDING)," + , "w2 AS (w1 PARTITION BY e ORDER BY f ROWS UNBOUNDED PRECEDING);" + ] + -- These should parse Successfully, but fail to resolve: + -- Named window uses cannot include already defined components + , TL.unlines + [ "SELECT RANK() OVER (x ORDER BY c) FROM potato" + , "WINDOW x AS (PARTITION BY a ORDER BY b);" + ] + -- Named windows must have unique names + , TL.unlines + [ "SELECT RANK() OVER x FROM potato" + , "WINDOW x as (PARTITION BY a), x AS (PARTITION BY b);" + ] + , TL.unlines + [ "SELECT RANK() OVER (x) FROM potato" + , "WINDOW x AS (ORDER BY b);" + ] + + -- test lambda + , "SELECT numbers, transform(numbers, (n) -> n * n);" + , "SELECT transform(prices, n -> TRY_CAST(n AS VARCHAR) || '$');" + , "SELECT * FROM foo WHERE any_match(numbers, n -> COALESCE(n, 0) > 100);" + + -- test create table as + , "CREATE TABLE t (order_date, total_price) AS SELECT orderdate, totalprice" + , "CREATE TABLE t AS (SELECT orderdate, totalprice)" + , "CREATE TABLE t COMMENT 'Summary of orders by date' WITH (format = 'ORC') AS SELECT orderdate, totalprice" + , "CREATE TABLE IF NOT EXISTS t AS SELECT orderdate, totalprice" + , "CREATE TABLE t AS SELECT orderdate, totalprice WITH NO DATA" + , "CREATE TABLE t AS SELECT orderdate, totalprice WITH DATA" + , "CREATE TABLE IF NOT EXISTS t (a, b) COMMENT 'Summary of orders by date' WITH (format = 'ORC') AS SELECT orderdate, totalprice WITH NO DATA" ] , "Exclude some broken examples" ~: map (TestCase . parsesUnsuccessfully) @@ -357,6 +455,8 @@ testParser = test , "WITH x AS (SELECT 1 AS a FROM dual) SELECT * FROM X ORDER BY a UNION SELECT * FROM X ;" , "SELECT 1 EXCEPT ALL SELECT 1;" , "SELECT 1 INTERSECT ALL SELECT 1;" + , "SELECT n -> n;" + , "SELECT (n) -> n;" ] , ticket "T655130" diff --git a/test/Database/Sql/Util/Columns/Test.hs b/test/Database/Sql/Util/Columns/Test.hs index 076fc9b..944ba63 100644 --- a/test/Database/Sql/Util/Columns/Test.hs +++ b/test/Database/Sql/Util/Columns/Test.hs @@ -84,6 +84,48 @@ testColumnAccesses = test , (FullyQualifiedColumnName "default_db" "public" "bar" "b", "SELECT") ] ) + , testPresto "SELECT * FROM (bar);" defaultTestCatalog + ((@=?) $ S.fromList + [ (FullyQualifiedColumnName "default_db" "public" "bar" "a", "SELECT") + , (FullyQualifiedColumnName "default_db" "public" "bar" "b", "SELECT") + ] + ) + , testPresto "SELECT * FROM (bar) t(x,y);" defaultTestCatalog + ((@=?) $ S.fromList + [ (FullyQualifiedColumnName "default_db" "public" "bar" "a", "SELECT") + , (FullyQualifiedColumnName "default_db" "public" "bar" "b", "SELECT") + ] + ) + , testPresto "SELECT * FROM (SELECT * FROM bar) t(x,y);" defaultTestCatalog + ((@=?) $ S.fromList + [ (FullyQualifiedColumnName "default_db" "public" "bar" "a", "SELECT") + , (FullyQualifiedColumnName "default_db" "public" "bar" "b", "SELECT") + ] + ) + , testPresto "SELECT * FROM (foo CROSS JOIN bar) t(x,y,z);" defaultTestCatalog + ((@=?) $ S.fromList + [ (FullyQualifiedColumnName "default_db" "public" "foo" "a", "SELECT") + , (FullyQualifiedColumnName "default_db" "public" "bar" "a", "SELECT") + , (FullyQualifiedColumnName "default_db" "public" "bar" "b", "SELECT") + ] + ) + , testPresto "SELECT * FROM (bar CROSS JOIN UNNEST(ARRAY[1,2]) AS t1(x)) t;" defaultTestCatalog + ((@=?) $ S.fromList + [ (FullyQualifiedColumnName "default_db" "public" "bar" "a", "SELECT") + , (FullyQualifiedColumnName "default_db" "public" "bar" "b", "SELECT") + ] + ) + , testPresto "SELECT f(a -> a + b) FROM bar;" defaultTestCatalog + ((@=?) $ S.fromList + [ (FullyQualifiedColumnName "default_db" "public" "bar" "b", "SELECT") + ] + ) + , testPresto "SELECT f(a, a -> a + b) FROM bar;" defaultTestCatalog + ((@=?) $ S.fromList + [ (FullyQualifiedColumnName "default_db" "public" "bar" "a", "SELECT") + , (FullyQualifiedColumnName "default_db" "public" "bar" "b", "SELECT") + ] + ) -- FROM , testAll "SELECT 1 FROM foo JOIN bar ON foo.a = bar.a;" defaultTestCatalog @@ -108,6 +150,12 @@ testColumnAccesses = test ((@=?) $ S.singleton (FullyQualifiedColumnName "default_db" "public" "bar" "a", "SELECT") ) + , testPresto "SELECT 1 FROM (foo JOIN bar ON foo.a = bar.a);" defaultTestCatalog + ((@=?) $ S.fromList + [ (FullyQualifiedColumnName "default_db" "public" "foo" "a", "JOIN") + , (FullyQualifiedColumnName "default_db" "public" "bar" "a", "JOIN") + ] + ) -- WHERE , testAll "SELECT 1 FROM foo WHERE a IS NOT NULL;" defaultTestCatalog @@ -187,6 +235,12 @@ testColumnAccesses = test , (FullyQualifiedColumnName "default_db" "public" "foo" "a", "ORDER") ] ) + , testPresto "SELECT cAlias FROM (foo) AS tAlias (cAlias) ORDER BY cAlias;" defaultTestCatalog + ((@=?) $ S.fromList + [ (FullyQualifiedColumnName "default_db" "public" "foo" "a", "SELECT") + , (FullyQualifiedColumnName "default_db" "public" "foo" "a", "ORDER") + ] + ) , let query = TL.unlines [ "SELECT arrayVal" , "FROM foo AS fooAlias (arrayCol)" diff --git a/test/Database/Sql/Util/Scope/Test.hs b/test/Database/Sql/Util/Scope/Test.hs index e935bcc..fd31914 100644 --- a/test/Database/Sql/Util/Scope/Test.hs +++ b/test/Database/Sql/Util/Scope/Test.hs @@ -195,6 +195,12 @@ testNoResolveErrors = , "CROSS JOIN UNNEST(numbers) AS t (n);" ] ] + , "test named window" ~: + map (TestCase . parsesAndResolvesSuccessfullyPresto (makeCatalog catalog path currentDatabase)) + [ "SELECT sum(col) OVER x FROM foo WINDOW x AS (partition by col);" + , "SELECT sum(col) OVER (x) FROM foo WINDOW x AS (partition by col);" + , "SELECT sum(col) OVER (x ORDER BY col) FROM foo WINDOW x AS (partition by col);" + ] ]