From 61b70f49dc4fea7fc89308aec397e0f0efefc79c Mon Sep 17 00:00:00 2001 From: nooberfsh Date: Tue, 30 Nov 2021 14:58:31 +0800 Subject: [PATCH 01/10] ignore files generated by intellij idea --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 8c8534a..93582dd 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ queries/ schemas/ results/ + +/out +*.iml From 4714584ad69cf4d761ece67b54b83f1efd5c454c Mon Sep 17 00:00:00 2001 From: nooberfsh Date: Wed, 8 Dec 2021 09:52:15 +0800 Subject: [PATCH 02/10] support parenthesized relation --- dialects/hive/src/Database/Sql/Hive/Parser.hs | 4 ++ .../presto/src/Database/Sql/Presto/Parser.hs | 9 +++- src/Database/Sql/Info.hs | 1 + src/Database/Sql/Type/Query.hs | 8 ++++ src/Database/Sql/Util/Columns.hs | 12 +++++- src/Database/Sql/Util/Eval.hs | 1 + src/Database/Sql/Util/Joins.hs | 1 + src/Database/Sql/Util/Scope.hs | 43 ++++++++++++++++++- src/Database/Sql/Util/Tables.hs | 1 + test/Database/Sql/Presto/Parser/Test.hs | 6 +++ test/Database/Sql/Util/Columns/Test.hs | 43 +++++++++++++++++++ 11 files changed, 126 insertions(+), 3 deletions(-) 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..480da77 100644 --- a/dialects/presto/src/Database/Sql/Presto/Parser.hs +++ b/dialects/presto/src/Database/Sql/Presto/Parser.hs @@ -272,6 +272,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 +363,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 +377,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) diff --git a/src/Database/Sql/Info.hs b/src/Database/Sql/Info.hs index 6fbedf2..7f04030 100644 --- a/src/Database/Sql/Info.hs +++ b/src/Database/Sql/Info.hs @@ -350,6 +350,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 diff --git a/src/Database/Sql/Type/Query.hs b/src/Database/Sql/Type/Query.hs index 629ab81..b79867a 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) @@ -1227,6 +1228,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/Util/Columns.hs b/src/Database/Sql/Util/Columns.hs index 6fd8de6..73e7df6 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 diff --git a/src/Database/Sql/Util/Eval.hs b/src/Database/Sql/Util/Eval.hs index 27eb0d4..11f47ad 100644 --- a/src/Database/Sql/Util/Eval.hs +++ b/src/Database/Sql/Util/Eval.hs @@ -215,6 +215,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 diff --git a/src/Database/Sql/Util/Joins.hs b/src/Database/Sql/Util/Joins.hs index 3913398..39f45e6 100644 --- a/src/Database/Sql/Util/Joins.hs +++ b/src/Database/Sql/Util/Joins.hs @@ -354,6 +354,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/Scope.hs b/src/Database/Sql/Util/Scope.hs index 5a44751..0967a16 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 @@ -237,6 +237,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 @@ -689,6 +720,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..e8c21d0 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 diff --git a/test/Database/Sql/Presto/Parser/Test.hs b/test/Database/Sql/Presto/Parser/Test.hs index 40a822a..1cd8b75 100644 --- a/test/Database/Sql/Presto/Parser/Test.hs +++ b/test/Database/Sql/Presto/Parser/Test.hs @@ -327,6 +327,12 @@ 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);" ] , "Exclude some broken examples" ~: map (TestCase . parsesUnsuccessfully) diff --git a/test/Database/Sql/Util/Columns/Test.hs b/test/Database/Sql/Util/Columns/Test.hs index 076fc9b..b23ec86 100644 --- a/test/Database/Sql/Util/Columns/Test.hs +++ b/test/Database/Sql/Util/Columns/Test.hs @@ -84,6 +84,37 @@ 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") + ] + ) -- FROM , testAll "SELECT 1 FROM foo JOIN bar ON foo.a = bar.a;" defaultTestCatalog @@ -108,6 +139,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 +224,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)" From 9d8ac5dc1ead5a3ddce82508ad487c30e21a1063 Mon Sep 17 00:00:00 2001 From: nooberfsh Date: Wed, 22 Dec 2021 18:23:59 +0800 Subject: [PATCH 03/10] support SET ROLE/SESSION/TIMEZONE statement in presto --- dialects/presto/src/Database/Sql/Presto/Parser.hs | 10 ++++++++++ .../presto/src/Database/Sql/Presto/Parser/Token.hs | 9 +++++++++ test/Database/Sql/Presto/Parser/Test.hs | 11 +++++++++++ 3 files changed, 30 insertions(+) diff --git a/dialects/presto/src/Database/Sql/Presto/Parser.hs b/dialects/presto/src/Database/Sql/Presto/Parser.hs index 480da77..2db9eaa 100644 --- a/dialects/presto/src/Database/Sql/Presto/Parser.hs +++ b/dialects/presto/src/Database/Sql/Presto/Parser.hs @@ -63,6 +63,7 @@ statementParser = do , PrestoUnhandledStatement <$> showP , PrestoUnhandledStatement <$> callP , PrestoUnhandledStatement <$> describeP + , PrestoUnhandledStatement <$> setP ] case maybeStmt of Just stmt -> terminator >> return stmt @@ -1558,3 +1559,12 @@ 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 diff --git a/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs b/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs index 7420a66..49700e9 100644 --- a/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs +++ b/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs @@ -635,3 +635,12 @@ 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" diff --git a/test/Database/Sql/Presto/Parser/Test.hs b/test/Database/Sql/Presto/Parser/Test.hs index 1cd8b75..6174f4f 100644 --- a/test/Database/Sql/Presto/Parser/Test.hs +++ b/test/Database/Sql/Presto/Parser/Test.hs @@ -333,6 +333,17 @@ testParser = test , "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');" ] , "Exclude some broken examples" ~: map (TestCase . parsesUnsuccessfully) From adc68b8a63561c570e2a552f1c4fd9a9df4217d5 Mon Sep 17 00:00:00 2001 From: nooberfsh Date: Tue, 28 Dec 2021 14:26:59 +0800 Subject: [PATCH 04/10] support named window in presto --- .../presto/src/Database/Sql/Presto/Parser.hs | 193 +++++++++++++----- .../src/Database/Sql/Presto/Parser/Token.hs | 6 + .../presto/src/Database/Sql/Presto/Token.hs | 1 + .../presto/src/Database/Sql/Presto/Type.hs | 2 +- test/Database/Sql/Presto/Parser/Test.hs | 66 ++++++ test/Database/Sql/Util/Scope/Test.hs | 6 + 6 files changed, 222 insertions(+), 52 deletions(-) diff --git a/dialects/presto/src/Database/Sql/Presto/Parser.hs b/dialects/presto/src/Database/Sql/Presto/Parser.hs index 2db9eaa..0b8b099 100644 --- a/dialects/presto/src/Database/Sql/Presto/Parser.hs +++ b/dialects/presto/src/Database/Sql/Presto/Parser.hs @@ -231,13 +231,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{..} @@ -249,6 +250,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 $ @@ -905,6 +938,59 @@ arrayPrimaryExprP = do 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 @@ -1042,61 +1128,53 @@ functionCallPrimaryExprP = do 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 - ] + return (WindowExpr info partition order frame) - 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 - - , 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 @@ -1568,3 +1646,16 @@ setP = do 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) + ] + ] diff --git a/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs b/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs index 49700e9..e29dae1 100644 --- a/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs +++ b/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs @@ -644,3 +644,9 @@ 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 diff --git a/dialects/presto/src/Database/Sql/Presto/Token.hs b/dialects/presto/src/Database/Sql/Presto/Token.hs index 2dee594..fb871b3 100644 --- a/dialects/presto/src/Database/Sql/Presto/Token.hs +++ b/dialects/presto/src/Database/Sql/Presto/Token.hs @@ -122,4 +122,5 @@ 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) ] 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/test/Database/Sql/Presto/Parser/Test.hs b/test/Database/Sql/Presto/Parser/Test.hs index 6174f4f..9792edd 100644 --- a/test/Database/Sql/Presto/Parser/Test.hs +++ b/test/Database/Sql/Presto/Parser/Test.hs @@ -344,6 +344,72 @@ testParser = test , "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);" + ] ] , "Exclude some broken examples" ~: map (TestCase . parsesUnsuccessfully) 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);" + ] ] From 486b28e17c268523b3a5d2b60fcca107663f17e1 Mon Sep 17 00:00:00 2001 From: nooberfsh Date: Tue, 28 Dec 2021 18:42:16 +0800 Subject: [PATCH 05/10] ast support lambda expression --- .../src/Database/Sql/Vertica/Parser.hs | 2 ++ src/Database/Sql/Info.hs | 6 ++++ src/Database/Sql/Type/Names.hs | 28 +++++++++++++++++++ src/Database/Sql/Type/Query.hs | 14 ++++++++++ src/Database/Sql/Type/Scope.hs | 3 ++ src/Database/Sql/Util/Columns.hs | 2 ++ src/Database/Sql/Util/Joins.hs | 2 ++ src/Database/Sql/Util/Scope.hs | 20 +++++++++++-- src/Database/Sql/Util/Tables.hs | 2 ++ 9 files changed, 77 insertions(+), 2 deletions(-) diff --git a/dialects/vertica/src/Database/Sql/Vertica/Parser.hs b/dialects/vertica/src/Database/Sql/Vertica/Parser.hs index e875b23..216dee4 100644 --- a/dialects/vertica/src/Database/Sql/Vertica/Parser.hs +++ b/dialects/vertica/src/Database/Sql/Vertica/Parser.hs @@ -1022,6 +1022,8 @@ makeExprAlias (InSubqueryExpr info _ _) = makeDummyAlias info makeExprAlias (BetweenExpr info _ _ _) = makeDummyAlias info makeExprAlias (OverlapsExpr info _ _) = makeDummyAlias info makeExprAlias (AtTimeZoneExpr info _ _) = makeColumnAlias info "timezone" -- because reasons +makeExprAlias LambdaParamExpr {} = error "Lambda param expression should always be used inside a lambda body" +makeExprAlias LambdaExpr {} = error "Lambda expression should always be used inside a function" -- function expressions get the name of the function makeExprAlias (FunctionExpr info (QFunctionName _ _ name) _ _ _ _ _) = makeColumnAlias info name diff --git a/src/Database/Sql/Info.hs b/src/Database/Sql/Info.hs index 7f04030..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 @@ -387,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 b79867a..014d877 100644 --- a/src/Database/Sql/Type/Query.hs +++ b/src/Database/Sql/Type/Query.hs @@ -418,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) @@ -948,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 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 73e7df6..50089f5 100644 --- a/src/Database/Sql/Util/Columns.hs +++ b/src/Database/Sql/Util/Columns.hs @@ -464,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/Joins.hs b/src/Database/Sql/Util/Joins.hs index 39f45e6..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 diff --git a/src/Database/Sql/Util/Scope.hs b/src/Database/Sql/Util/Scope.hs index 0967a16..bb03d8a 100644 --- a/src/Database/Sql/Util/Scope.hs +++ b/src/Database/Sql/Util/Scope.hs @@ -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 , .. @@ -562,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 @@ -598,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 @@ -678,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 diff --git a/src/Database/Sql/Util/Tables.hs b/src/Database/Sql/Util/Tables.hs index e8c21d0..ec6629d 100644 --- a/src/Database/Sql/Util/Tables.hs +++ b/src/Database/Sql/Util/Tables.hs @@ -321,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 From 2c700dbaf16bc73b1585ddbb3cbeec5b0716334e Mon Sep 17 00:00:00 2001 From: nooberfsh Date: Wed, 29 Dec 2021 15:40:23 +0800 Subject: [PATCH 06/10] support lambda expression in presto --- .../presto/src/Database/Sql/Presto/Parser.hs | 31 ++++++++++++++++++- .../src/Database/Sql/Presto/Parser/Token.hs | 10 ++++++ .../presto/src/Database/Sql/Presto/Scanner.hs | 1 + .../src/Database/Sql/Vertica/Parser.hs | 5 ++- test/Database/Sql/Presto/Parser/Test.hs | 7 +++++ 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/dialects/presto/src/Database/Sql/Presto/Parser.hs b/dialects/presto/src/Database/Sql/Presto/Parser.hs index 0b8b099..a415820 100644 --- a/dialects/presto/src/Database/Sql/Presto/Parser.hs +++ b/dialects/presto/src/Database/Sql/Presto/Parser.hs @@ -438,6 +438,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 @@ -696,7 +701,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] @@ -720,6 +727,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 @@ -743,6 +753,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) @@ -856,6 +868,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 @@ -1125,6 +1138,22 @@ 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 diff --git a/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs b/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs index e29dae1..ef9b01b 100644 --- a/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs +++ b/dialects/presto/src/Database/Sql/Presto/Parser/Token.hs @@ -308,6 +308,16 @@ 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 + structFieldNameP :: Parser (Text, Range) structFieldNameP = P.tokenPrim showTok posFromTok testTok where 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/vertica/src/Database/Sql/Vertica/Parser.hs b/dialects/vertica/src/Database/Sql/Vertica/Parser.hs index 216dee4..9ad7394 100644 --- a/dialects/vertica/src/Database/Sql/Vertica/Parser.hs +++ b/dialects/vertica/src/Database/Sql/Vertica/Parser.hs @@ -1022,8 +1022,6 @@ makeExprAlias (InSubqueryExpr info _ _) = makeDummyAlias info makeExprAlias (BetweenExpr info _ _ _) = makeDummyAlias info makeExprAlias (OverlapsExpr info _ _) = makeDummyAlias info makeExprAlias (AtTimeZoneExpr info _ _) = makeColumnAlias info "timezone" -- because reasons -makeExprAlias LambdaParamExpr {} = error "Lambda param expression should always be used inside a lambda body" -makeExprAlias LambdaExpr {} = error "Lambda expression should always be used inside a function" -- function expressions get the name of the function makeExprAlias (FunctionExpr info (QFunctionName _ _ name) _ _ _ _ _) = makeColumnAlias info name @@ -1034,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/test/Database/Sql/Presto/Parser/Test.hs b/test/Database/Sql/Presto/Parser/Test.hs index 9792edd..b987bfd 100644 --- a/test/Database/Sql/Presto/Parser/Test.hs +++ b/test/Database/Sql/Presto/Parser/Test.hs @@ -410,6 +410,11 @@ testParser = test [ "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);" ] , "Exclude some broken examples" ~: map (TestCase . parsesUnsuccessfully) @@ -440,6 +445,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" From 309cb44233a78aa9cfec851c497ee4c4ff72638c Mon Sep 17 00:00:00 2001 From: nooberfsh Date: Wed, 29 Dec 2021 16:57:05 +0800 Subject: [PATCH 07/10] add more tests --- test/Database/Sql/Util/Columns/Test.hs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/Database/Sql/Util/Columns/Test.hs b/test/Database/Sql/Util/Columns/Test.hs index b23ec86..944ba63 100644 --- a/test/Database/Sql/Util/Columns/Test.hs +++ b/test/Database/Sql/Util/Columns/Test.hs @@ -115,6 +115,17 @@ testColumnAccesses = test , (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 From a668332b7db78aa90d82237a62ee7f1f4461f40e Mon Sep 17 00:00:00 2001 From: nooberfsh Date: Thu, 30 Dec 2021 14:42:21 +0800 Subject: [PATCH 08/10] support lambda in eval --- src/Database/Sql/Util/Eval.hs | 4 ++++ src/Database/Sql/Util/Eval/Concrete.hs | 4 ++++ src/Database/Sql/Util/Lineage/ColumnPlus.hs | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/src/Database/Sql/Util/Eval.hs b/src/Database/Sql/Util/Eval.hs index 11f47ad..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) @@ -387,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/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 From 951ab064e7b439f76935f7412a76315ed8781845 Mon Sep 17 00:00:00 2001 From: nooberfsh Date: Sun, 2 Jan 2022 11:26:39 +0800 Subject: [PATCH 09/10] support create table as statement in presto --- .../presto/src/Database/Sql/Presto/Parser.hs | 87 ++++++++++++++++++- .../src/Database/Sql/Presto/Parser/Token.hs | 25 ++++++ .../presto/src/Database/Sql/Presto/Token.hs | 2 + test/Database/Sql/Presto/Parser/Test.hs | 9 ++ 4 files changed, 122 insertions(+), 1 deletion(-) diff --git a/dialects/presto/src/Database/Sql/Presto/Parser.hs b/dialects/presto/src/Database/Sql/Presto/Parser.hs index a415820..6b4b709 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) @@ -127,6 +127,7 @@ statementP = choice , GrantStmt <$> grantP , RevokeStmt <$> revokeP , InsertStmt <$> insertP + , CreateTableStmt <$> createTableP ] queryP :: Parser (Query RawNames Range) @@ -1688,3 +1689,87 @@ constantP = choice , 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 ef9b01b..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 @@ -318,6 +321,16 @@ lambdaParamP = P.tokenPrim showTok posFromTok testTok _ -> 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 @@ -660,3 +673,15 @@ 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/Token.hs b/dialects/presto/src/Database/Sql/Presto/Token.hs index fb871b3..115b94f 100644 --- a/dialects/presto/src/Database/Sql/Presto/Token.hs +++ b/dialects/presto/src/Database/Sql/Presto/Token.hs @@ -123,4 +123,6 @@ wordInfo word = maybe (WordInfo True True True True) id $ M.lookup word $ M.from , ("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/test/Database/Sql/Presto/Parser/Test.hs b/test/Database/Sql/Presto/Parser/Test.hs index b987bfd..fe03b4e 100644 --- a/test/Database/Sql/Presto/Parser/Test.hs +++ b/test/Database/Sql/Presto/Parser/Test.hs @@ -415,6 +415,15 @@ testParser = test , "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) From 5a4d65f3304868939e84dc1d0763665c1568231a Mon Sep 17 00:00:00 2001 From: nooberfsh Date: Sun, 2 Jan 2022 15:52:52 +0800 Subject: [PATCH 10/10] support empty array expression in presto --- dialects/presto/src/Database/Sql/Presto/Parser.hs | 2 +- test/Database/Sql/Presto/Parser/Test.hs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dialects/presto/src/Database/Sql/Presto/Parser.hs b/dialects/presto/src/Database/Sql/Presto/Parser.hs index 6b4b709..84485fc 100644 --- a/dialects/presto/src/Database/Sql/Presto/Parser.hs +++ b/dialects/presto/src/Database/Sql/Presto/Parser.hs @@ -948,7 +948,7 @@ 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 diff --git a/test/Database/Sql/Presto/Parser/Test.hs b/test/Database/Sql/Presto/Parser/Test.hs index fe03b4e..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);"