diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2025-02-28 08:49:36 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2025-02-28 09:08:11 +0300 |
commit | 35d7c485a4db047e51377145f5fb8db47b3d1eec (patch) | |
tree | 975903b491953192f6a396df14247d8093cad23e /yql | |
parent | 2f77ea5d3edfb2dc3d07a2f1f5ecdfd017e1187b (diff) | |
download | ydb-35d7c485a4db047e51377145f5fb8db47b3d1eec.tar.gz |
Intermediate changes
commit_hash:6de10ee070edbce022e46ebcc3c5740ed2f84b10
Diffstat (limited to 'yql')
-rw-r--r-- | yql/essentials/sql/v1/sql_ut.cpp | 8155 | ||||
-rw-r--r-- | yql/essentials/sql/v1/sql_ut_antlr4.cpp | 8169 | ||||
-rw-r--r-- | yql/essentials/sql/v1/sql_ut_antlr4.h | 8 | ||||
-rw-r--r-- | yql/essentials/sql/v1/sql_ut_common.h | 8351 |
4 files changed, 8363 insertions, 16320 deletions
diff --git a/yql/essentials/sql/v1/sql_ut.cpp b/yql/essentials/sql/v1/sql_ut.cpp index aa1e17e53f..66c9a159fb 100644 --- a/yql/essentials/sql/v1/sql_ut.cpp +++ b/yql/essentials/sql/v1/sql_ut.cpp @@ -4,7 +4,6 @@ #include <yql/essentials/providers/common/provider/yql_provider_names.h> #include <yql/essentials/sql/sql.h> -#include <yql/essentials/sql/v1/sql.h> #include <yql/essentials/sql/v1/lexer/antlr3/lexer.h> #include <util/generic/map.h> @@ -30,8121 +29,10 @@ TParsedTokenList Tokenize(const TString& query) { return tokens; } -TString ToString(const TParsedTokenList& tokens) { - TStringBuilder reconstructedQuery; - for (const auto& token : tokens) { - if (token.Name == "WS" || token.Name == "EOF") { - continue; - } - if (!reconstructedQuery.empty()) { - reconstructedQuery << ' '; - } - reconstructedQuery << token.Content; - } - return reconstructedQuery; -} - -} - -Y_UNIT_TEST_SUITE(AnsiMode) { - Y_UNIT_TEST(PragmaAnsi) { - UNIT_ASSERT(SqlToYql("PRAGMA ANSI 2016;").IsOk()); - } -} - -Y_UNIT_TEST_SUITE(SqlParsingOnly) { - ///This function is used in BACKWARD COMPATIBILITY tests below that LIMIT the sets of token that CAN NOT be used - ///as identifiers in different contexts in a SQL request - ///\return list of tokens that failed this check - TVector<TString> ValidateTokens(const THashSet<TString>& forbidden, const std::function<TString (const TString& )>& makeRequest) { - THashMap<TString, bool> allTokens; - for (const auto& t: NSQLFormat::GetKeywords()) { - allTokens[t] = !forbidden.contains((t)); - } - for (const auto& f: forbidden) { - UNIT_ASSERT(allTokens.contains(f)); //check that forbidden list contains tokens only(argument check) - } - TVector<TString> failed; - for (const auto& [token, allowed]: allTokens) { - if (SqlToYql(makeRequest(token)).IsOk() != allowed) - failed.push_back(token); - } - return failed; - } - - Y_UNIT_TEST(TokensAsColumnName) { //id_expr - auto failed = ValidateTokens({ - "ALL", "ANY", "AS", "ASSUME", "ASYMMETRIC", "AUTOMAP", "BETWEEN", "BITCAST", - "CALLABLE", "CASE", "CAST", "CUBE", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", - "DICT", "DISTINCT", "ENUM", "ERASE", "EXCEPT", "EXISTS", "FLOW", "FROM", "FULL", - "HAVING", "HOP", "INTERSECT", "JSON_EXISTS", "JSON_QUERY", "JSON_VALUE", "LIMIT", "LIST", - "NOT", "OPTIONAL", "PROCESS", "REDUCE", "REPEATABLE", "RESOURCE", "RETURN", "RETURNING", "ROLLUP", - "SELECT", "SET", "STREAM", "STRUCT", "SYMMETRIC", "TAGGED", "TUPLE", "UNBOUNDED", - "UNION", "VARIANT", "WHEN", "WHERE", "WINDOW", "WITHOUT" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT " << token << " FROM Plato.Input"; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsWithoutColumnName) { //id_without - auto failed = ValidateTokens({ - "ALL", "AS", "ASSUME", "ASYMMETRIC", "AUTOMAP", "BETWEEN", "BITCAST", - "CALLABLE", "CASE", "CAST", "CUBE", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", - "DICT", "DISTINCT", "EMPTY_ACTION", "ENUM", "EXCEPT", "EXISTS", "FALSE", "FLOW", "FROM", "FULL", - "HAVING", "HOP", "INTERSECT", "JSON_EXISTS", "JSON_QUERY", "JSON_VALUE", "LIMIT", "LIST", - "NOT", "NULL", "OPTIONAL", "PROCESS", "REDUCE", "REPEATABLE", "RESOURCE", "RETURN", "RETURNING", "ROLLUP", - "SELECT", "SET", "STRUCT", "SYMMETRIC", "TAGGED", "TRUE", "TUPLE", "UNBOUNDED", - "UNION", "VARIANT", "WHEN", "WHERE", "WINDOW", "WITHOUT" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT * WITHOUT " << token << " FROM Plato.Input"; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsColumnNameInAddColumn) { //id_schema - auto failed = ValidateTokens({ - "ANY", "AUTOMAP", "CALLABLE", "COLUMN", "DICT", "ENUM", "ERASE", "FALSE", "FLOW", - "LIST", "OPTIONAL", "REPEATABLE", "RESOURCE", - "SET", "STREAM", "STRUCT", "TAGGED", "TRUE", "TUPLE", "VARIANT" - }, - [](const TString& token){ - TStringBuilder req; - req << "ALTER TABLE Plato.Input ADD COLUMN " << token << " Bool"; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsColumnAlias) { - auto failed = ValidateTokens({ - "AUTOMAP", "FALSE", - "REPEATABLE", "TRUE" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT Col as " << token << " FROM Plato.Input"; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsTableName) { //id_table_or_type - auto failed = ValidateTokens({ - "ANY", "AUTOMAP", "COLUMN", "ERASE", "FALSE", - "REPEATABLE", "STREAM", "TRUE" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT * FROM Plato." << token; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsTableAlias) { //id_table - auto failed = ValidateTokens({ - "AUTOMAP", "CALLABLE", "DICT", "ENUM","FALSE", "FLOW", - "LIST", "OPTIONAL", "REPEATABLE", "RESOURCE", - "SET", "STRUCT", "TAGGED", "TRUE", "TUPLE", "VARIANT" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT * FROM Plato.Input AS " << token; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsHints) { //id_hint - auto failed = ValidateTokens({ - "AUTOMAP", "CALLABLE", "COLUMNS", "DICT", "ENUM", "FALSE", "FLOW", - "LIST", "OPTIONAL", "REPEATABLE", "RESOURCE", - "SCHEMA", "SET", "STRUCT", "TAGGED", "TRUE", "TUPLE", "VARIANT" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT * FROM Plato.Input WITH " << token; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsWindow) { //id_window - auto failed = ValidateTokens({ - "AUTOMAP", "CALLABLE", "DICT", "ENUM", "FALSE", "FLOW", "GROUPS", "LIST", "OPTIONAL", - "RANGE", "REPEATABLE", "RESOURCE", "ROWS", "SET", "STRUCT", "TAGGED" ,"TRUE", "TUPLE", "VARIANT" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT * FROM Plato.Input WINDOW " << token << " AS ()"; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsIdExprIn) { //id_expr_in - auto failed = ValidateTokens({ - "ALL", "ANY", "AS", "ASSUME", "ASYMMETRIC", "AUTOMAP", "BETWEEN", "BITCAST", - "CALLABLE", "CASE", "CAST", "COMPACT", "CUBE", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", - "DICT", "DISTINCT", "ENUM", "ERASE", "EXCEPT", "EXISTS", "FLOW", "FROM", "FULL", - "HAVING", "HOP", "INTERSECT", "JSON_EXISTS", "JSON_QUERY", "JSON_VALUE", "LIMIT", "LIST", - "NOT", "OPTIONAL", "PROCESS", "REDUCE", "REPEATABLE", "RESOURCE", "RETURN", "RETURNING", "ROLLUP", - "SELECT", "SET", "STREAM", "STRUCT", "SYMMETRIC", "TAGGED", "TUPLE", "UNBOUNDED", - "UNION", "VARIANT", "WHEN", "WHERE", "WINDOW", "WITHOUT" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT * FROM Plato.Input WHERE q IN " << token; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TableHints) { - UNIT_ASSERT(SqlToYql("SELECT * FROM plato.Input WITH INFER_SCHEMA").IsOk()); - UNIT_ASSERT(SqlToYql("SELECT * FROM plato.Input WITH (INFER_SCHEMA)").IsOk()); - } - - Y_UNIT_TEST(InNoHints) { - TString query = "SELECT * FROM plato.Input WHERE key IN (1,2,3)"; - - VerifySqlInHints(query, { "'('('warnNoAnsi))" }, {}); - VerifySqlInHints(query, { "'()" }, false); - VerifySqlInHints(query, { "'('('ansi))" }, true); - } - - Y_UNIT_TEST(InHintCompact) { - // should parse COMPACT as hint - TString query = "SELECT * FROM plato.Input WHERE key IN COMPACT(1, 2, 3)"; - - VerifySqlInHints(query, { "'('isCompact)" }); - } - - Y_UNIT_TEST(InHintSubquery) { - // should parse tableSource as hint - TString query = "$subq = (SELECT key FROM plato.Input); SELECT * FROM plato.Input WHERE key IN $subq"; - - VerifySqlInHints(query, { "'('tableSource)" }); - } - - Y_UNIT_TEST(InHintCompactSubquery) { - TString query = "$subq = (SELECT key FROM plato.Input); SELECT * FROM plato.Input WHERE key IN COMPACT $subq"; - - VerifySqlInHints(query, { "'('isCompact)", "'('tableSource)" }); - } - - Y_UNIT_TEST(CompactKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("SELECT COMPACT FROM plato.Input WHERE COMPACT IN COMPACT(1, 2, 3)").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT * FROM COMPACT").IsOk()); - } - - Y_UNIT_TEST(FamilyKeywordNotReservedForNames) { - // FIXME: check if we can get old behaviour - //UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE FAMILY (FAMILY Uint32, PRIMARY KEY (FAMILY));").IsOk()); - //UNIT_ASSERT(SqlToYql("USE plato; SELECT FAMILY FROM FAMILY").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT FAMILY FROM Input").IsOk()); - } - - Y_UNIT_TEST(ResetKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE RESET (RESET Uint32, PRIMARY KEY (RESET));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT RESET FROM RESET").IsOk()); - } - - Y_UNIT_TEST(SyncKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE SYNC (SYNC Uint32, PRIMARY KEY (SYNC));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT SYNC FROM SYNC").IsOk()); - } - - Y_UNIT_TEST(AsyncKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE ASYNC (ASYNC Uint32, PRIMARY KEY (ASYNC));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT ASYNC FROM ASYNC").IsOk()); - } - - Y_UNIT_TEST(DisableKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE DISABLE (DISABLE Uint32, PRIMARY KEY (DISABLE));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT DISABLE FROM DISABLE").IsOk()); - } - - Y_UNIT_TEST(ChangefeedKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE CHANGEFEED (CHANGEFEED Uint32, PRIMARY KEY (CHANGEFEED));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT CHANGEFEED FROM CHANGEFEED").IsOk()); - } - - Y_UNIT_TEST(ReplicationKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE REPLICATION (REPLICATION Uint32, PRIMARY KEY (REPLICATION));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT REPLICATION FROM REPLICATION").IsOk()); - } - - Y_UNIT_TEST(TransferKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE TRANSFER (TRANSFER Uint32, PRIMARY KEY (TRANSFER));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT TRANSFER FROM TRANSFER").IsOk()); - } - - Y_UNIT_TEST(SecondsKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE SECONDS (SECONDS Uint32, PRIMARY KEY (SECONDS));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT SECONDS FROM SECONDS").IsOk()); - } - - Y_UNIT_TEST(MillisecondsKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE MILLISECONDS (MILLISECONDS Uint32, PRIMARY KEY (MILLISECONDS));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT MILLISECONDS FROM MILLISECONDS").IsOk()); - } - - Y_UNIT_TEST(MicrosecondsKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE MICROSECONDS (MICROSECONDS Uint32, PRIMARY KEY (MICROSECONDS));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT MICROSECONDS FROM MICROSECONDS").IsOk()); - } - - Y_UNIT_TEST(NanosecondsKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE NANOSECONDS (NANOSECONDS Uint32, PRIMARY KEY (NANOSECONDS));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT NANOSECONDS FROM NANOSECONDS").IsOk()); - } - - Y_UNIT_TEST(Jubilee) { - NYql::TAstParseResult res = SqlToYql("USE plato; INSERT INTO Arcadia (r2000000) VALUES (\"2M GET!!!\");"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(QualifiedAsteriskBefore) { - NYql::TAstParseResult res = SqlToYql( - "PRAGMA DisableSimpleColumns;" - "select interested_table.*, LENGTH(value) AS megahelpful_len from plato.Input as interested_table;" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - static bool seenStar = false; - if (word == "FlattenMembers") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("interested_table.")); - } else if (word == "SqlProjectItem") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("megahelpful_len"))); - UNIT_ASSERT_VALUES_EQUAL(seenStar, true); - } else if (word == "SqlProjectStarItem") { - seenStar = true; - } - }; - TWordCountHive elementStat = {{TString("FlattenMembers"), 0}, {TString("SqlProjectItem"), 0}, {TString("SqlProjectStarItem"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["FlattenMembers"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectStarItem"]); - } - - Y_UNIT_TEST(QualifiedAsteriskAfter) { - NYql::TAstParseResult res = SqlToYql( - "PRAGMA DisableSimpleColumns;" - "select LENGTH(value) AS megahelpful_len, interested_table.* from plato.Input as interested_table;" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - static bool seenStar = false; - if (word == "FlattenMembers") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("interested_table.")); - } else if (word == "SqlProjectItem") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("megahelpful_len"))); - UNIT_ASSERT_VALUES_EQUAL(seenStar, false); - } else if (word == "SqlProjectStarItem") { - seenStar = true; - } - }; - TWordCountHive elementStat = {{TString("FlattenMembers"), 0}, {TString("SqlProjectItem"), 0}, {TString("SqlProjectStarItem"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["FlattenMembers"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectStarItem"]); - } - - Y_UNIT_TEST(QualifiedMembers) { - NYql::TAstParseResult res = SqlToYql("select interested_table.key, interested_table.value from plato.Input as interested_table;"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - const bool fieldKey = TString::npos != line.find(Quote("key")); - const bool fieldValue = TString::npos != line.find(Quote("value")); - const bool refOnTable = TString::npos != line.find("interested_table."); - if (word == "SqlProjectItem") { - UNIT_ASSERT(fieldKey || fieldValue); - UNIT_ASSERT(!refOnTable); - } else if (word == "Write!") { - UNIT_ASSERT(fieldKey && fieldValue && !refOnTable); - } - }; - TWordCountHive elementStat = {{TString("SqlProjectStarItem"), 0}, {TString("SqlProjectItem"), 0}, {TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(0, elementStat["SqlProjectStarItem"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(ExplainQueryPlan) { - UNIT_ASSERT(SqlToYql("EXPLAIN SELECT 1;").IsOk()); - UNIT_ASSERT(SqlToYql("EXPLAIN QUERY PLAN SELECT 1;").IsOk()); - } - - Y_UNIT_TEST(JoinParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - "PRAGMA DisableSimpleColumns;" - " SELECT table_bb.*, table_aa.key as megakey" - " FROM plato.Input AS table_aa" - " JOIN plato.Input AS table_bb" - " ON table_aa.value == table_bb.value;" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "SelectMembers") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("table_aa.")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table_bb.")); - } else if (word == "SqlProjectItem") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("megakey"))); - } else if (word == "SqlColumn") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("table_aa"))); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("key"))); - } - }; - TWordCountHive elementStat = {{TString("SqlProjectItem"), 0}, {TString("SqlProjectStarItem"), 0}, {TString("SelectMembers"), 0}, {TString("SqlColumn"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectStarItem"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SelectMembers"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlColumn"]); - } - - Y_UNIT_TEST(Join3Table) { - NYql::TAstParseResult res = SqlToYql( - " PRAGMA DisableSimpleColumns;" - " SELECT table_bb.*, table_aa.key as gigakey, table_cc.* " - " FROM plato.Input AS table_aa" - " JOIN plato.Input AS table_bb ON table_aa.key == table_bb.key" - " JOIN plato.Input AS table_cc ON table_aa.subkey == table_cc.subkey;" - ); - Err2Str(res); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "SelectMembers") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("table_aa.")); - UNIT_ASSERT(line.find("table_bb.") != TString::npos || line.find("table_cc.") != TString::npos); - } else if (word == "SqlProjectItem") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("gigakey"))); - } else if (word == "SqlColumn") { - const auto posTableAA = line.find(Quote("table_aa")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, posTableAA); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("key"))); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("table_aa", posTableAA + 3)); - } - }; - TWordCountHive elementStat = {{TString("SqlProjectItem"), 0}, {TString("SqlProjectStarItem"), 0}, {TString("SelectMembers"), 0}, {TString("SqlColumn"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlProjectStarItem"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SelectMembers"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlColumn"]); - } - - Y_UNIT_TEST(DisabledJoinCartesianProduct) { - NYql::TAstParseResult res = SqlToYql("pragma DisableAnsiImplicitCrossJoin; use plato; select * from A,B,C"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:67: Error: Cartesian product of tables is disabled. Please use explicit CROSS JOIN or enable it via PRAGMA AnsiImplicitCrossJoin\n"); - } - - Y_UNIT_TEST(JoinCartesianProduct) { - NYql::TAstParseResult res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; select * from A,B,C"); - UNIT_ASSERT(res.Root); - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "EquiJoin") { - auto pos = line.find("Cross"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, pos); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Cross", pos + 1)); - } - }; - TWordCountHive elementStat = {{TString("EquiJoin"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["EquiJoin"]); - } - - Y_UNIT_TEST(JoinWithoutConcreteColumns) { - NYql::TAstParseResult res = SqlToYql( - " use plato;" - " SELECT a.v, b.value" - " FROM `Input1` VIEW `ksv` AS a" - " JOIN `Input2` AS b" - " ON a.k == b.key;" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "SqlProjectItem") { - UNIT_ASSERT(line.find(Quote("a.v")) != TString::npos || line.find(Quote("b.value")) != TString::npos); - } else if (word == "SqlColumn") { - const auto posTableA = line.find(Quote("a")); - const auto posTableB = line.find(Quote("b")); - if (posTableA != TString::npos) { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("v"))); - } else { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, posTableB); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("value"))); - } - } - }; - TWordCountHive elementStat = {{TString("SqlProjectStarItem"), 0}, {TString("SqlProjectItem"), 0}, {TString("SqlColumn"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(0, elementStat["SqlProjectStarItem"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlColumn"]); - } - - Y_UNIT_TEST(JoinWithSameValues) { - NYql::TAstParseResult res = SqlToYql("SELECT a.value, b.value FROM plato.Input AS a JOIN plato.Input as b ON a.key == b.key;"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "SqlProjectItem") { - const bool isValueFromA = TString::npos != line.find(Quote("a.value")); - const bool isValueFromB = TString::npos != line.find(Quote("b.value")); - UNIT_ASSERT(isValueFromA || isValueFromB); - } if (word == "Write!") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("a.a.")); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("b.b.")); - } - }; - TWordCountHive elementStat = {{TString("SqlProjectStarItem"), 0}, {TString("SqlProjectItem"), 0}, {"Write!", 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(0, elementStat["SqlProjectStarItem"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(SameColumnsForDifferentTables) { - NYql::TAstParseResult res = SqlToYql("SELECT a.key, b.key FROM plato.Input as a JOIN plato.Input as b on a.key==b.key;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SameColumnsForDifferentTablesFullJoin) { - NYql::TAstParseResult res = SqlToYql("SELECT a.key, b.key, a.value, b.value FROM plato.Input AS a FULL JOIN plato.Input AS b USING(key);"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(JoinStreamLookupStrategyHint) { - { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ StreamLookup() */ plato.Input AS b USING(key);"); - UNIT_ASSERT(res.Root); - } - //case insensitive - { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ streamlookup() */ plato.Input AS b USING(key);"); - UNIT_ASSERT(res.Root); - } - } - - Y_UNIT_TEST(JoinConflictingStrategyHint) { - { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ StreamLookup() */ /*+ Merge() */ plato.Input AS b USING(key);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:91: Error: Conflicting join strategy hints\n"); - } - } - - Y_UNIT_TEST(JoinDuplicatingStrategyHint) { - { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ StreamLookup() */ /*+ StreamLookup() */ plato.Input AS b USING(key);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:98: Error: Duplicate join strategy hint\n"); - } - } - - Y_UNIT_TEST(WarnCrossJoinStrategyHint) { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a CROSS JOIN /*+ merge() */ plato.Input AS b;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:32: Warning: Non-default join strategy will not be used for CROSS JOIN, code: 4534\n"); - } - - Y_UNIT_TEST(WarnCartesianProductStrategyHint) { - NYql::TAstParseResult res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; SELECT * FROM A, /*+ merge() */ B;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:74: Warning: Non-default join strategy will not be used for CROSS JOIN, code: 4534\n"); - } - - Y_UNIT_TEST(WarnUnknownJoinStrategyHint) { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ xmerge() */ plato.Input AS b USING (key);"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:41: Warning: Unsupported join hint: xmerge, code: 4534\n"); - } - - Y_UNIT_TEST(ReverseLabels) { - NYql::TAstParseResult res = SqlToYql("select in.key as subkey, subkey as key from plato.Input as in;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(AutogenerationAliasWithoutCollisionConflict1) { - NYql::TAstParseResult res = SqlToYql("select LENGTH(Value), key as column1 from plato.Input;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(AutogenerationAliasWithoutCollision2Conflict2) { - NYql::TAstParseResult res = SqlToYql("select key as column0, LENGTH(Value) from plato.Input;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(InputAliasForQualifiedAsterisk) { - NYql::TAstParseResult res = SqlToYql("use plato; select zyuzya.*, key from plato.Input as zyuzya;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectSupportsResultColumnsWithTrailingComma) { - NYql::TAstParseResult res = SqlToYql("select a, b, c, from plato.Input;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectOrderByLabeledColumn) { - NYql::TAstParseResult res = SqlToYql("pragma DisableOrderedColumns; select key as goal from plato.Input order by goal"); - UNIT_ASSERT(res.Root); - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "DataSource") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("plato")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Input")); - - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("goal")); - } else if (word == "Sort") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("goal")); - - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("key")); - } - }; - TWordCountHive elementStat = {{TString("DataSource"), 0}, {TString("Sort"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["DataSource"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Sort"]); - } - - Y_UNIT_TEST(SelectOrderBySimpleExpr) { - NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by a + a"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectAssumeOrderByTableRowAccess) { - NYql::TAstParseResult res = SqlToYql("$key = 'foo';select * from plato.Input assume order by TableRow().$key"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectOrderByDuplicateLabels) { - NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by a, a"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectOrderByExpression) { - NYql::TAstParseResult res = SqlToYql("select * from plato.Input as i order by cast(key as uint32) + cast(subkey as uint32)"); - UNIT_ASSERT(res.Root); - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Sort") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"+MayWarn\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("key")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("subkey")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Bool 'true)")); - - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("i.key")); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("i.subkey")); - } - }; - TWordCountHive elementStat = {{TString("Sort"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Sort"]); - } - - Y_UNIT_TEST(SelectOrderByExpressionDesc) { - NYql::TAstParseResult res = SqlToYql("pragma disablesimplecolumns; select i.*, key, subkey from plato.Input as i order by cast(i.key as uint32) - cast(i.subkey as uint32) desc"); - UNIT_ASSERT(res.Root); - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Sort") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"-MayWarn\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"key\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"subkey\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Bool 'false)")); - } else if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'columns")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"key\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"subkey\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("prefix")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"i.\"")); - } - }; - TWordCountHive elementStat = {{TString("Sort"), 0}, {TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Sort"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(SelectOrderByExpressionAsc) { - NYql::TAstParseResult res = SqlToYql("select i.key, i.subkey from plato.Input as i order by cast(key as uint32) % cast(i.subkey as uint32) asc"); - UNIT_ASSERT(res.Root); - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Sort") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"%MayWarn\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"key\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"subkey\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Bool 'true)")); - } else if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'columns")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"key\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"subkey\"")); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("i.")); - } - }; - TWordCountHive elementStat = {{TString("Sort"), 0}, {TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Sort"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(ReferenceToKeyInSubselect) { - NYql::TAstParseResult res = SqlToYql("select b.key from (select a.key from plato.Input as a) as b;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(OrderByCastValue) { - NYql::TAstParseResult res = SqlToYql("select i.key, i.subkey from plato.Input as i order by cast(key as uint32) desc;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(GroupByCastValue) { - NYql::TAstParseResult res = SqlToYql("select count(1) from plato.Input as i group by cast(key as uint8);"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(KeywordInSelectColumns) { - NYql::TAstParseResult res = SqlToYql("select in, s.check from (select 1 as in, \"test\" as check) as s;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectAllGroupBy) { - NYql::TAstParseResult res = SqlToYql("select * from plato.Input group by subkey;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(CreateObjectWithFeatures) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT secretId (TYPE SECRET) WITH (Key1=Value1, K2=V2);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('\"K2\" '\"V2\") '('\"Key1\" '\"Value1\")")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(CreateObjectIfNotExists) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT IF NOT EXISTS secretId (TYPE SECRET) WITH (Key1=Value1, K2=V2);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObjectIfNotExists")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(CreateObjectWithFeaturesStrings) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT secretId (TYPE SECRET) WITH (Key1=\"Value1\", K2='V2', K3=V3, K4='', K5=`aaa`, K6='a\\'aa');"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('\"K2\" '\"V2\") '('\"K3\" '\"V3\") '('\"K4\" '\"\") '('\"K5\" '\"aaa\") '('\"K6\" '\"a'aa\") '('\"Key1\" '\"Value1\")")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}, {TString("SECRET"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - } - - Y_UNIT_TEST(UpsertObjectWithFeatures) { - NYql::TAstParseResult res = SqlToYql("USE plato; UPSERT OBJECT secretId (TYPE SECRET) WITH (Key1=Value1, K2=V2);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('\"K2\" '\"V2\") '('\"Key1\" '\"Value1\")")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("upsertObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(CreateObjectWithFeaturesAndFlags) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT secretId (TYPE SECRET) WITH (Key1=Value1, K2=V2, RECURSE);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('\"Key1\" '\"Value1\") '('\"RECURSE\")")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(Select1Type) { - NYql::TAstParseResult res = SqlToYql("SELECT 1 type;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectTableType) { - NYql::TAstParseResult res = SqlToYql("USE plato; SELECT * from T type;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(CreateObjectNoFeatures) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT secretId (TYPE SECRET);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(AlterObjectWithFeatures) { - NYql::TAstParseResult res = SqlToYql( - "USE plato;\n" - "declare $path as String;\n" - "ALTER OBJECT secretId (TYPE SECRET) SET (Key1=$path, K2=V2);" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"Key1\" (EvaluateAtom \"$path\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"K2\" '\"V2\"")); - - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alterObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(AlterObjectNoFeatures) { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER OBJECT secretId (TYPE SECRET);"); - UNIT_ASSERT(!res.Root); - } - - Y_UNIT_TEST(DropObjectNoFeatures) { - NYql::TAstParseResult res = SqlToYql("USE plato; DROP OBJECT secretId (TYPE SECRET);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(DropObjectWithFeatures) { - NYql::TAstParseResult res = SqlToYql("USE plato; DROP OBJECT secretId (TYPE SECRET) WITH (A, B, C);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(DropObjectWithOneOption) { - NYql::TAstParseResult res = SqlToYql("USE plato; DROP OBJECT secretId (TYPE SECRET) WITH OVERRIDE;"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"OVERRIDE\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(DropObjectIfExists) { - NYql::TAstParseResult res = SqlToYql("USE plato; DROP OBJECT IF EXISTS secretId (TYPE SECRET);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObjectIfExists")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(PrimaryKeyParseCorrect) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE tableName (Key Uint32, Subkey Int64, Value String, PRIMARY KEY (Key, Subkey));"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"Key\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"Subkey\"")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}, {TString("primarykey"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["primarykey"]); - } - - Y_UNIT_TEST(AlterDatabaseAst) { - NYql::TAstParseResult request = SqlToYql("USE plato; ALTER DATABASE `/Root/test` OWNER TO user1;"); - UNIT_ASSERT(request.IsOk()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - Y_UNUSED(word); - - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find( - R"(let world (Write! world sink (Key '('databasePath (String '"/Root/test"))) (Void) '('('mode 'alterDatabase) '('owner '"user1"))))" - )); - }; - - TWordCountHive elementStat({TString("\'mode \'alterDatabase")}); - VerifyProgram(request, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["\'mode \'alterDatabase"]); - } - - Y_UNIT_TEST(CreateTableNonNullableYqlTypeAstCorrect) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32 not null);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (DataType 'Int32) '('columnConstrains '('('not_null))) '())))))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTableNullableYqlTypeAstCorrect) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTableNonNullablePgTypeAstCorrect) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a pg_int4 not null);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (PgType '_int4) '('columnConstrains '('('not_null))) '())))))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTableNullablePgTypeAstCorrect) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a pg_int4);"); - UNIT_ASSERT(res.Root); - - res.Root->PrettyPrintTo(Cout, PRETTY_FLAGS); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (PgType '_int4)) '('columnConstrains '()) '()))))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTableNullPkColumnsAreAllowed) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32, primary key(a));"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))) '('primarykey '('"a")))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTableNotNullPkColumnsAreIdempotentAstCorrect) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32 not null, primary key(a));"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (DataType 'Int32) '('columnConstrains '('('not_null))) '()))) '('primarykey '('"a"))))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTableWithIfNotExists) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE IF NOT EXISTS t (a int32, primary key(a));"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create_if_not_exists) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))) '('primarykey '('"a")))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTempTable) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TEMP TABLE t (a int32, primary key(a));"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))) '('primarykey '('"a")) '('temporary))))__"), line); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTemporaryTable) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TEMPORARY TABLE t (a int32, primary key(a));"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))) '('primarykey '('"a")) '('temporary))))__"), line); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTableWithoutTypes) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a, primary key(a));"); - UNIT_ASSERT(!res.Root); - } - - Y_UNIT_TEST(CreateTableAsSelectWithTypes) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32, primary key(a)) AS SELECT * FROM ts;"); - UNIT_ASSERT(!res.Root); - } - - Y_UNIT_TEST(CreateTableAsSelect) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a, b, primary key(a)) AS SELECT * FROM ts;"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((let world (Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a") '('"b"))) '('primarykey '('"a"))))))__")); - } - if (word == "Read!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Read! world (DataSource '"yt" '"plato") (MrTableConcat (Key '('table (String '"ts")))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}, {TString("Read!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Read!"]); - } - - Y_UNIT_TEST(CreateTableAsSelectOnlyPrimary) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (primary key(a)) AS SELECT * FROM ts;"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((let world (Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '()) '('primarykey '('"a"))))))__")); - } - if (word == "Read!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Read! world (DataSource '"yt" '"plato") (MrTableConcat (Key '('table (String '"ts")))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}, {TString("Read!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Read!"]); - } - - Y_UNIT_TEST(CreateTableAsValuesFail) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a, primary key(a)) AS VALUES (1), (2);"); - UNIT_ASSERT(!res.Root); - } - - Y_UNIT_TEST(CreateTableDuplicatedPkColumnsFail) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32 not null, primary key(a, a));"); - UNIT_ASSERT(!res.Root); - } - - Y_UNIT_TEST(DeleteFromTableByKey) { - NYql::TAstParseResult res = SqlToYql("delete from plato.Input where key = 200;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DeleteFromTable) { - NYql::TAstParseResult res = SqlToYql("delete from plato.Input;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DeleteFromTableBatch) { - NYql::TAstParseResult res = SqlToYql("batch delete from plato.Input;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete)")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('is_batch 'true)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DeleteFromTableOnValues) { - NYql::TAstParseResult res = SqlToYql("delete from plato.Input on (key) values (1);", - 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete_on)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DeleteFromTableOnSelect) { - NYql::TAstParseResult res = SqlToYql( - "delete from plato.Input on select key from plato.Input where value > 0;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete_on)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DeleteFromTableOnBatch) { - NYql::TAstParseResult res = SqlToYql("batch delete from plato.Input on (key) values (1);", - 10, "kikimr"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:6: Error: BATCH DELETE is unsupported with ON\n"); - } - - Y_UNIT_TEST(UpdateByValues) { - NYql::TAstParseResult res = SqlToYql("update plato.Input set key = 777, value = 'cool' where key = 200;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update)")); - } else if (word == "AsStruct") { - const bool isKey = line.find("key") != TString::npos; - const bool isValue = line.find("value") != TString::npos; - UNIT_ASSERT(isKey || isValue); - if (isKey) { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("777"))); - } else if (isValue) { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("cool"))); - } - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}, {TString("AsStruct"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["AsStruct"]); - } - - Y_UNIT_TEST(UpdateByValuesBatch) { - NYql::TAstParseResult res = SqlToYql("batch update plato.Input set key = 777, value = 'cool' where key = 200;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update)")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('is_batch 'true)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(UpdateByMultiValues) { - NYql::TAstParseResult res = SqlToYql("update plato.Input set (key, value, subkey) = ('2','ddd',':') where key = 200;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update)")); - } else if (word == "AsStruct") { - const bool isKey = line.find("key") != TString::npos; - const bool isSubkey = line.find("subkey") != TString::npos; - const bool isValue = line.find("value") != TString::npos; - UNIT_ASSERT(isKey || isSubkey || isValue); - if (isKey && !isSubkey) { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("2"))); - } else if (isSubkey) { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote(":"))); - } else if (isValue) { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("ddd"))); - } - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}, {TString("AsStruct"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["AsStruct"]); - } - - Y_UNIT_TEST(UpdateBySelect) { - NYql::TAstParseResult res = SqlToYql("update plato.Input set (key, value, subkey) = (select key, value, subkey from plato.Input where key = 911) where key = 200;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - int lineIndex = 0; - int writeLineIndex = -1; - bool found = false; - - TVerifyLineFunc verifyLine = [&lineIndex, &writeLineIndex, &found](const TString& word, const TString& line) { - if (word == "Write") { - writeLineIndex = lineIndex; - found = line.find("('mode 'update)") != TString::npos; - } else if (word == "mode") { - found |= lineIndex == writeLineIndex + 1 && line.find("('mode 'update)") != TString::npos; - UNIT_ASSERT(found); - } - - ++lineIndex; - }; - - TWordCountHive elementStat = {{TString("Write"), 0}, {TString("mode"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(UpdateSelfModifyAll) { - NYql::TAstParseResult res = SqlToYql("update plato.Input set subkey = subkey + 's';", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update)")); - } else if (word == "AsStruct") { - const bool isSubkey = line.find("subkey") != TString::npos; - UNIT_ASSERT(isSubkey); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("subkey"))); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("s"))); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}, {TString("AsStruct"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["AsStruct"]); - } - - Y_UNIT_TEST(UpdateOnValues) { - NYql::TAstParseResult res = SqlToYql("update plato.Input on (key, value) values (5, 'cool')", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update_on)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(UpdateOnSelect) { - NYql::TAstParseResult res = SqlToYql( - "update plato.Input on select key, value + 1 as value from plato.Input", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update_on)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(UpdateOnBatch) { - NYql::TAstParseResult res = SqlToYql("batch update plato.Input on (key, value) values (5, 'cool')", 10, "kikimr"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:6: Error: BATCH UPDATE is unsupported with ON\n"); - } - - Y_UNIT_TEST(UnionAllTest) { - NYql::TAstParseResult res = SqlToYql("PRAGMA DisableEmitUnionMerge; SELECT key FROM plato.Input UNION ALL select subkey FROM plato.Input;"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("UnionAll"), 0}}; - VerifyProgram(res, elementStat, {}); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["UnionAll"]); - } - - Y_UNIT_TEST(UnionAllMergeTest) { - NYql::TAstParseResult res = SqlToYql("PRAGMA EmitUnionMerge; SELECT key FROM plato.Input UNION ALL select subkey FROM plato.Input;"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("UnionMerge"), 0}}; - VerifyProgram(res, elementStat, {}); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["UnionMerge"]); - } - - Y_UNIT_TEST(UnionTest) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input UNION select subkey FROM plato.Input;"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("Union"), 0}}; - VerifyProgram(res, elementStat, {}); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Union"]); - } - - Y_UNIT_TEST(UnionAggregationTest) { - NYql::TAstParseResult res = SqlToYql(R"( - PRAGMA DisableEmitUnionMerge; - SELECT 1 - UNION ALL - SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 - UNION - SELECT 1 UNION SELECT 1 UNION SELECT 1 UNION SELECT 1 - UNION ALL - SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1; - )"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("Union"), 0}, {TString("UnionAll"), 0}}; - VerifyProgram(res, elementStat, {}); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["UnionAll"]); - UNIT_ASSERT_VALUES_EQUAL(3, elementStat["Union"]); - } - - Y_UNIT_TEST(UnionMergeAggregationTest) { - NYql::TAstParseResult res = SqlToYql(R"( - PRAGMA EmitUnionMerge; - SELECT 1 - UNION ALL - SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 - UNION - SELECT 1 UNION SELECT 1 UNION SELECT 1 UNION SELECT 1 - UNION ALL - SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1; - )"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("Union"), 0}, {TString("UnionMerge"), 0}}; - VerifyProgram(res, elementStat, {}); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["UnionMerge"]); - UNIT_ASSERT_VALUES_EQUAL(3, elementStat["Union"]); - } - - Y_UNIT_TEST(DeclareDecimalParameter) { - NYql::TAstParseResult res = SqlToYql("declare $value as Decimal(22,9); select $value as cnt;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SimpleGroupBy) { - NYql::TAstParseResult res = SqlToYql("select count(1),z from plato.Input group by key as z order by z;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(EmptyColumnName0) { - /// Now it's parsed well and error occur on validate step like "4:31:Empty struct member name is not allowed" in "4:31:Function: AddMember" - NYql::TAstParseResult res = SqlToYql("insert into plato.Output (``, list1) values (0, AsList(0, 1, 2));"); - /// Verify that parsed well without crash - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(KikimrRollback) { - NYql::TAstParseResult res = SqlToYql("use plato; select * from Input; rollback;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("rollback"), 0}}; - VerifyProgram(res, elementStat); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["rollback"]); - } - - Y_UNIT_TEST(PragmaFile) { - NYql::TAstParseResult res = SqlToYql(R"(pragma file("HW", "sbr:181041334");)"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString(R"((let world (Configure! world (DataSource '"config") '"AddFileByUrl" '"HW" '"sbr:181041334")))"), 0}}; - VerifyProgram(res, elementStat); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat.cbegin()->second); - } - - Y_UNIT_TEST(DoNotCrashOnNamedInFilter) { - NYql::TAstParseResult res = SqlToYql("USE plato; $all = ($table_name) -> { return true; }; SELECT * FROM FILTER(Input, $all)"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(PragmasFileAndUdfOrder) { - NYql::TAstParseResult res = SqlToYql(R"( - PRAGMA file("libvideoplayers_udf.so", "https://proxy.sandbox.yandex-team.ru/235185290"); - PRAGMA udf("libvideoplayers_udf.so"); - )"); - UNIT_ASSERT(res.Root); - - const auto programm = GetPrettyPrint(res); - const auto file = programm.find("AddFileByUrl"); - const auto udfs = programm.find("ImportUdfs"); - UNIT_ASSERT(file < udfs); - } - - Y_UNIT_TEST(ProcessUserType) { - NYql::TAstParseResult res = SqlToYql("process plato.Input using Kikimr::PushData(TableRows());", 1, TString(NYql::KikimrProviderName)); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Kikimr.PushData") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("TupleType")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("TypeOf")); - } - }; - - TWordCountHive elementStat = {{TString("Kikimr.PushData"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Kikimr.PushData"]); - } - - Y_UNIT_TEST(ProcessUserTypeAuth) { - NYql::TAstParseResult res = SqlToYql("process plato.Input using YDB::PushData(TableRows(), AsTuple('oauth', SecureParam('api:oauth')));", 1, TString(NYql::KikimrProviderName)); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "YDB.PushData") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("TupleType")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("TypeOf")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("api:oauth")); - } - }; - - TWordCountHive elementStat = {{TString("YDB.PushData"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["YDB.PushData"]); - } - - Y_UNIT_TEST(SelectStreamRtmr) { - NYql::TAstParseResult res = SqlToYql( - "USE plato; INSERT INTO Output SELECT STREAM key FROM Input;", - 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(res.Root); - - res = SqlToYql( - "USE plato; INSERT INTO Output SELECT key FROM Input;", - 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectStreamRtmrJoinWithYt) { - NYql::TAstParseResult res = SqlToYql( - "USE plato; INSERT INTO Output SELECT STREAM key FROM Input LEFT JOIN hahn.ttt as t ON Input.key = t.Name;", - 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectStreamNonRtmr) { - NYql::TAstParseResult res = SqlToYql( - "USE plato; INSERT INTO Output SELECT STREAM key FROM Input;", - 10); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:31: Error: SELECT STREAM is unsupported for non-streaming sources\n"); - } - - Y_UNIT_TEST(GroupByHopRtmr) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; INSERT INTO Output SELECT key, SUM(value) AS value FROM Input - GROUP BY key, HOP(subkey, "PT10S", "PT30S", "PT20S"); - )", 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(GroupByHopRtmrSubquery) { - // 'use plato' intentially avoided - NYql::TAstParseResult res = SqlToYql(R"( - SELECT COUNT(*) AS value FROM (SELECT * FROM plato.Input) - GROUP BY HOP(Data, "PT10S", "PT30S", "PT20S") - )", 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(GroupByHopRtmrSubqueryBinding) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - $q = SELECT * FROM Input; - INSERT INTO Output SELECT STREAM * FROM ( - SELECT COUNT(*) AS value FROM $q - GROUP BY HOP(Data, "PT10S", "PT30S", "PT20S") - ); - )", 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(GroupByNoHopRtmr) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; INSERT INTO Output SELECT STREAM key, SUM(value) AS value FROM Input - GROUP BY key; - )", 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:22: Error: Streaming group by query must have a hopping window specification.\n"); - } - - Y_UNIT_TEST(KikimrInserts) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - INSERT INTO Output SELECT key, value FROM Input; - INSERT OR ABORT INTO Output SELECT key, value FROM Input; - INSERT OR IGNORE INTO Output SELECT key, value FROM Input; - INSERT OR REVERT INTO Output SELECT key, value FROM Input; - )", 10, TString(NYql::KikimrProviderName)); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(WarnMissingIsBeforeNotNull) { - NYql::TAstParseResult res = SqlToYql("select 1 NOT NULL"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Warning: Missing IS keyword before NOT NULL, code: 4507\n"); - } - - Y_UNIT_TEST(Subqueries) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - $sq1 = (SELECT * FROM plato.Input); - - $sq2 = SELECT * FROM plato.Input; - - $squ1 = ( - SELECT * FROM plato.Input - UNION ALL - SELECT * FROM plato.Input - ); - - $squ2 = - SELECT * FROM plato.Input - UNION ALL - SELECT * FROM plato.Input; - - $squ3 = ( - (SELECT * FROM plato.Input) - UNION ALL - (SELECT * FROM plato.Input) - ); - - SELECT * FROM $sq1; - SELECT * FROM $sq2; - SELECT * FROM $squ1; - SELECT * FROM $squ2; - SELECT * FROM $squ3; - )"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SubqueriesJoin) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - - $left = SELECT * FROM plato.Input1 WHERE value != "BadValue"; - $right = SELECT * FROM plato.Input2; - - SELECT * FROM $left AS l - JOIN $right AS r - ON l.key == r.key; - )"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(AnyInBackticksAsTableName) { - NYql::TAstParseResult res = SqlToYql("use plato; select * from `any`;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(AnyJoinForTableAndSubQuery) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - - $r = SELECT * FROM plato.Input2; - - SELECT * FROM ANY plato.Input1 AS l - LEFT JOIN ANY $r AS r - USING (key); - )"); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "EquiJoin") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('left 'any)")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('right 'any)")); - } - }; - - TWordCountHive elementStat = {{TString("left"), 0}, {TString("right"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["left"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["right"]); - } - - Y_UNIT_TEST(AnyJoinForTableAndTableSource) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - - $r = AsList( - AsStruct("aaa" as key, "bbb" as subkey, "ccc" as value) - ); - - SELECT * FROM ANY plato.Input1 AS l - LEFT JOIN ANY AS_TABLE($r) AS r - USING (key); - )"); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "EquiJoin") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('left 'any)")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('right 'any)")); - } - }; - - TWordCountHive elementStat = {{TString("left"), 0}, {TString("right"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["left"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["right"]); - } - - Y_UNIT_TEST(AnyJoinNested) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - - FROM ANY Input1 as a - JOIN Input2 as b ON a.key = b.key - LEFT JOIN ANY Input3 as c ON a.key = c.key - RIGHT JOIN ANY Input4 as d ON d.key = b.key - CROSS JOIN Input5 - SELECT *; - )"); - - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("left"), 0}, {TString("right"), 0}}; - VerifyProgram(res, elementStat); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["left"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["right"]); - } - - Y_UNIT_TEST(InlineAction) { - NYql::TAstParseResult res = SqlToYql( - "do begin\n" - " select 1\n" - "; end do\n"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), ""); - } - - Y_UNIT_TEST(FlattenByCorrelationName) { - UNIT_ASSERT(SqlToYql("select * from plato.Input as t flatten by t.x").IsOk()); - UNIT_ASSERT(SqlToYql("select * from plato.Input as t flatten by t -- same as flatten by t.t").IsOk()); - } - - Y_UNIT_TEST(DiscoveryMode) { - UNIT_ASSERT(SqlToYqlWithMode("insert into plato.Output select * from plato.Input", NSQLTranslation::ESqlMode::DISCOVERY).IsOk()); - UNIT_ASSERT(SqlToYqlWithMode("select * from plato.concat(Input1, Input2)", NSQLTranslation::ESqlMode::DISCOVERY).IsOk()); - UNIT_ASSERT(SqlToYqlWithMode("select * from plato.each(AsList(\"Input1\", \"Input2\"))", NSQLTranslation::ESqlMode::DISCOVERY).IsOk()); - } - - Y_UNIT_TEST(CubeWithAutoGeneratedLikeColumnName) { - UNIT_ASSERT(SqlToYql("select key,subkey,group from plato.Input group by cube(key,subkey,group)").IsOk()); - } - - Y_UNIT_TEST(CubeWithAutoGeneratedLikeAlias) { - UNIT_ASSERT(SqlToYql("select key,subkey,group from plato.Input group by cube(key,subkey,value as group)").IsOk()); - } - - Y_UNIT_TEST(FilterCanBeUsedAsColumnIdOrBind) { - UNIT_ASSERT(SqlToYql("select filter from plato.Input").IsOk()); - UNIT_ASSERT(SqlToYql("select 1 as filter").IsOk()); - UNIT_ASSERT(SqlToYql("$filter = 1; select $filter").IsOk()); - } - - Y_UNIT_TEST(DuplicateSemicolonsAreAllowedBetweenTopLevelStatements) { - UNIT_ASSERT(SqlToYql(";;select 1; ; select 2;/*comment*/;select 3;;--comment\n;select 4;;").IsOk()); - } - - Y_UNIT_TEST(DuplicateAndMissingTrailingSemicolonsAreAllowedBetweenActionStatements) { - TString req = - "define action $action($b,$c) as\n" - " ;;$d = $b + $c;\n" - " select $b;\n" - " select $c;;\n" - " select $d,\n" - "end define;\n" - "\n" - "do $action(1,2);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(DuplicateAndMissingTrailingSemicolonsAreAllowedBetweenInlineActionStatements) { - TString req = - "do begin\n" - " ;select 1,\n" - "end do;\n" - "evaluate for $i in AsList(1,2,3) do begin\n" - " select $i;;\n" - " select $i + $i;;\n" - "end do;"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(DuplicateSemicolonsAreAllowedBetweenLambdaStatements) { - TString req = - "$x=1;\n" - "$foo = ($a, $b)->{\n" - " ;;$v = $a + $b;\n" - " $bar = ($c) -> {; return $c << $x};;\n" - " return $bar($v);;\n" - "};\n" - "select $foo(1,2);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(StringLiteralWithEscapedBackslash) { - NYql::TAstParseResult res1 = SqlToYql(R"foo(SELECT 'a\\';)foo"); - NYql::TAstParseResult res2 = SqlToYql(R"foo(SELECT "a\\";)foo"); - UNIT_ASSERT(res1.Root); - UNIT_ASSERT(res2.Root); - - TWordCountHive elementStat = {{TString("a\\"), 0}}; - - VerifyProgram(res1, elementStat); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["a\\"]); - - VerifyProgram(res2, elementStat); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["a\\"]); - } - - Y_UNIT_TEST(StringMultiLineLiteralWithEscapes) { - UNIT_ASSERT(SqlToYql("SELECT @@@foo@@@@bar@@@").IsOk()); - UNIT_ASSERT(SqlToYql("SELECT @@@@@@@@@").IsOk()); - } - - Y_UNIT_TEST(StringMultiLineLiteralConsequitiveAt) { - UNIT_ASSERT(!SqlToYql("SELECT @").IsOk()); - UNIT_ASSERT(!SqlToYql("SELECT @@").IsOk()); - UNIT_ASSERT(!SqlToYql("SELECT @@@").IsOk()); - UNIT_ASSERT( SqlToYql("SELECT @@@@").IsOk()); - UNIT_ASSERT( SqlToYql("SELECT @@@@@").IsOk()); - - UNIT_ASSERT(!SqlToYql("SELECT @@@@@@").IsOk()); - UNIT_ASSERT(!SqlToYql("SELECT @@@@@@@").IsOk()); - - UNIT_ASSERT( SqlToYql("SELECT @@@@@@@@").IsOk()); - UNIT_ASSERT( SqlToYql("SELECT @@@@@@@@@").IsOk()); - UNIT_ASSERT(!SqlToYql("SELECT @@@@@@@@@@").IsOk()); - } - - Y_UNIT_TEST(ConstnessForListDictSetCreate) { - auto req = "$foo = ($x, $y) -> (\"aaaa\");\n" - "\n" - "select\n" - " $foo(sum(key), ListCreate(String)),\n" - " $foo(sum(key), DictCreate(String, String)),\n" - " $foo(sum(key), SetCreate(String)),\n" - "from (select 1 as key);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(CanUseEmptyTupleInWindowPartitionBy) { - auto req = "select sum(key) over w\n" - "from plato.Input\n" - "window w as (partition compact by (), (subkey), (), value || value as dvalue);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(DenyAnsiOrderByLimitLegacyMode) { - auto req = "pragma DisableAnsiOrderByLimitInUnionAll;\n" - "use plato;\n" - "\n" - "select * from Input order by key limit 10\n" - "union all\n" - "select * from Input order by key limit 1;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: DisableAnsiOrderByLimitInUnionAll pragma is deprecated and no longer supported\n"); - } - - Y_UNIT_TEST(ReduceUsingUdfWithShortcutsWorks) { - auto req = "use plato;\n" - "\n" - "$arg = 'foo';\n" - "$func = XXX::YYY($arg);\n" - "\n" - "REDUCE Input ON key using $func(subkey);\n" - "REDUCE Input ON key using $func(UUU::VVV(TableRow()));\n"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - req = "use plato;\n" - "\n" - "$arg = 'foo';\n" - "$func = XXX::YYY($arg);\n" - "\n" - "REDUCE Input ON key using all $func(subkey);\n" - "REDUCE Input ON key using all $func(UUU::VVV(TableRow()));"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(YsonDisableStrict) { - UNIT_ASSERT(SqlToYql("pragma yson.DisableStrict = \"false\";").IsOk()); - UNIT_ASSERT(SqlToYql("pragma yson.DisableStrict;").IsOk()); - } - - Y_UNIT_TEST(YsonStrict) { - UNIT_ASSERT(SqlToYql("pragma yson.Strict = \"false\";").IsOk()); - UNIT_ASSERT(SqlToYql("pragma yson.Strict;").IsOk()); - } - - Y_UNIT_TEST(JoinByTuple) { - auto req = "use plato;\n" - "\n" - "select * from T1 as a\n" - "join T2 as b\n" - "on AsTuple(a.key, a.subkey) = AsTuple(b.key, b.subkey);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(JoinByStruct) { - auto req = "use plato;\n" - "\n" - "select * from T1 as a\n" - "join T2 as b\n" - "on AsStruct(a.key as k, a.subkey as sk) = AsStruct(b.key as k, b.subkey as sk);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(JoinByUdf) { - auto req = "use plato;\n" - "\n" - "select a.align\n" - "from T1 as a\n" - "join T2 as b\n" - "on Yson::SerializeJsonEncodeUtf8(a.align)=b.align;"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(EscapedIdentifierAsLambdaArg) { - auto req = "$f = ($`foo bar`, $x) -> { return $`foo bar` + $x; };\n" - "\n" - "select $f(1, 2);"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(lambda '(\"$foo bar\" \"$x\")"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(UdfSyntaxSugarOnlyCallable) { - auto req = "SELECT Udf(DateTime::FromString)('2022-01-01');"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType)))"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(UdfSyntaxSugarTypeNoRun) { - auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, 'foo' as TypeConfig)('2022-01-01');"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\")"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(UdfSyntaxSugarRunNoType) { - auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, Void() as RunConfig)('2022-01-01');"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"\" (Void))"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(UdfSyntaxSugarFullTest) { - auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, 'foo' as TypeConfig, Void() As RunConfig)('2022-01-01');"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\" (Void))"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(UdfSyntaxSugarOtherRunConfigs) { - auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, 'foo' as TypeConfig, '55' As RunConfig)('2022-01-01');"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\" (String '\"55\"))"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(UdfSyntaxSugarOtherRunConfigs2) { - auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, 'foo' as TypeConfig, AsTuple(32, 'no', AsStruct(1e-9 As SomeFloat)) As RunConfig)('2022-01-01');"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\" '((Int32 '\"32\") (String '\"no\") (AsStruct '('\"SomeFloat\" (Double '\"1e-9\")))))"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(UdfSyntaxSugarOptional) { - auto req = "SELECT Udf(DateTime::FromString, String?, Int32??, Tuple<Int32, Float>, \"foo\" as TypeConfig, Void() As RunConfig)(\"2022-01-01\");"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (OptionalType (DataType 'String)) (OptionalType (OptionalType (DataType 'Int32))) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\" (Void))"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(CompactionPolicyParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) - WITH ( COMPACTION_POLICY = "SomeCompactionPreset" );)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("compactionPolicy")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("SomeCompactionPreset")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(AutoPartitioningBySizeParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) - WITH ( AUTO_PARTITIONING_BY_SIZE = ENABLED );)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("autoPartitioningBySize")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("ENABLED")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(UniformPartitionsParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) - WITH ( UNIFORM_PARTITIONS = 16 );)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("uniformPartitions")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("16")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DateTimeTtlParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, CreatedAt Timestamp, PRIMARY KEY (Key)) - WITH (TTL = Interval("P1D") On CreatedAt);)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(IntTtlParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, CreatedAt Uint32, PRIMARY KEY (Key)) - WITH (TTL = Interval("P1D") On CreatedAt AS SECONDS);)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("columnUnit")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("seconds")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(TtlTieringParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, CreatedAt Uint32, PRIMARY KEY (Key)) - WITH (TTL = - Interval("P1D") TO EXTERNAL DATA SOURCE Tier1, - Interval("P2D") TO EXTERNAL DATA SOURCE Tier2, - Interval("P30D") DELETE - ON CreatedAt AS SECONDS);)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("storageName")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier1")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier2")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("172800000")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("2592000000")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("columnUnit")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("seconds")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(TtlTieringWithOtherActionsParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - ALTER TABLE tableName - ADD FAMILY cold (DATA = "rot"), - SET TTL - Interval("P1D") TO EXTERNAL DATA SOURCE Tier1, - Interval("P2D") TO EXTERNAL DATA SOURCE Tier2, - Interval("P30D") DELETE - ON CreatedAt, - ALTER COLUMN payload_v2 SET FAMILY cold, - ALTER FAMILY default SET DATA "ssd" - ;)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("addColumnFamilies")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("cold")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alterColumnFamilies")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("default")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("storageName")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier1")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier2")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("172800000")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("2592000000")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(TieringParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) - WITH ( TIERING = 'my_tiering' );)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiering")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("my_tiering")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(StoreExternalBlobsParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) - WITH ( STORE_EXTERNAL_BLOBS = ENABLED );)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("storeExternalBlobs")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("ENABLED")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DefaultValueColumn2) { - auto res = SqlToYql(R"( use plato; - $lambda = () -> { - RETURN CAST(RandomUuid(2) as String) - }; - - CREATE TABLE tableName ( - Key Uint32 DEFAULT RandomNumber(1), - Value String DEFAULT $lambda, - PRIMARY KEY (Key) - ); - )"); - - UNIT_ASSERT_C(res.Root, Err2Str(res)); - - const auto program = GetPrettyPrint(res); - - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("RandomNumber")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("RandomUuid")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("columnConstrains")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("columnConstrains")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("Write")); - -#if 0 - Cerr << program << Endl; -#endif - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DefaultValueColumn3) { - auto res = SqlToYql(R"( use plato; - - CREATE TABLE tableName ( - database_id Utf8, - cloud_id Utf8, - global_id Utf8 DEFAULT database_id || "=====", - PRIMARY KEY (database_id) - ); - )"); - - UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:6:40: Error: Column reference \"database_id\" is not allowed in current scope\n"); - UNIT_ASSERT(!res.Root); - } - - Y_UNIT_TEST(DefaultValueColumn) { - auto res = SqlToYql(R"( use plato; - CREATE TABLE tableName ( - Key Uint32 FAMILY cold DEFAULT 5, - Value String FAMILY default DEFAULT "empty", - PRIMARY KEY (Key), - FAMILY default ( - DATA = "test", - COMPRESSION = "lz4" - ), - FAMILY cold ( - DATA = "test", - COMPRESSION = "off" - ) - ); - )"); - - UNIT_ASSERT_C(res.Root, Err2Str(res)); - -#if 0 - const auto program = GetPrettyPrint(res); - Cerr << program << Endl; -#endif - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("default")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("columnConstrains")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("columnFamilies")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(ChangefeedParseCorrect) { - auto res = SqlToYql(R"( USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH ( - MODE = 'KEYS_ONLY', - FORMAT = 'json', - INITIAL_SCAN = TRUE, - VIRTUAL_TIMESTAMPS = FALSE, - BARRIERS_INTERVAL = Interval("PT1S"), - RETENTION_PERIOD = Interval("P1D"), - TOPIC_MIN_ACTIVE_PARTITIONS = 10, - AWS_REGION = 'aws:region' - ) - ); - )"); - UNIT_ASSERT_C(res.Root, Err2Str(res)); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("changefeed")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("mode")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("KEYS_ONLY")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("format")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("json")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("initial_scan")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("true")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("virtual_timestamps")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("false")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("barriers_interval")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("retention_period")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("topic_min_active_partitions")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("aws_region")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("aws:region")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CloneForAsTableWorksWithCube) { - UNIT_ASSERT(SqlToYql("SELECT * FROM AS_TABLE([<|k1:1, k2:1|>]) GROUP BY CUBE(k1, k2);").IsOk()); - } - - Y_UNIT_TEST(WindowPartitionByColumnProperlyEscaped) { - NYql::TAstParseResult res = SqlToYql("SELECT SUM(key) OVER w FROM plato.Input WINDOW w AS (PARTITION BY `column with space`);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "CalcOverWindow") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"column with space\"")); - } - }; - - TWordCountHive elementStat = { {TString("CalcOverWindow"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["CalcOverWindow"]); - } - - Y_UNIT_TEST(WindowPartitionByExpressionWithoutAliasesAreAllowed) { - NYql::TAstParseResult res = SqlToYql("SELECT SUM(key) OVER w FROM plato.Input as i WINDOW w AS (PARTITION BY ii.subkey);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "AddMember") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("AddMember row 'group_w_0 (SqlAccess 'struct (Member row '\"ii\")")); - } - if (word == "CalcOverWindow") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("CalcOverWindow core '('\"group_w_0\")")); - } - }; - - TWordCountHive elementStat = { {TString("CalcOverWindow"), 0}, {TString("AddMember"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["CalcOverWindow"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["AddMember"]); - } - - Y_UNIT_TEST(PqReadByAfterUse) { - ExpectFailWithError("use plato; pragma PqReadBy='plato2';", - "<main>:1:28: Error: Cluster in PqReadPqBy pragma differs from cluster specified in USE statement: plato2 != plato\n"); - - UNIT_ASSERT(SqlToYql("pragma PqReadBy='plato2';").IsOk()); - UNIT_ASSERT(SqlToYql("pragma PqReadBy='plato2'; use plato;").IsOk()); - UNIT_ASSERT(SqlToYql("$x='plato'; use rtmr:$x; pragma PqReadBy='plato2';").IsOk()); - UNIT_ASSERT(SqlToYql("use plato; pragma PqReadBy='dq';").IsOk()); - } - - Y_UNIT_TEST(MrObject) { - NYql::TAstParseResult res = SqlToYql( - "declare $path as String;\n" - "select * from plato.object($path, `format`, \"comp\" || \"ression\" as compression, 1 as bar) with schema (Int32 as y, String as x)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "MrObject") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((MrObject (EvaluateAtom "$path") '"format" '('('"compression" (Concat (String '"comp") (String '"ression"))) '('"bar" (Int32 '"1")))))__")); - } else if (word == "userschema") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__('('('"userschema" (StructType '('"y" (DataType 'Int32)) '('"x" (DataType 'String))) '('"y" '"x"))))__")); - } - }; - - TWordCountHive elementStat = {{TString("MrObject"), 0}, {TString("userschema"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["MrObject"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["userschema"]); - } - - Y_UNIT_TEST(TableBindings) { - NSQLTranslation::TTranslationSettings settings = GetSettingsWithS3Binding("foo"); - NYql::TAstParseResult res = SqlToYqlWithSettings( - "select * from bindings.foo", - settings - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "MrObject") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((MrTableConcat (Key '('table (String '"path")))) (Void) '('('"bar" '"1") '('"compression" '"ccompression") '('"format" '"format") '('"partitionedby" '"key" '"subkey") '('"userschema" (SqlTypeFromYson)__")); - } - }; - - TWordCountHive elementStat = {{TString("MrTableConcat"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["MrTableConcat"]); - - settings.DefaultCluster = "plato"; - settings.BindingsMode = NSQLTranslation::EBindingsMode::DISABLED; - res = SqlToYqlWithSettings( - "select * from bindings.foo", - settings - ); - UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:1:15: Error: Please remove 'bindings.' from your query, the support for this syntax has ended, code: 4601\n"); - UNIT_ASSERT(!res.Root); - - settings.BindingsMode = NSQLTranslation::EBindingsMode::DROP; - res = SqlToYqlWithSettings( - "select * from bindings.foo", - settings - ); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine2 = [](const TString& word, const TString& line) { - if (word == "MrTableConcat") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((MrTableConcat (Key '('table (String '"foo")))) (Void) '())))__")); - } - }; - - TWordCountHive elementStat2 = {{TString("MrTableConcat"), 0}}; - VerifyProgram(res, elementStat2, verifyLine2); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat2["MrTableConcat"]); - - settings.BindingsMode = NSQLTranslation::EBindingsMode::DROP_WITH_WARNING; - res = SqlToYqlWithSettings( - "select * from bindings.foo", - settings - ); - UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:1:15: Warning: Please remove 'bindings.' from your query, the support for this syntax will be dropped soon, code: 4538\n"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat3 = {{TString("MrTableConcat"), 0}}; - VerifyProgram(res, elementStat3, verifyLine2); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat3["MrTableConcat"]); - } - - Y_UNIT_TEST(TableBindingsWithInsert) { - NSQLTranslation::TTranslationSettings settings = GetSettingsWithS3Binding("foo"); - NYql::TAstParseResult res = SqlToYqlWithSettings( - "insert into bindings.foo with truncate (x, y) values (1, 2);", - settings - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('table (String '"path"))) values '('('"bar" '"1") '('"compression" '"ccompression") '('"format" '"format") '('"partitionedby" '"key" '"subkey") '('"userschema" (SqlTypeFromYson)__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - - settings.DefaultCluster = "plato"; - settings.BindingsMode = NSQLTranslation::EBindingsMode::DISABLED; - res = SqlToYqlWithSettings( - "insert into bindings.foo with truncate (x, y) values (1, 2);", - settings - ); - UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:1:13: Error: Please remove 'bindings.' from your query, the support for this syntax has ended, code: 4601\n"); - UNIT_ASSERT(!res.Root); - - settings.BindingsMode = NSQLTranslation::EBindingsMode::DROP; - res = SqlToYqlWithSettings( - "insert into bindings.foo with truncate (x, y) values (1, 2);", - settings - ); - UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), ""); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine2 = [](const TString& word, const TString& line) { - if (word == "Write!") { - //UNIT_ASSERT_VALUES_EQUAL(line, ""); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('table (String '"foo"))) values '('('mode 'renew)))__")); - } - }; - - TWordCountHive elementStat2 = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat2, verifyLine2); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat2["Write!"]); - - settings.BindingsMode = NSQLTranslation::EBindingsMode::DROP_WITH_WARNING; - res = SqlToYqlWithSettings( - "insert into bindings.foo with truncate (x, y) values (1, 2);", - settings - ); - UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:1:13: Warning: Please remove 'bindings.' from your query, the support for this syntax will be dropped soon, code: 4538\n"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat3 = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat3, verifyLine2); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat3["Write!"]); - } - - Y_UNIT_TEST(TrailingCommaInWithout) { - UNIT_ASSERT(SqlToYql("SELECT * WITHOUT stream, FROM plato.Input").IsOk()); - UNIT_ASSERT(SqlToYql("SELECT a.* WITHOUT a.intersect, FROM plato.Input AS a").IsOk()); - UNIT_ASSERT(SqlToYql("SELECT a.* WITHOUT col1, col2, a.col3, FROM plato.Input AS a").IsOk()); - } - - Y_UNIT_TEST(NoStackOverflowOnBigCaseStatement) { - TStringBuilder req; - req << "select case 1 + 123"; - for (size_t i = 0; i < 20000; ++i) { - req << " when " << i << " then " << i + 1; - } - req << " else 100500 end;"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(CollectPreaggregatedInListLiteral) { - UNIT_ASSERT(SqlToYql("SELECT [COUNT(DISTINCT a+b)] FROM plato.Input").IsOk()); - } - - Y_UNIT_TEST(SmartParenInGroupByClause) { - UNIT_ASSERT(SqlToYql("SELECT * FROM plato.Input GROUP BY (k, v)").IsOk()); - } - - Y_UNIT_TEST(AlterTableRenameToIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table RENAME TO moved").IsOk()); - } - - Y_UNIT_TEST(AlterTableAddDropColumnIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table ADD COLUMN addc uint64, DROP COLUMN dropc, ADD addagain uint64").IsOk()); - } - - Y_UNIT_TEST(AlterTableSetTTLIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table SET (TTL = Interval(\"PT3H\") ON column)").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table SET (TTL = Interval(\"PT3H\") ON column AS SECONDS)").IsOk()); - } - - Y_UNIT_TEST(AlterTableSetTieringIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table SET (TIERING = 'my_tiering')").IsOk()); - } - - Y_UNIT_TEST(AlterTableAddChangefeedIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table ADD CHANGEFEED feed WITH (MODE = 'UPDATES', FORMAT = 'json')").IsOk()); - } - - Y_UNIT_TEST(AlterTableAlterChangefeedIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table ALTER CHANGEFEED feed DISABLE").IsOk()); - ExpectFailWithError("USE plato; ALTER TABLE table ALTER CHANGEFEED feed SET (FORMAT = 'proto');", - "<main>:1:57: Error: FORMAT alter is not supported\n"); - } - - Y_UNIT_TEST(AlterTableDropChangefeedIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table DROP CHANGEFEED feed").IsOk()); - } - - Y_UNIT_TEST(AlterTableSetPartitioningIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table SET (AUTO_PARTITIONING_BY_SIZE = DISABLED)").IsOk()); - } - - Y_UNIT_TEST(AlterTableAddIndexWithIsNotSupported) { - ExpectFailWithFuzzyError("USE plato; ALTER TABLE table ADD INDEX idx GLOBAL ON (col) WITH (a=b)", - "<main>:1:40: Error: with: alternative is not implemented yet: \\d+:\\d+: global_index\\n"); - } - - Y_UNIT_TEST(AlterTableAddIndexLocalIsNotSupported) { - ExpectFailWithFuzzyError("USE plato; ALTER TABLE table ADD INDEX idx LOCAL ON (col)", - "<main>:1:40: Error: local: alternative is not implemented yet: \\d+:\\d+: local_index\\n"); - } - - Y_UNIT_TEST(CreateTableAddIndexVector) { - const auto result = SqlToYql(R"(USE plato; - CREATE TABLE table ( - pk INT32 NOT NULL, - col String, - INDEX idx GLOBAL USING vector_kmeans_tree - ON (col) COVER (col) - WITH (distance=cosine, vector_type=float, vector_dimension=1024,), - PRIMARY KEY (pk)) - )"); - UNIT_ASSERT_C(result.IsOk(), result.Issues.ToString()); - } - - Y_UNIT_TEST(AlterTableAddIndexVector) { - const auto result = SqlToYql(R"(USE plato; - ALTER TABLE table ADD INDEX idx - GLOBAL USING vector_kmeans_tree - ON (col) COVER (col) - WITH (distance=cosine, vector_type="float", vector_dimension=1024) - )"); - UNIT_ASSERT_C(result.IsOk(), result.Issues.ToString()); - } - - Y_UNIT_TEST(AlterTableAddIndexUnknownSubtype) { - ExpectFailWithError("USE plato; ALTER TABLE table ADD INDEX idx GLOBAL USING unknown ON (col)", - "<main>:1:57: Error: UNKNOWN index subtype is not supported\n"); - } - - Y_UNIT_TEST(AlterTableAddIndexMissedParameter) { - ExpectFailWithError(R"(USE plato; - ALTER TABLE table ADD INDEX idx - GLOBAL USING vector_kmeans_tree - ON (col) - WITH (distance=cosine, vector_type=float) - )", - "<main>:5:52: Error: vector_dimension should be set\n"); - } - - Y_UNIT_TEST(AlterTableAlterIndexSetPartitioningIsCorrect) { - const auto result = SqlToYql("USE plato; ALTER TABLE table ALTER INDEX index SET AUTO_PARTITIONING_MIN_PARTITIONS_COUNT 10"); - UNIT_ASSERT_C(result.IsOk(), result.Issues.ToString()); - } - - Y_UNIT_TEST(AlterTableAlterIndexSetMultiplePartitioningSettings) { - const auto result = SqlToYql("USE plato; ALTER TABLE table ALTER INDEX index SET " - "(AUTO_PARTITIONING_BY_LOAD = ENABLED, AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = 10)" - ); - UNIT_ASSERT_C(result.IsOk(), result.Issues.ToString()); - } - - Y_UNIT_TEST(AlterTableAlterIndexResetPartitioningIsNotSupported) { - ExpectFailWithError("USE plato; ALTER TABLE table ALTER INDEX index RESET (AUTO_PARTITIONING_MIN_PARTITIONS_COUNT)", - "<main>:1:55: Error: AUTO_PARTITIONING_MIN_PARTITIONS_COUNT reset is not supported\n" - ); - } - - Y_UNIT_TEST(AlterTableAlterColumnDropNotNullAstCorrect) { - auto reqSetNull = SqlToYql(R"( - USE plato; - CREATE TABLE tableName ( - id Uint32, - val Uint32 NOT NULL, - PRIMARY KEY (id) - ); - - COMMIT; - ALTER TABLE tableName ALTER COLUMN val DROP NOT NULL; - )"); - - UNIT_ASSERT(reqSetNull.IsOk()); - UNIT_ASSERT(reqSetNull.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - Y_UNUSED(word); - - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find( - R"(let world (Write! world sink (Key '('tablescheme (String '"tableName"))) (Void) '('('mode 'alter) '('actions '('('alterColumns '('('"val" '('changeColumnConstraints '('('drop_not_null)))))))))))" - )); - }; - - TWordCountHive elementStat({TString("\'mode \'alter")}); - VerifyProgram(reqSetNull, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["\'mode \'alter"]); - } - - Y_UNIT_TEST(AlterSequence) { - UNIT_ASSERT(SqlToYql(R"( - USE plato; - ALTER SEQUENCE sequence START WITH 10 INCREMENT 2 RESTART WITH 5; - )").IsOk()); - UNIT_ASSERT(SqlToYql(R"( - USE plato; - ALTER SEQUENCE sequence INCREMENT 2; - )").IsOk()); - UNIT_ASSERT(SqlToYql(R"( - USE plato; - ALTER SEQUENCE sequence INCREMENT 2 START 1000; - )").IsOk()); - UNIT_ASSERT(SqlToYql(R"( - USE plato; - ALTER SEQUENCE sequence RESTART START 1000; - )").IsOk()); - UNIT_ASSERT(SqlToYql(R"( - USE plato; - ALTER SEQUENCE IF EXISTS sequence INCREMENT 1000 START 100 RESTART; - )").IsOk()); - UNIT_ASSERT(SqlToYql(R"( - USE plato; - ALTER SEQUENCE IF EXISTS sequence RESTART 1000 START WITH 100 INCREMENT BY 7; - )").IsOk()); - } - - Y_UNIT_TEST(AlterSequenceIncorrect) { - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence START WITH 10 INCREMENT 2 RESTART WITH 5 RESTART;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:75: Error: Restart value defined more than once\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence START WITH 10 INCREMENT 2 START 100 RESTART WITH 5;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:60: Error: Start value defined more than once\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence INCREMENT BY 7 START WITH 10 INCREMENT 2 RESTART WITH 5 RESTART;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:62: Error: Increment defined more than once\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 100 START WITH 10 INCREMENT 2 RESTART WITH 5;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:77: Error: Restart value defined more than once\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1234234543563435151456 START WITH 10 INCREMENT 2;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:49: Error: Failed to parse number from string: 1234234543563435151456, number limit overflow\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1 START WITH 9223372036854775817 INCREMENT 4;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Start value: 9223372036854775817 cannot be greater than max value: 9223372036854775807\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 9223372036854775827 START WITH 5 INCREMENT 4;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Restart value: 9223372036854775827 cannot be greater than max value: 9223372036854775807\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1 START WITH 4 INCREMENT 0;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Increment must not be zero\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 0 START WITH 4 INCREMENT 1;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Restart value: 0 cannot be less than min value: 1\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1 START WITH 0 INCREMENT 1;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Start value: 0 cannot be less than min value: 1\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1 START WITH 1 INCREMENT 9223372036854775837;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Increment: 9223372036854775837 cannot be greater than max value: 9223372036854775807\n"); - } - } - - Y_UNIT_TEST(AlterSequenceCorrect) { - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence START WITH 10 INCREMENT 2 RESTART WITH 5;"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("sequence")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter")); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("alter_if_exists")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("start")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("increment")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("restart")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE IF EXISTS sequence INCREMENT 2 RESTART;"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("sequence")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter_if_exists")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("increment")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("restart")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE IF EXISTS sequence START 10 INCREMENT BY 2;"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("sequence")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter_if_exists")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("start")); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("restart")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("increment")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - } - - Y_UNIT_TEST(ShowCreateTable) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - SHOW CREATE TABLE user; - )"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Read") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("showCreateTable")); - } - }; - - TWordCountHive elementStat = {{TString("Read"), 0}, {TString("showCreateTable"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Read"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["showCreateTable"]); - } - - Y_UNIT_TEST(OptionalAliases) { - UNIT_ASSERT(SqlToYql("USE plato; SELECT foo FROM (SELECT key foo FROM Input);").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT a.x FROM Input1 a JOIN Input2 b ON a.key = b.key;").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT a.x FROM (VALUES (1,2), (3,4)) a(x,key) JOIN Input b ON a.key = b.key;").IsOk()); - } - - Y_UNIT_TEST(TableNameConstness) { - UNIT_ASSERT(SqlToYql("USE plato; $path = 'foo'; SELECT TableName($path), count(*) FROM Input;").IsOk()); - UNIT_ASSERT(SqlToYql("$path = 'foo'; SELECT TableName($path, 'yt'), count(*) FROM plato.Input;").IsOk()); - ExpectFailWithError("USE plato; SELECT TableName(), count(*) FROM plato.Input;", - "<main>:1:19: Error: Expression has to be an aggregation function or key column, because aggregation is used elsewhere in this subquery\n"); - } - - Y_UNIT_TEST(UseShouldWorkAsColumnName) { - UNIT_ASSERT(SqlToYql("select use from (select 1 as use);").IsOk()); - } - - Y_UNIT_TEST(TrueFalseWorkAfterDollar) { - UNIT_ASSERT(SqlToYql("$ true = false; SELECT $ true or false;").IsOk()); - UNIT_ASSERT(SqlToYql("$False = 0; SELECT $False;").IsOk()); - } - - Y_UNIT_TEST(WithSchemaEquals) { - UNIT_ASSERT(SqlToYql("select * from plato.T with schema Struct<a:Int32, b:String>;").IsOk()); - UNIT_ASSERT(SqlToYql("select * from plato.T with columns = Struct<a:Int32, b:String>;").IsOk()); - } - - Y_UNIT_TEST(WithNonStructSchemaS3) { - NSQLTranslation::TTranslationSettings settings; - settings.ClusterMapping["s3bucket"] = NYql::S3ProviderName; - UNIT_ASSERT(SqlToYqlWithSettings("select * from s3bucket.`foo` with schema (col1 Int32, String as col2, Int64 as col3);", settings).IsOk()); - } - - Y_UNIT_TEST(AllowNestedTuplesInGroupBy) { - NYql::TAstParseResult res = SqlToYql("select count(*) from plato.Input group by 1 + (x, y, z);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Aggregate core '('\"group0\")")); - }; - - TWordCountHive elementStat({"Aggregate"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["Aggregate"] == 1); - } - - Y_UNIT_TEST(AllowGroupByWithParens) { - NYql::TAstParseResult res = SqlToYql("select count(*) from plato.Input group by (x, y as alias1, z);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Aggregate core '('\"x\" '\"alias1\" '\"z\")")); - }; - - TWordCountHive elementStat({"Aggregate"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["Aggregate"] == 1); - } - - Y_UNIT_TEST(CreateAsyncReplicationParseCorrect) { - auto req = R"( - USE plato; - CREATE ASYNC REPLICATION MyReplication - FOR table1 AS table2, table3 AS table4 - WITH ( - CONNECTION_STRING = "grpc://localhost:2135/?database=/MyDatabase", - ENDPOINT = "localhost:2135", - DATABASE = "/MyDatabase" - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("MyReplication")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("create")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table1")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table2")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table3")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table4")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("connection_string")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("grpc://localhost:2135/?database=/MyDatabase")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("endpoint")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("localhost:2135")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("database")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("/MyDatabase")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateAsyncReplicationUnsupportedSettings) { - auto reqTpl = R"( - USE plato; - CREATE ASYNC REPLICATION MyReplication - FOR table1 AS table2, table3 AS table4 - WITH ( - %s = "%s" - ) - )"; - - auto settings = THashMap<TString, TString>{ - {"STATE", "DONE"}, - {"FAILOVER_MODE", "FORCE"}, - }; - - for (const auto& [k, v] : settings) { - auto req = Sprintf(reqTpl, k.c_str(), v.c_str()); - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), Sprintf("<main>:6:%zu: Error: %s is not supported in CREATE\n", 20 + k.size(), k.c_str())); - } - } - - Y_UNIT_TEST(AsyncReplicationInvalidCommitInterval) { - auto req = R"( - USE plato; - CREATE ASYNC REPLICATION MyReplication - FOR table1 AS table2, table3 AS table4 - WITH ( - COMMIT_INTERVAL = "FOO" - ); - )"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:6:35: Error: Literal of Interval type is expected for COMMIT_INTERVAL\n"); - } - - Y_UNIT_TEST(AlterAsyncReplicationParseCorrect) { - auto req = R"( - USE plato; - ALTER ASYNC REPLICATION MyReplication - SET ( - STATE = "DONE", - FAILOVER_MODE = "FORCE" - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("MyReplication")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("state")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("DONE")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("failover_mode")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("FORCE")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(AlterAsyncReplicationSettings) { - auto reqTpl = R"( - USE plato; - ALTER ASYNC REPLICATION MyReplication - SET ( - %s = "%s" - ) - )"; - - auto settings = THashMap<TString, TString>{ - {"connection_string", "grpc://localhost:2135/?database=/MyDatabase"}, - {"endpoint", "localhost:2135"}, - {"database", "/MyDatabase"}, - {"token", "foo"}, - {"token_secret_name", "foo_secret_name"}, - {"user", "user"}, - {"password", "bar"}, - {"password_secret_name", "bar_secret_name"}, - }; - - for (const auto& [k, v] : settings) { - auto req = Sprintf(reqTpl, k.c_str(), v.c_str()); - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&k, &v](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("MyReplication")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(k)); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(v)); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - } - - Y_UNIT_TEST(AlterAsyncReplicationUnsupportedSettings) { - { - auto req = R"( - USE plato; - ALTER ASYNC REPLICATION MyReplication SET (CONSISTENCY_LEVEL = "GLOBAL"); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:80: Error: CONSISTENCY_LEVEL is not supported in ALTER\n"); - } - { - auto req = R"( - USE plato; - ALTER ASYNC REPLICATION MyReplication SET (COMMIT_INTERVAL = Interval("PT10S")); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:87: Error: COMMIT_INTERVAL is not supported in ALTER\n"); - } - } - - Y_UNIT_TEST(AsyncReplicationInvalidSettings) { - auto req = R"( - USE plato; - ALTER ASYNC REPLICATION MyReplication SET (FOO = "BAR"); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:62: Error: Unknown replication setting: FOO\n"); - } - - Y_UNIT_TEST(DropAsyncReplicationParseCorrect) { - auto req = R"( - USE plato; - DROP ASYNC REPLICATION MyReplication; - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("MyReplication")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("drop")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropAsyncReplicationCascade) { - auto req = R"( - USE plato; - DROP ASYNC REPLICATION MyReplication CASCADE; - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropCascade")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(PragmaCompactGroupBy) { - auto req = "PRAGMA CompactGroupBy; SELECT key, COUNT(*) FROM plato.Input GROUP BY key;"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Aggregate") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('compact)")); - } - }; - - TWordCountHive elementStat = { {TString("Aggregate"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Aggregate"]); - } - - Y_UNIT_TEST(PragmaDisableCompactGroupBy) { - auto req = "PRAGMA DisableCompactGroupBy; SELECT key, COUNT(*) FROM plato.Input GROUP /*+ compact() */ BY key;"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Aggregate") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'('compact)")); - } - }; - - TWordCountHive elementStat = { {TString("Aggregate"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Aggregate"]); - } - - Y_UNIT_TEST(AutoSampleWorksWithNamedSubquery) { - UNIT_ASSERT(SqlToYql("$src = select * from plato.Input; select * from $src sample 0.2").IsOk()); - } - - Y_UNIT_TEST(AutoSampleWorksWithSubquery) { - UNIT_ASSERT(SqlToYql("select * from (select * from plato.Input) sample 0.2").IsOk()); - } - - Y_UNIT_TEST(CreateTableTrailingComma) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE tableName (Key Uint32, PRIMARY KEY (Key),);").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE tableName (Key Uint32,);").IsOk()); - } - - Y_UNIT_TEST(BetweenSymmetric) { - UNIT_ASSERT(SqlToYql("select 3 between symmetric 5 and 4;").IsOk()); - UNIT_ASSERT(SqlToYql("select 3 between asymmetric 5 and 4;").IsOk()); - UNIT_ASSERT(SqlToYql("use plato; select key between symmetric and and and from Input;").IsOk()); - UNIT_ASSERT(SqlToYql("use plato; select key between and and and from Input;").IsOk()); - } -} - -Y_UNIT_TEST_SUITE(ExternalFunction) { - Y_UNIT_TEST(ValidUseFunctions) { - - UNIT_ASSERT(SqlToYql( - "PROCESS plato.Input" - " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo', <|a: 123, b: a + 641|>)" - " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>," - " CONCURRENCY=3, OPTIMIZE_FOR='CALLS'").IsOk()); - - // use CALLS without quotes, as keyword - UNIT_ASSERT(SqlToYql( - "PROCESS plato.Input" - " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo')" - " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>," - " OPTIMIZE_FOR=CALLS").IsOk()); - - UNIT_ASSERT(SqlToYql( - "PROCESS plato.Input" - " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo', TableRow())" - " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>," - " CONCURRENCY=3").IsOk()); - - UNIT_ASSERT(SqlToYql( - "PROCESS plato.Input" - " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo')" - " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>," - " CONCURRENCY=3, BATCH_SIZE=1000000, CONNECTION='yc-folder34fse-con'," - " INIT=[0, 900]").IsOk()); - - UNIT_ASSERT(SqlToYql( - "PROCESS plato.Input" - " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'bar', TableRow())" - " WITH UNKNOWN_PARAM_1='837747712', UNKNOWN_PARAM_2=Tuple<Uint16, Utf8>," - " INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>").IsOk()); - } - - - Y_UNIT_TEST(InValidUseFunctions) { - ExpectFailWithError("PROCESS plato.Input USING some::udf(*) WITH INPUT_TYPE=Struct<a:Int32>", - "<main>:1:33: Error: PROCESS without USING EXTERNAL FUNCTION doesn't allow WITH block\n"); - - ExpectFailWithError("PROCESS plato.Input USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'jhhjfh88134d')" - " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>" - " ASSUME ORDER BY key", - "<main>:1:129: Error: PROCESS with USING EXTERNAL FUNCTION doesn't allow ASSUME block\n"); - - ExpectFailWithError("PROCESS plato.Input USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo', 'bar', 'baz')", - "<main>:1:15: Error: EXTERNAL FUNCTION requires from 2 to 3 arguments, but got: 4\n"); - - ExpectFailWithError("PROCESS plato.Input\n" - " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo', <|field_1: a1, field_b: b1|>)\n" - " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>,\n" - " CONCURRENCY=3, BATCH_SIZE=1000000, CONNECTION='yc-folder34fse-con',\n" - " CONCURRENCY=5, INPUT_TYPE=Struct<b:Bool>,\n" - " INIT=[0, 900]\n", - "<main>:5:2: Error: WITH \"CONCURRENCY\" clause should be specified only once\n" - "<main>:5:17: Error: WITH \"INPUT_TYPE\" clause should be specified only once\n"); - } -} - -Y_UNIT_TEST_SUITE(SqlToYQLErrors) { - Y_UNIT_TEST(UdfSyntaxSugarMissingCall) { - auto req = "SELECT Udf(DateTime::FromString, \"foo\" as RunConfig);"; - auto res = SqlToYql(req); - TString a1 = Err2Str(res); - TString a2("<main>:1:8: Error: Abstract Udf Node can't be used as a part of expression.\n"); - UNIT_ASSERT_NO_DIFF(a1, a2); - } - - Y_UNIT_TEST(UdfSyntaxSugarIsNotCallable) { - auto req = "SELECT Udf(123, \"foo\" as RunConfig);"; - auto res = SqlToYql(req); - TString a1 = Err2Str(res); - TString a2("<main>:1:8: Error: Udf: first argument must be a callable, like Foo::Bar\n"); - UNIT_ASSERT_NO_DIFF(a1, a2); - } - - Y_UNIT_TEST(UdfSyntaxSugarNoArgs) { - auto req = "SELECT Udf()();"; - auto res = SqlToYql(req); - TString a1 = Err2Str(res); - TString a2("<main>:1:8: Error: Udf: expected at least one argument\n"); - UNIT_ASSERT_NO_DIFF(a1, a2); - } - - Y_UNIT_TEST(StrayUTF8) { - /// 'c' in plato is russian here - NYql::TAstParseResult res = SqlToYql("select * from сedar.Input"); - UNIT_ASSERT(!res.Root); - - TString a1 = Err2Str(res); - TString a2(R"foo(<main>:1:14: Error: Unexpected character 'с' (Unicode character <1089>) : cannot match to any predicted input... - -<main>:1:15: Error: Unexpected character : cannot match to any predicted input... - -)foo"); - - UNIT_ASSERT_NO_DIFF(a1, a2); - } - - Y_UNIT_TEST(IvalidStringLiteralWithEscapedBackslash) { - NYql::TAstParseResult res1 = SqlToYql(R"foo($bar = 'a\\'b';)foo"); - NYql::TAstParseResult res2 = SqlToYql(R"foo($bar = "a\\"b";)foo"); - UNIT_ASSERT(!res1.Root); - UNIT_ASSERT(!res2.Root); - - UNIT_ASSERT_NO_DIFF(Err2Str(res1), "<main>:1:15: Error: Unexpected character : syntax error...\n\n"); - UNIT_ASSERT_NO_DIFF(Err2Str(res2), "<main>:1:15: Error: Unexpected character : syntax error...\n\n"); - } - - Y_UNIT_TEST(InvalidHexInStringLiteral) { - NYql::TAstParseResult res = SqlToYql("select \"foo\\x1\\xfe\""); - UNIT_ASSERT(!res.Root); - TString a1 = Err2Str(res); - TString a2 = "<main>:1:15: Error: Failed to parse string literal: Invalid hexadecimal value\n"; - - UNIT_ASSERT_NO_DIFF(a1, a2); - } - - Y_UNIT_TEST(InvalidOctalInMultilineStringLiteral) { - NYql::TAstParseResult res = SqlToYql("select \"foo\n" - "bar\n" - "\\01\""); - UNIT_ASSERT(!res.Root); - TString a1 = Err2Str(res); - TString a2 = "<main>:3:4: Error: Failed to parse string literal: Invalid octal value\n"; - - UNIT_ASSERT_NO_DIFF(a1, a2); - } - - Y_UNIT_TEST(InvalidDoubleAtString) { - NYql::TAstParseResult res = SqlToYql("select @@@@@@"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:13: Error: Unexpected character : syntax error...\n\n"); - } - - Y_UNIT_TEST(InvalidDoubleAtStringWhichWasAcceptedEarlier) { - NYql::TAstParseResult res = SqlToYql("SELECT @@foo@@ @ @@bar@@"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:7: Error: Unexpected token '@@foo@@' : cannot match to any predicted input...\n\n"); - } - - Y_UNIT_TEST(InvalidStringFromTable) { - NYql::TAstParseResult res = SqlToYql("select \"FOO\"\"BAR from plato.foo"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:31: Error: Unexpected character : syntax error...\n\n"); - } - - Y_UNIT_TEST(InvalidDoubleAtStringFromTable) { - NYql::TAstParseResult res = SqlToYql("select @@@@@@ from plato.foo"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:28: Error: Unexpected character : syntax error...\n\n"); - } - - Y_UNIT_TEST(SelectInvalidSyntax) { - NYql::TAstParseResult res = SqlToYql("select 1 form Wat"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:14: Error: Unexpected token 'Wat' : cannot match to any predicted input...\n\n"); - } - - Y_UNIT_TEST(SelectNoCluster) { - NYql::TAstParseResult res = SqlToYql("select foo from bar"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: No cluster name given and no default cluster is selected\n"); - } - - Y_UNIT_TEST(SelectDuplicateColumns) { - NYql::TAstParseResult res = SqlToYql("select a, a from plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:11: Error: Unable to use duplicate column names. Collision in name: a\n"); - } - - Y_UNIT_TEST(SelectDuplicateLabels) { - NYql::TAstParseResult res = SqlToYql("select a as foo, b as foo from plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:18: Error: Unable to use duplicate column names. Collision in name: foo\n"); - } - - Y_UNIT_TEST(SelectCaseWithoutThen) { - NYql::TAstParseResult res = SqlToYql("select case when true 1;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - "<main>:1:22: Error: Unexpected token absence : Missing THEN \n\n" - "<main>:1:23: Error: Unexpected token absence : Missing END \n\n" - ); - } - - Y_UNIT_TEST(SelectComplexCaseWithoutThen) { - NYql::TAstParseResult res = SqlToYql( - "SELECT *\n" - "FROM plato.Input AS a\n" - "WHERE CASE WHEN a.key = \"foo\" a.subkey ELSE a.value END\n" - ); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:30: Error: Unexpected token absence : Missing THEN \n\n"); - } - - Y_UNIT_TEST(SelectCaseWithoutEnd) { - NYql::TAstParseResult res = SqlToYql("select case a when b then c end from plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: ELSE is required\n"); - } - - Y_UNIT_TEST(SelectWithBadAggregationNoInput) { - NYql::TAstParseResult res = SqlToYql("select a, Min(b), c"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:8: Error: Column reference 'a'\n" - "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:15: Error: Column reference 'b'\n" - "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:19: Error: Column reference 'c'\n" - ); - } - - Y_UNIT_TEST(SelectWithBadAggregation) { - ExpectFailWithError("select count(*), 1 + key from plato.Input", - "<main>:1:22: Error: Column `key` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(SelectWithBadAggregatedTerms) { - ExpectFailWithError("select key, 2 * subkey from plato.Input group by key", - "<main>:1:17: Error: Column `subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(SelectDistinctWithBadAggregation) { - ExpectFailWithError("select distinct count(*), 1 + key from plato.Input", - "<main>:1:31: Error: Column `key` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - ExpectFailWithError("select distinct key, 2 * subkey from plato.Input group by key", - "<main>:1:26: Error: Column `subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(SelectWithBadAggregationInHaving) { - ExpectFailWithError("select key from plato.Input group by key\n" - "having \"f\" || value == \"foo\"", - "<main>:2:15: Error: Column `value` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(JoinWithNonAggregatedColumnInProjection) { - ExpectFailWithError("select a.key, 1 + b.subkey\n" - "from plato.Input1 as a join plato.Input2 as b using(key)\n" - "group by a.key;", - "<main>:1:19: Error: Column `b.subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - - ExpectFailWithError("select a.key, 1 + b.subkey.x\n" - "from plato.Input1 as a join plato.Input2 as b using(key)\n" - "group by a.key;", - "<main>:1:19: Error: Column must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(SelectWithBadAggregatedTermsWithSources) { - ExpectFailWithError("select key, 1 + a.subkey\n" - "from plato.Input1 as a\n" - "group by a.key;", - "<main>:1:17: Error: Column `a.subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - ExpectFailWithError("select key, 1 + a.subkey.x\n" - "from plato.Input1 as a\n" - "group by a.key;", - "<main>:1:17: Error: Column must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(WarnForAggregationBySelectAlias) { - NYql::TAstParseResult res = SqlToYql("select c + 1 as c from plato.Input\n" - "group by c"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - "<main>:2:11: Warning: GROUP BY will aggregate by column `c` instead of aggregating by SELECT expression with same alias, code: 4532\n" - "<main>:1:10: Warning: You should probably use alias in GROUP BY instead of using it here. Please consult documentation for more details, code: 4532\n"); - - res = SqlToYql("select c + 1 as c from plato.Input\n" - "group by Math::Floor(c + 2) as c;"); - - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - "<main>:2:22: Warning: GROUP BY will aggregate by column `c` instead of aggregating by SELECT expression with same alias, code: 4532\n" - "<main>:1:10: Warning: You should probably use alias in GROUP BY instead of using it here. Please consult documentation for more details, code: 4532\n"); - } - - Y_UNIT_TEST(NoWarnForAggregationBySelectAliasWhenAggrFunctionsAreUsedInAlias) { - NYql::TAstParseResult res = SqlToYql("select\n" - " cast(avg(val) as int) as value,\n" - " value as key\n" - "from\n" - " plato.Input\n" - "group by value"); - - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.Issues.Size() == 0); - - res = SqlToYql("select\n" - " cast(avg(val) over w as int) as value,\n" - " value as key\n" - "from\n" - " plato.Input\n" - "group by value\n" - "window w as ()"); - - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.Issues.Size() == 0); - } - - Y_UNIT_TEST(NoWarnForAggregationBySelectAliasWhenQualifiedNameIsUsed) { - NYql::TAstParseResult res = SqlToYql("select\n" - " Unwrap(a.key) as key\n" - "from plato.Input as a\n" - "join plato.Input2 as b using(k)\n" - "group by a.key;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.Issues.Size() == 0); - - res = SqlToYql("select Unwrap(a.key) as key\n" - "from plato.Input as a\n" - "group by a.key;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.Issues.Size() == 0); - } - - Y_UNIT_TEST(NoWarnForAggregationBySelectAliasWhenTrivialRenamingIsUsed) { - NYql::TAstParseResult res = SqlToYql("select a.key as key\n" - "from plato.Input as a\n" - "group by key;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.Issues.Size() == 0); - - res = SqlToYql("select key as key\n" - "from plato.Input\n" - "group by key;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.Issues.Size() == 0); - } - - Y_UNIT_TEST(ErrorByAggregatingByExpressionWithSameExpressionInSelect) { - ExpectFailWithError("select k * 2 from plato.Input group by k * 2", - "<main>:1:8: Error: Column `k` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(ErrorForAggregationBySelectAlias) { - ExpectFailWithError("select key, Math::Floor(1.1 + a.subkey) as foo\n" - "from plato.Input as a\n" - "group by a.key, foo;", - "<main>:3:17: Warning: GROUP BY will aggregate by column `foo` instead of aggregating by SELECT expression with same alias, code: 4532\n" - "<main>:1:19: Warning: You should probably use alias in GROUP BY instead of using it here. Please consult documentation for more details, code: 4532\n" - "<main>:1:31: Error: Column `a.subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - - ExpectFailWithError("select c + 1 as c from plato.Input\n" - "group by Math::Floor(c + 2);", - "<main>:2:22: Warning: GROUP BY will aggregate by column `c` instead of aggregating by SELECT expression with same alias, code: 4532\n" - "<main>:1:10: Warning: You should probably use alias in GROUP BY instead of using it here. Please consult documentation for more details, code: 4532\n" - "<main>:1:8: Error: Column `c` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(ExplainQueryPlan) { - NYql::TAstParseResult res = SqlToYql("EXPLAIN Q U E R Y PLAN SELECT 1;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "<main>:1:8: Error: Unexpected token 'Q' : cannot match to any predicted input"); - } - - Y_UNIT_TEST(SelectWithDuplicateGroupingColumns) { - NYql::TAstParseResult res = SqlToYql("select c from plato.Input group by c, c"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Duplicate grouping column: c\n"); - } - - Y_UNIT_TEST(SelectWithBadAggregationInGrouping) { - NYql::TAstParseResult res = SqlToYql("select a, Min(b), c group by c"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:30: Error: Column reference 'c'\n"); - } - - Y_UNIT_TEST(SelectWithOpOnBadAggregation) { - ExpectFailWithError("select 1 + a + Min(b) from plato.Input", - "<main>:1:12: Error: Column `a` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(SelectOrderByConstantNum) { - NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by 1"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:36: Error: Unable to ORDER BY constant expression\n"); - } - - Y_UNIT_TEST(SelectOrderByConstantExpr) { - NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by 1 * 42"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:38: Error: Unable to ORDER BY constant expression\n"); - } - - Y_UNIT_TEST(SelectOrderByConstantString) { - NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by \"nest\""); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:36: Error: Unable to ORDER BY constant expression\n"); - } - - Y_UNIT_TEST(SelectOrderByAggregated) { - NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by min(a)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:36: Error: Unable to ORDER BY aggregated values\n"); - } - - Y_UNIT_TEST(ErrorInOrderByExpresison) { - NYql::TAstParseResult res = SqlToYql("select key, value from plato.Input order by (key as zey)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:45: Error: You should use in ORDER BY column name, qualified field, callable function or expression\n"); - } - - Y_UNIT_TEST(ErrorsInOrderByWhenColumnIsMissingInProjection) { - ExpectFailWithError("select subkey from (select 1 as subkey) order by key", "<main>:1:50: Error: Column key is not in source column set\n"); - ExpectFailWithError("select subkey from plato.Input as a order by x.key", "<main>:1:46: Error: Unknown correlation name: x\n"); - ExpectFailWithError("select distinct a, b from plato.Input order by c", "<main>:1:48: Error: Column c is not in source column set. Did you mean a?\n"); - ExpectFailWithError("select count(*) as a from plato.Input order by c", "<main>:1:48: Error: Column c is not in source column set. Did you mean a?\n"); - ExpectFailWithError("select count(*) as a, b, from plato.Input group by b order by c", "<main>:1:63: Error: Column c is not in source column set. Did you mean a?\n"); - UNIT_ASSERT(SqlToYql("select a, b from plato.Input order by c").IsOk()); - } - - Y_UNIT_TEST(SelectAggregatedWhere) { - NYql::TAstParseResult res = SqlToYql("select * from plato.Input where count(key)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:33: Error: Can not use aggregated values in filtering\n"); - } - - Y_UNIT_TEST(DoubleFrom) { - NYql::TAstParseResult res = SqlToYql("from plato.Input select * from plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:27: Error: Only one FROM clause is allowed\n"); - } - - Y_UNIT_TEST(SelectJoinMissingCorrName) { - NYql::TAstParseResult res = SqlToYql("select * from plato.Input1 as a join plato.Input2 as b on a.key == key"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:65: Error: JOIN: column requires correlation name\n"); - } - - Y_UNIT_TEST(SelectJoinMissingCorrName1) { - NYql::TAstParseResult res = SqlToYql( - "use plato;\n" - "$foo = select * from Input1;\n" - "select * from Input2 join $foo USING(key);\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:27: Error: JOIN: missing correlation name for source\n"); - } - - Y_UNIT_TEST(SelectJoinMissingCorrName2) { - NYql::TAstParseResult res = SqlToYql( - "use plato;\n" - "$foo = select * from Input1;\n" - "select * from Input2 cross join $foo;\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:33: Error: JOIN: missing correlation name for source\n"); - } - - Y_UNIT_TEST(SelectJoinEmptyCorrNames) { - NYql::TAstParseResult res = SqlToYql( - "$left = (SELECT * FROM plato.Input1 LIMIT 2);\n" - "$right = (SELECT * FROM plato.Input2 LIMIT 2);\n" - "SELECT * FROM $left FULL JOIN $right USING (key);\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:45: Error: At least one correlation name is required in join\n"); - } - - Y_UNIT_TEST(SelectJoinSameCorrNames) { - NYql::TAstParseResult res = SqlToYql("SELECT Input.key FROM plato.Input JOIN plato.Input1 ON Input.key == Input.subkey\n"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:66: Error: JOIN: different correlation names are required for joined tables\n"); - } - - Y_UNIT_TEST(SelectJoinConstPredicateArg) { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input1 as A JOIN plato.Input2 as B ON A.key == B.key AND A.subkey == \"wtf\"\n"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:87: Error: JOIN: each equality predicate argument must depend on exactly one JOIN input\n"); - } - - Y_UNIT_TEST(SelectJoinNonEqualityPredicate) { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input1 as A JOIN plato.Input2 as B ON A.key == B.key AND A.subkey > B.subkey\n"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:87: Error: JOIN ON expression must be a conjunction of equality predicates\n"); - } - - Y_UNIT_TEST(SelectEquiJoinCorrNameOutOfScope) { - NYql::TAstParseResult res = SqlToYql( - "PRAGMA equijoin;\n" - "SELECT * FROM plato.A JOIN plato.B ON A.key == C.key JOIN plato.C ON A.subkey == C.subkey;\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:45: Error: JOIN: can not use source: C in equality predicate, it is out of current join scope\n"); - } - - Y_UNIT_TEST(SelectEquiJoinNoRightSource) { - NYql::TAstParseResult res = SqlToYql( - "PRAGMA equijoin;\n" - "SELECT * FROM plato.A JOIN plato.B ON A.key == B.key JOIN plato.C ON A.subkey == B.subkey;\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:79: Error: JOIN ON equality predicate must have one of its arguments from the rightmost source\n"); - } - - Y_UNIT_TEST(SelectEquiJoinOuterWithoutType) { - NYql::TAstParseResult res = SqlToYql( - "SELECT * FROM plato.A Outer JOIN plato.B ON A.key == B.key;\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:23: Error: Invalid join type: OUTER JOIN. OUTER keyword is optional and can only be used after LEFT, RIGHT or FULL\n"); - } - - Y_UNIT_TEST(SelectEquiJoinOuterWithWrongType) { - NYql::TAstParseResult res = SqlToYql( - "SELECT * FROM plato.A LEFT semi OUTER JOIN plato.B ON A.key == B.key;\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:33: Error: Invalid join type: LEFT SEMI OUTER JOIN. OUTER keyword is optional and can only be used after LEFT, RIGHT or FULL\n"); - } - - Y_UNIT_TEST(InsertNoCluster) { - NYql::TAstParseResult res = SqlToYql("insert into Output (foo) values (1)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: No cluster name given and no default cluster is selected\n"); - } - - Y_UNIT_TEST(InsertValuesNoLabels) { - NYql::TAstParseResult res = SqlToYql("insert into plato.Output values (1)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: INSERT INTO ... VALUES requires specification of table columns\n"); - } - - Y_UNIT_TEST(UpsertValuesNoLabelsKikimr) { - NYql::TAstParseResult res = SqlToYql("upsert into plato.Output values (1)", 10, TString(NYql::KikimrProviderName)); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: UPSERT INTO ... VALUES requires specification of table columns\n"); - } - - Y_UNIT_TEST(ReplaceValuesNoLabelsKikimr) { - NYql::TAstParseResult res = SqlToYql("replace into plato.Output values (1)", 10, TString(NYql::KikimrProviderName)); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:20: Error: REPLACE INTO ... VALUES requires specification of table columns\n"); - } - - Y_UNIT_TEST(InsertValuesInvalidLabels) { - NYql::TAstParseResult res = SqlToYql("insert into plato.Output (foo) values (1, 2)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:27: Error: VALUES have 2 columns, INSERT INTO expects: 1\n"); - } - - Y_UNIT_TEST(BuiltinFileOpNoArgs) { - NYql::TAstParseResult res = SqlToYql("select FilePath()"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: FilePath() requires exactly 1 arguments, given: 0\n"); - } - - Y_UNIT_TEST(ProcessWithHaving) { - NYql::TAstParseResult res = SqlToYql("process plato.Input using some::udf(value) having value == 1"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:15: Error: PROCESS does not allow HAVING yet! You may request it on yql@ maillist.\n"); - } - - Y_UNIT_TEST(ReduceNoBy) { - NYql::TAstParseResult res = SqlToYql("reduce plato.Input using some::udf(value)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: Unexpected token absence : Missing ON \n\n<main>:1:25: Error: Unexpected token absence : Missing USING \n\n"); - } - - Y_UNIT_TEST(ReduceDistinct) { - NYql::TAstParseResult res = SqlToYql("reduce plato.Input on key using some::udf(distinct value)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:43: Error: DISTINCT can not be used in PROCESS/REDUCE\n"); - } - - Y_UNIT_TEST(CreateTableWithView) { - NYql::TAstParseResult res = SqlToYql("CREATE TABLE plato.foo:bar (key INT);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:22: Error: Unexpected token ':' : syntax error...\n\n"); - } - - Y_UNIT_TEST(AsteriskWithSomethingAfter) { - NYql::TAstParseResult res = SqlToYql("select *, LENGTH(value) from plato.Input;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Unable to use plain '*' with other projection items. Please use qualified asterisk instead: '<table>.*' (<table> can be either table name or table alias).\n"); - } - - Y_UNIT_TEST(AsteriskWithSomethingBefore) { - NYql::TAstParseResult res = SqlToYql("select LENGTH(value), * from plato.Input;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:23: Error: Unable to use plain '*' with other projection items. Please use qualified asterisk instead: '<table>.*' (<table> can be either table name or table alias).\n"); - } - - Y_UNIT_TEST(DuplicatedQualifiedAsterisk) { - NYql::TAstParseResult res = SqlToYql("select in.*, key, in.* from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: Unable to use twice same quialified asterisk. Invalid source: in\n"); - } - - Y_UNIT_TEST(BrokenLabel) { - NYql::TAstParseResult res = SqlToYql("select in.*, key as `funny.label` from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:14: Error: Unable to use '.' in column name. Invalid column name: funny.label\n"); - } - - Y_UNIT_TEST(KeyConflictDetect0) { - NYql::TAstParseResult res = SqlToYql("select key, in.key as key from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:13: Error: Unable to use duplicate column names. Collision in name: key\n"); - } - - Y_UNIT_TEST(KeyConflictDetect1) { - NYql::TAstParseResult res = SqlToYql("select length(key) as key, key from plato.Input;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:28: Error: Unable to use duplicate column names. Collision in name: key\n"); - } - - Y_UNIT_TEST(KeyConflictDetect2) { - NYql::TAstParseResult res = SqlToYql("select key, in.key from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); - } - - Y_UNIT_TEST(AutogenerationAliasWithCollisionConflict1) { - UNIT_ASSERT(SqlToYql("select LENGTH(Value), key as column0 from plato.Input;").IsOk()); - } - - Y_UNIT_TEST(AutogenerationAliasWithCollisionConflict2) { - UNIT_ASSERT(SqlToYql("select key as column1, LENGTH(Value) from plato.Input;").IsOk()); - } - - Y_UNIT_TEST(MissedSourceTableForQualifiedAsteriskOnSimpleSelect) { - NYql::TAstParseResult res = SqlToYql("use plato; select Intop.*, Input.key from plato.Input;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: Unknown correlation name: Intop\n"); - } - - Y_UNIT_TEST(MissedSourceTableForQualifiedAsteriskOnJoin) { - NYql::TAstParseResult res = SqlToYql("use plato; select tmissed.*, t2.*, t1.key from plato.Input as t1 join plato.Input as t2 on t1.key==t2.key;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: Unknown correlation name for asterisk: tmissed\n"); - } - - Y_UNIT_TEST(UnableToReferenceOnNotExistSubcolumn) { - NYql::TAstParseResult res = SqlToYql("select b.subkey from (select key from plato.Input as a) as b;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Column subkey is not in source column set\n"); - } - - Y_UNIT_TEST(ConflictOnSameNameWithQualify0) { - NYql::TAstParseResult res = SqlToYql("select in.key, in.key as key from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); - } - - Y_UNIT_TEST(ConflictOnSameNameWithQualify1) { - NYql::TAstParseResult res = SqlToYql("select in.key, length(key) as key from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); - } - - Y_UNIT_TEST(ConflictOnSameNameWithQualify2) { - NYql::TAstParseResult res = SqlToYql("select key, in.key from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); - } - - Y_UNIT_TEST(ConflictOnSameNameWithQualify3) { - NYql::TAstParseResult res = SqlToYql("select in.key, subkey as key from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); - } - - Y_UNIT_TEST(SelectFlattenBySameColumns) { - NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key, key as kk)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:46: Error: Duplicate column name found: key in FlattenBy section\n"); - } - - Y_UNIT_TEST(SelectFlattenBySameAliases) { - NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key as kk, subkey as kk);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Error: Duplicate alias found: kk in FlattenBy section\n"); - } - - Y_UNIT_TEST(SelectFlattenByExprSameAliases) { - NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key as kk, ListSkip(subkey,1) as kk);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Error: Collision between alias and column name: kk in FlattenBy section\n"); - } - - Y_UNIT_TEST(SelectFlattenByConflictNameAndAlias0) { - NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key, subkey as key);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:46: Error: Collision between alias and column name: key in FlattenBy section\n"); - } - - Y_UNIT_TEST(SelectFlattenByConflictNameAndAlias1) { - NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key as kk, subkey as key);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Error: Collision between alias and column name: key in FlattenBy section\n"); - } - - Y_UNIT_TEST(SelectFlattenByExprConflictNameAndAlias1) { - NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key as kk, ListSkip(subkey,1) as key);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Error: Duplicate column name found: key in FlattenBy section\n"); - } - - Y_UNIT_TEST(SelectFlattenByUnnamedExpr) { - NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key, ListSkip(key, 1))"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:46: Error: Unnamed expression after FLATTEN BY is not allowed\n"); - } - - Y_UNIT_TEST(UseInOnStrings) { - NYql::TAstParseResult res = SqlToYql("select * from plato.Input where \"foo\" in \"foovalue\";"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:42: Error: Unable to use IN predicate with string argument, it won't search substring - " - "expecting tuple, list, dict or single column table source\n"); - } - - Y_UNIT_TEST(UseSubqueryInScalarContextInsideIn) { - NYql::TAstParseResult res = SqlToYql("$q = (select key from plato.Input); select * from plato.Input where subkey in ($q);"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:79: Warning: Using subrequest in scalar context after IN, " - "perhaps you should remove parenthesis here, code: 4501\n"); - } - - Y_UNIT_TEST(InHintsWithKeywordClash) { - NYql::TAstParseResult res = SqlToYql("SELECT COMPACT FROM plato.Input WHERE COMPACT IN COMPACT `COMPACT`(1,2,3)"); - UNIT_ASSERT(!res.Root); - // should try to parse last compact as call expression - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:58: Error: Unknown builtin: COMPACT\n"); - } - - Y_UNIT_TEST(ErrorColumnPosition) { - NYql::TAstParseResult res = SqlToYql( - "USE plato;\n" - "SELECT \n" - "value FROM (\n" - "select key from Input\n" - ");\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:1: Error: Column value is not in source column set\n"); - } - - Y_UNIT_TEST(PrimaryViewAbortMapReduce) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input VIEW PRIMARY KEY"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: primary view is not supported for yt tables\n"); - } - - Y_UNIT_TEST(InsertAbortMapReduce) { - NYql::TAstParseResult res = SqlToYql("INSERT OR ABORT INTO plato.Output SELECT key FROM plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: INSERT OR ABORT INTO is not supported for yt tables\n"); - } - - Y_UNIT_TEST(ReplaceIntoMapReduce) { - NYql::TAstParseResult res = SqlToYql("REPLACE INTO plato.Output SELECT key FROM plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: Meaning of REPLACE INTO has been changed, now you should use INSERT INTO <table> WITH TRUNCATE ... for yt\n"); - } - - Y_UNIT_TEST(UpsertIntoMapReduce) { - NYql::TAstParseResult res = SqlToYql("UPSERT INTO plato.Output SELECT key FROM plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: UPSERT INTO is not supported for yt tables\n"); - } - - Y_UNIT_TEST(UpdateMapReduce) { - NYql::TAstParseResult res = SqlToYql("UPDATE plato.Output SET value = value + 1 WHERE key < 1"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: UPDATE is unsupported for yt\n"); - } - - Y_UNIT_TEST(DeleteMapReduce) { - NYql::TAstParseResult res = SqlToYql("DELETE FROM plato.Output WHERE key < 1"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: DELETE is unsupported for yt\n"); - } - - Y_UNIT_TEST(ReplaceIntoWithTruncate) { - NYql::TAstParseResult res = SqlToYql("REPLACE INTO plato.Output WITH TRUNCATE SELECT key FROM plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:32: Error: Unable REPLACE INTO with truncate mode\n"); - } - - Y_UNIT_TEST(UpsertIntoWithTruncate) { - NYql::TAstParseResult res = SqlToYql("UPSERT INTO plato.Output WITH TRUNCATE SELECT key FROM plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:31: Error: Unable UPSERT INTO with truncate mode\n"); - } - - Y_UNIT_TEST(InsertIntoWithTruncateKikimr) { - NYql::TAstParseResult res = SqlToYql("INSERT INTO plato.Output WITH TRUNCATE SELECT key FROM plato.Input", 10, TString(NYql::KikimrProviderName)); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: INSERT INTO WITH TRUNCATE is not supported for kikimr tables\n"); - } - - Y_UNIT_TEST(InsertIntoWithWrongArgumentCount) { - NYql::TAstParseResult res = SqlToYql("insert into plato.Output with truncate (key, value, subkey) values (5, '1', '2', '3');"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:53: Error: VALUES have 4 columns, INSERT INTO ... WITH TRUNCATE expects: 3\n"); - } - - Y_UNIT_TEST(UpsertWithWrongArgumentCount) { - NYql::TAstParseResult res = SqlToYql("upsert into plato.Output (key, value, subkey) values (2, '3');", 10, TString(NYql::KikimrProviderName)); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:39: Error: VALUES have 2 columns, UPSERT INTO expects: 3\n"); - } - - Y_UNIT_TEST(GroupingSetByExprWithoutAlias) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY GROUPING SETS (cast(key as uint32), subkey);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:53: Error: Unnamed expressions are not supported in GROUPING SETS. Please use '<expr> AS <name>'.\n"); - } - - Y_UNIT_TEST(GroupingSetByExprWithoutAlias2) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY subkey || subkey, GROUPING SETS (\n" - "cast(key as uint32), subkey);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:1: Error: Unnamed expressions are not supported in GROUPING SETS. Please use '<expr> AS <name>'.\n"); - } - - Y_UNIT_TEST(CubeByExprWithoutAlias) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY CUBE (key, subkey / key);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:56: Error: Unnamed expressions are not supported in CUBE. Please use '<expr> AS <name>'.\n"); - } - - Y_UNIT_TEST(RollupByExprWithoutAlias) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY ROLLUP (subkey / key);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:53: Error: Unnamed expressions are not supported in ROLLUP. Please use '<expr> AS <name>'.\n"); - } - - Y_UNIT_TEST(GroupByHugeCubeDeniedNoPragma) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY CUBE (key, subkey, value, key + subkey as sum, key - subkey as sub, key + val as keyval);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:119: Error: GROUP BY CUBE is allowed only for 5 columns, but you use 6\n"); - } - - Y_UNIT_TEST(GroupByInvalidPragma) { - NYql::TAstParseResult res = SqlToYql("PRAGMA GroupByCubeLimit = '-4';"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:27: Error: Expected unsigned integer literal as a single argument for: GroupByCubeLimit\n"); - } - - Y_UNIT_TEST(GroupByHugeCubeDeniedPragme) { - NYql::TAstParseResult res = SqlToYql("PRAGMA GroupByCubeLimit = '4'; SELECT key FROM plato.Input GROUP BY CUBE (key, subkey, value, key + subkey as sum, key - subkey as sub);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:132: Error: GROUP BY CUBE is allowed only for 4 columns, but you use 5\n"); - } - - Y_UNIT_TEST(GroupByFewBigCubes) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY CUBE(key, subkey, key + subkey as sum), CUBE(value, value + key + subkey as total);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Unable to GROUP BY more than 64 groups, you try use 80 groups\n"); - } - - Y_UNIT_TEST(GroupByFewBigCubesWithPragmaLimit) { - NYql::TAstParseResult res = SqlToYql("PRAGMA GroupByLimit = '16'; SELECT key FROM plato.Input GROUP BY GROUPING SETS(key, subkey, key + subkey as sum), ROLLUP(value, value + key + subkey as total);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:29: Error: Unable to GROUP BY more than 16 groups, you try use 18 groups\n"); - } - - Y_UNIT_TEST(NoGroupingColumn0) { - NYql::TAstParseResult res = SqlToYql( - "select count(1), key_first, val_first, grouping(key_first, val_first, nomind) as group\n" - "from plato.Input group by grouping sets (cast(key as uint32) /100 as key_first, Substring(value, 1, 1) as val_first);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:71: Error: Column 'nomind' is not a grouping column\n"); - } - - Y_UNIT_TEST(NoGroupingColumn1) { - NYql::TAstParseResult res = SqlToYql("select count(1), grouping(key, value) as group_duo from plato.Input group by cube (key, subkey);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:32: Error: Column 'value' is not a grouping column\n"); - } - - Y_UNIT_TEST(EmptyAccess0) { - NYql::TAstParseResult res = SqlToYql("insert into plato.Output (list0, list1) values (AsList(0, 1, 2), AsList(``));"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:73: Error: Column reference \"\" is not allowed in current scope\n"); - } - - Y_UNIT_TEST(EmptyAccess1) { - NYql::TAstParseResult res = SqlToYql("insert into plato.Output (list0, list1) values (AsList(0, 1, 2), ``);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:66: Error: Column reference \"\" is not allowed in current scope\n"); - } - - Y_UNIT_TEST(UseUnknownColumnInInsert) { - NYql::TAstParseResult res = SqlToYql("insert into plato.Output (list0, list1) values (AsList(0, 1, 2), AsList(`test`));"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:73: Error: Column reference \"test\" is not allowed in current scope\n"); - } - - Y_UNIT_TEST(GroupByEmptyColumn) { - NYql::TAstParseResult res = SqlToYql("select count(1) from plato.Input group by ``;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:43: Error: Column name can not be empty\n"); - } - - Y_UNIT_TEST(ConvertNumberOutOfBase) { - NYql::TAstParseResult res = SqlToYql("select 0o80l;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to parse number from string: 0o80l, char: '8' is out of base: 8\n"); - } - - Y_UNIT_TEST(ConvertNumberOutOfRangeForInt64ButFitsInUint64) { - NYql::TAstParseResult res = SqlToYql("select 0xc000000000000000l;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to parse 13835058055282163712 as integer literal of Int64 type: value out of range for Int64\n"); - } - - Y_UNIT_TEST(ConvertNumberOutOfRangeUint64) { - NYql::TAstParseResult res = SqlToYql("select 0xc0000000000000000l;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to parse number from string: 0xc0000000000000000l, number limit overflow\n"); - - res = SqlToYql("select 1234234543563435151456;\n"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to parse number from string: 1234234543563435151456, number limit overflow\n"); - } - - Y_UNIT_TEST(ConvertNumberNegativeOutOfRange) { - NYql::TAstParseResult res = SqlToYql("select -9223372036854775808;\n" - "select -9223372036854775809;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:8: Error: Failed to parse negative integer: -9223372036854775809, number limit overflow\n"); - } - - Y_UNIT_TEST(InvaildUsageReal0) { - NYql::TAstParseResult res = SqlToYql("select .0;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:7: Error: Unexpected token '.' : cannot match to any predicted input...\n\n"); - } - - Y_UNIT_TEST(InvaildUsageReal1) { - NYql::TAstParseResult res = SqlToYql("select .0f;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:7: Error: Unexpected token '.' : cannot match to any predicted input...\n\n"); - } - - Y_UNIT_TEST(InvaildUsageWinFunctionWithoutWindow) { - NYql::TAstParseResult res = SqlToYql("select lead(key, 2) from plato.Input;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to use window function Lead without window specification\n"); - } - - Y_UNIT_TEST(DropTableWithIfExists) { - NYql::TAstParseResult res = SqlToYql("DROP TABLE IF EXISTS plato.foo;"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("drop_if_exists")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(TooManyErrors) { - const char* q = R"( - USE plato; - select A, B, C, D, E, F, G, H, I, J, K, L, M, N from (select b from `abc`); -)"; - - NYql::TAstParseResult res = SqlToYql(q, 10); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - R"(<main>:3:16: Error: Column A is not in source column set. Did you mean b? -<main>:3:19: Error: Column B is not in source column set. Did you mean b? -<main>:3:22: Error: Column C is not in source column set. Did you mean b? -<main>:3:25: Error: Column D is not in source column set. Did you mean b? -<main>:3:28: Error: Column E is not in source column set. Did you mean b? -<main>:3:31: Error: Column F is not in source column set. Did you mean b? -<main>:3:34: Error: Column G is not in source column set. Did you mean b? -<main>:3:37: Error: Column H is not in source column set. Did you mean b? -<main>:3:40: Error: Column I is not in source column set. Did you mean b? -<main>: Error: Too many issues, code: 1 -)"); - }; - - Y_UNIT_TEST(ShouldCloneBindingForNamedParameter) { - NYql::TAstParseResult res = SqlToYql(R"($f = () -> { - $value_type = TypeOf(1); - $pair_type = StructType( - TypeOf("2") AS key, - $value_type AS value - ); - - RETURN TupleType( - ListType($value_type), - $pair_type); -}; - -select FormatType($f()); -)"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(BlockedInvalidFrameBounds) { - auto check = [](const TString& frame, const TString& err) { - const TString prefix = "SELECT SUM(x) OVER w FROM plato.Input WINDOW w AS (PARTITION BY key ORDER BY subkey\n"; - NYql::TAstParseResult res = SqlToYql(prefix + frame + ")"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), err); - }; - - check("ROWS UNBOUNDED FOLLOWING", "<main>:2:5: Error: Frame cannot start from UNBOUNDED FOLLOWING\n"); - check("ROWS BETWEEN 5 PRECEDING AND UNBOUNDED PRECEDING", "<main>:2:29: Error: Frame cannot end with UNBOUNDED PRECEDING\n"); - check("ROWS BETWEEN CURRENT ROW AND 5 PRECEDING", "<main>:2:13: Error: Frame cannot start from CURRENT ROW and end with PRECEDING\n"); - check("ROWS BETWEEN 5 FOLLOWING AND CURRENT ROW", "<main>:2:14: Error: Frame cannot start from FOLLOWING and end with CURRENT ROW\n"); - check("ROWS BETWEEN 5 FOLLOWING AND 5 PRECEDING", "<main>:2:14: Error: Frame cannot start from FOLLOWING and end with PRECEDING\n"); - } - - Y_UNIT_TEST(BlockedRangeValueWithoutSingleOrderBy) { - UNIT_ASSERT(SqlToYql("SELECT COUNT(*) OVER (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM plato.Input").IsOk()); - UNIT_ASSERT(SqlToYql("SELECT COUNT(*) OVER (RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) FROM plato.Input").IsOk()); - - auto res = SqlToYql("SELECT COUNT(*) OVER (RANGE 5 PRECEDING) FROM plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:29: Error: RANGE with <offset> PRECEDING/FOLLOWING requires exactly one expression in ORDER BY partition clause\n"); - - res = SqlToYql("SELECT COUNT(*) OVER (ORDER BY key, value RANGE 5 PRECEDING) FROM plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:49: Error: RANGE with <offset> PRECEDING/FOLLOWING requires exactly one expression in ORDER BY partition clause\n"); - } - - Y_UNIT_TEST(NoColumnsInFrameBounds) { - NYql::TAstParseResult res = SqlToYql( - "SELECT SUM(x) OVER w FROM plato.Input WINDOW w AS (ROWS BETWEEN\n" - " 1 + key PRECEDING AND 2 + key FOLLOWING);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:6: Error: Column reference \"key\" is not allowed in current scope\n"); - } - - Y_UNIT_TEST(WarnOnEmptyFrameBounds) { - NYql::TAstParseResult res = SqlToYql( - "SELECT SUM(x) OVER w FROM plato.Input WINDOW w AS (PARTITION BY key ORDER BY subkey\n" - "ROWS BETWEEN 10 FOLLOWING AND 5 FOLLOWING)"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:14: Warning: Used frame specification implies empty window frame, code: 4520\n"); - } - - Y_UNIT_TEST(WarnOnRankWithUnorderedWindow) { - NYql::TAstParseResult res = SqlToYql("SELECT RANK() OVER w FROM plato.Input WINDOW w AS ()"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Warning: Rank() is used with unordered window - all rows will be considered equal to each other, code: 4521\n"); - } - - Y_UNIT_TEST(WarnOnRankExprWithUnorderedWindow) { - NYql::TAstParseResult res = SqlToYql("SELECT RANK(key) OVER w FROM plato.Input WINDOW w AS ()"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Warning: Rank(<expression>) is used with unordered window - the result is likely to be undefined, code: 4521\n"); - } - - Y_UNIT_TEST(AnyAsTableName) { - NYql::TAstParseResult res = SqlToYql("use plato; select * from any;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:28: Error: Unexpected token ';' : syntax error...\n\n"); - } - - Y_UNIT_TEST(IncorrectOrderOfLambdaOptionalArgs) { - NYql::TAstParseResult res = SqlToYql("$f = ($x?, $y)->($x + $y); select $f(1);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Non-optional argument can not follow optional one\n"); - } - - Y_UNIT_TEST(IncorrectOrderOfActionOptionalArgs) { - NYql::TAstParseResult res = SqlToYql("define action $f($x?, $y) as select $x,$y; end define; do $f(1);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:23: Error: Non-optional argument can not follow optional one\n"); - } - - Y_UNIT_TEST(NotAllowedQuestionOnNamedNode) { - NYql::TAstParseResult res = SqlToYql("$f = 1; select $f?;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:18: Error: Unexpected token '?' at the end of expression\n"); - } - - Y_UNIT_TEST(AnyAndCrossJoin) { - NYql::TAstParseResult res = SqlToYql("use plato; select * from any Input1 cross join Input2"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:26: Error: ANY should not be used with Cross JOIN\n"); - - res = SqlToYql("use plato; select * from Input1 cross join any Input2"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:44: Error: ANY should not be used with Cross JOIN\n"); - } - - Y_UNIT_TEST(AnyWithCartesianProduct) { - NYql::TAstParseResult res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; select * from any Input1, Input2"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:56: Error: ANY should not be used with Cross JOIN\n"); - - res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; select * from Input1, any Input2"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:64: Error: ANY should not be used with Cross JOIN\n"); - } - - Y_UNIT_TEST(ErrorPlainEndAsInlineActionTerminator) { - NYql::TAstParseResult res = SqlToYql( - "do begin\n" - " select 1\n" - "; end\n"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:0: Error: Unexpected token absence : Missing DO \n\n"); - } - - Y_UNIT_TEST(ErrorMultiWayJoinWithUsing) { - NYql::TAstParseResult res = SqlToYql( - "USE plato;\n" - "PRAGMA DisableSimpleColumns;\n" - "SELECT *\n" - "FROM Input1 AS a\n" - "JOIN Input2 AS b USING(key)\n" - "JOIN Input3 AS c ON a.key = c.key;\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - "<main>:5:24: Error: Multi-way JOINs should be connected with ON clause instead of USING clause\n" - ); - } - - Y_UNIT_TEST(RequireLabelInFlattenByWithDot) { - NYql::TAstParseResult res = SqlToYql("select * from plato.Input flatten by x.y"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - "<main>:1:40: Error: Unnamed expression after FLATTEN BY is not allowed\n" - ); - } - - Y_UNIT_TEST(WarnUnnamedColumns) { - NYql::TAstParseResult res = SqlToYql( - "PRAGMA WarnUnnamedColumns;\n" - "\n" - "SELECT key, subkey, key || subkey FROM plato.Input ORDER BY subkey;\n"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:28: Warning: Autogenerated column name column2 will be used for expression, code: 4516\n"); - } - - Y_UNIT_TEST(WarnSourceColumnMismatch) { - NYql::TAstParseResult res = SqlToYql( - "insert into plato.Output (key, subkey, new_value, one_more_value) select key as Key, subkey, value, \"x\" from plato.Input;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:51: Warning: Column names in SELECT don't match column specification in parenthesis. \"key\" doesn't match \"Key\". \"new_value\" doesn't match \"value\", code: 4517\n"); - } - - Y_UNIT_TEST(YtCaseInsensitive) { - NYql::TAstParseResult res = SqlToYql("select * from PlatO.foo;"); - UNIT_ASSERT(res.Root); - - res = SqlToYql("use PlatO; select * from foo;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(KikimrCaseSensitive) { - NYql::TAstParseResult res = SqlToYql("select * from PlatO.foo;", 10, "kikimr"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:15: Error: Unknown cluster: PlatO\n"); - - res = SqlToYql("use PlatO; select * from foo;", 10, "kikimr"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:5: Error: Unknown cluster: PlatO\n"); - } - - Y_UNIT_TEST(DiscoveryModeForbidden) { - NYql::TAstParseResult res = SqlToYqlWithMode("insert into plato.Output select * from plato.range(\"\", Input1, Input4)", NSQLTranslation::ESqlMode::DISCOVERY); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: range is not allowed in Discovery mode, code: 4600\n"); - - res = SqlToYqlWithMode("insert into plato.Output select * from plato.like(\"\", \"Input%\")", NSQLTranslation::ESqlMode::DISCOVERY); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: like is not allowed in Discovery mode, code: 4600\n"); - - res = SqlToYqlWithMode("insert into plato.Output select * from plato.regexp(\"\", \"Input.\")", NSQLTranslation::ESqlMode::DISCOVERY); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: regexp is not allowed in Discovery mode, code: 4600\n"); - - res = SqlToYqlWithMode("insert into plato.Output select * from plato.filter(\"\", ($name) -> { return find($name, \"Input\") is not null; })", NSQLTranslation::ESqlMode::DISCOVERY); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: filter is not allowed in Discovery mode, code: 4600\n"); - - res = SqlToYqlWithMode("select Path from plato.folder(\"\") where Type == \"table\"", NSQLTranslation::ESqlMode::DISCOVERY); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:18: Error: folder is not allowed in Discovery mode, code: 4600\n"); - } - - Y_UNIT_TEST(YsonFuncWithoutArgs) { - UNIT_ASSERT(SqlToYql("SELECT Yson::SerializeText(Yson::From());").IsOk()); - } - - Y_UNIT_TEST(CanNotUseOrderByInNonLastSelectInUnionAllChain) { - auto req = "pragma AnsiOrderByLimitInUnionAll;\n" - "use plato;\n" - "\n" - "select * from Input order by key\n" - "union all\n" - "select * from Input order by key limit 1;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:21: Error: ORDER BY within UNION ALL is only allowed after last subquery\n"); - } - - Y_UNIT_TEST(CanNotUseLimitInNonLastSelectInUnionAllChain) { - auto req = "pragma AnsiOrderByLimitInUnionAll;\n" - "use plato;\n" - "\n" - "select * from Input limit 1\n" - "union all\n" - "select * from Input order by key limit 1;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:21: Error: LIMIT within UNION ALL is only allowed after last subquery\n"); - } - - Y_UNIT_TEST(CanNotUseDiscardInNonFirstSelectInUnionAllChain) { - auto req = "pragma AnsiOrderByLimitInUnionAll;\n" - "use plato;\n" - "\n" - "select * from Input\n" - "union all\n" - "discard select * from Input;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:6:1: Error: DISCARD within UNION ALL is only allowed before first subquery\n"); - } - - Y_UNIT_TEST(CanNotUseIntoResultInNonLastSelectInUnionAllChain) { - auto req = "use plato;\n" - "pragma AnsiOrderByLimitInUnionAll;\n" - "\n" - "select * from Input\n" - "union all\n" - "discard select * from Input;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:6:1: Error: DISCARD within UNION ALL is only allowed before first subquery\n"); - } - - Y_UNIT_TEST(YsonStrictInvalidPragma) { - auto res = SqlToYql("pragma yson.Strict = \"wrong\";"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:22: Error: Expected 'true', 'false' or no parameter for: Strict\n"); - } - - Y_UNIT_TEST(WarnTableNameInSomeContexts) { - UNIT_ASSERT(SqlToYql("use plato; select TableName() from Input;").IsOk()); - UNIT_ASSERT(SqlToYql("use plato; select TableName(\"aaaa\");").IsOk()); - UNIT_ASSERT(SqlToYql("select TableName(\"aaaa\", \"yt\");").IsOk()); - - auto res = SqlToYql("select TableName() from plato.Input;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: TableName requires either service name as second argument or current cluster name\n"); - - res = SqlToYql("use plato;\n" - "select TableName() from Input1 as a join Input2 as b using(key);"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:8: Warning: TableName() may produce empty result when used in ambiguous context (with JOIN), code: 4525\n"); - - res = SqlToYql("use plato;\n" - "select SOME(TableName()), key from Input group by key;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:13: Warning: TableName() will produce empty result when used with aggregation.\n" - "Please consult documentation for possible workaround, code: 4525\n"); - } - - Y_UNIT_TEST(WarnOnDistincWithHavingWithoutAggregations) { - auto res = SqlToYql("select distinct key from plato.Input having key != '0';"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:49: Warning: The usage of HAVING without aggregations with SELECT DISTINCT is non-standard and will stop working soon. Please use WHERE instead., code: 4526\n"); - } - - Y_UNIT_TEST(FlattenByExprWithNestedNull) { - auto res = SqlToYql("USE plato;\n" - "\n" - "SELECT * FROM (SELECT 1 AS region_id)\n" - "FLATTEN BY (\n" - " CAST($unknown(region_id) AS List<String>) AS region\n" - ")"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:10: Error: Unknown name: $unknown\n"); - } - - Y_UNIT_TEST(EmptySymbolNameIsForbidden) { - auto req = " $`` = 1; select $``;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:5: Error: Empty symbol name is not allowed\n"); - } - - Y_UNIT_TEST(WarnOnBinaryOpWithNullArg) { - auto req = "select * from plato.Input where cast(key as Int32) != NULL"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Warning: Binary operation != will return NULL here, code: 4529\n"); - - req = "select 1 or null"; - res = SqlToYql(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), ""); - } - - Y_UNIT_TEST(ErrorIfTableSampleArgUsesColumns) { - auto req = "SELECT key FROM plato.Input TABLESAMPLE BERNOULLI(MIN_OF(100.0, CAST(subkey as Int32)));"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:70: Error: Column reference \"subkey\" is not allowed in current scope\n"); - } - - Y_UNIT_TEST(DerivedColumnListForSelectIsNotSupportedYet) { - auto req = "SELECT a,b,c FROM plato.Input as t(x,y,z);"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:35: Error: Derived column list is only supported for VALUES\n"); - } - - Y_UNIT_TEST(ErrorIfValuesHasDifferentCountOfColumns) { - auto req = "VALUES (1,2,3), (4,5);"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: All VALUES items should have same size: expecting 3, got 2\n"); - } - - Y_UNIT_TEST(ErrorIfDerivedColumnSizeExceedValuesColumnCount) { - auto req = "SELECT * FROM(VALUES (1,2), (3,4)) as t(x,y,z);"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: Derived column list size exceeds column count in VALUES\n"); - } - - Y_UNIT_TEST(WarnoOnAutogeneratedNamesForValues) { - auto req = "PRAGMA WarnUnnamedColumns;\n" - "SELECT * FROM (VALUES (1,2,3,4), (5,6,7,8)) as t(x,y);"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:16: Warning: Autogenerated column names column2...column3 will be used here, code: 4516\n"); - } - - Y_UNIT_TEST(ErrUnionAllWithOrderByWithoutExplicitLegacyMode) { - auto req = "use plato;\n" - "\n" - "select * from Input order by key\n" - "union all\n" - "select * from Input order by key;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:21: Error: ORDER BY within UNION ALL is only allowed after last subquery\n"); - } - - Y_UNIT_TEST(ErrUnionAllWithLimitWithoutExplicitLegacyMode) { - auto req = "use plato;\n" - "\n" - "select * from Input limit 10\n" - "union all\n" - "select * from Input limit 1;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:21: Error: LIMIT within UNION ALL is only allowed after last subquery\n"); - } - - Y_UNIT_TEST(ErrUnionAllWithIntoResultWithoutExplicitLegacyMode) { - auto req = "use plato;\n" - "\n" - "select * from Input into result aaa\n" - "union all\n" - "select * from Input;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:21: Error: INTO RESULT within UNION ALL is only allowed after last subquery\n"); - } - - Y_UNIT_TEST(ErrUnionAllWithDiscardWithoutExplicitLegacyMode) { - auto req = "use plato;\n" - "\n" - "select * from Input\n" - "union all\n" - "discard select * from Input;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:1: Error: DISCARD within UNION ALL is only allowed before first subquery\n"); - } - - Y_UNIT_TEST(ErrUnionAllKeepsIgnoredOrderByWarning) { - auto req = "use plato;\n" - "\n" - "SELECT * FROM (\n" - " SELECT * FROM Input\n" - " UNION ALL\n" - " SELECT t.* FROM Input AS t ORDER BY t.key\n" - ");"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:3: Warning: ORDER BY without LIMIT in subquery will be ignored, code: 4504\n" - "<main>:6:39: Error: Unknown correlation name: t\n"); - } - - Y_UNIT_TEST(ErrOrderByIgnoredButCheckedForMissingColumns) { - auto req = "$src = SELECT key FROM (SELECT 1 as key, 2 as subkey) ORDER BY x; SELECT * FROM $src;"; - ExpectFailWithError(req, "<main>:1:8: Warning: ORDER BY without LIMIT in subquery will be ignored, code: 4504\n" - "<main>:1:64: Error: Column x is not in source column set\n"); - - req = "$src = SELECT key FROM plato.Input ORDER BY x; SELECT * FROM $src;"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Warning: ORDER BY without LIMIT in subquery will be ignored, code: 4504\n"); - } - - Y_UNIT_TEST(InvalidTtlInterval) { - auto req = R"( - USE plato; - CREATE TABLE tableName (Key Uint32, CreatedAt Timestamp, PRIMARY KEY (Key)) - WITH (TTL = 1 On CreatedAt); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:25: Error: Literal of Interval type is expected for TTL\n" - "<main>:4:25: Error: Invalid TTL settings\n"); - } - - Y_UNIT_TEST(InvalidTtlUnit) { - auto req = R"( - USE plato; - CREATE TABLE tableName (Key Uint32, CreatedAt Uint32, PRIMARY KEY (Key)) - WITH (TTL = Interval("P1D") On CreatedAt AS PICOSECONDS); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "<main>:4:56: Error: Unexpected token 'PICOSECONDS'"); - } - - Y_UNIT_TEST(InvalidChangefeedSink) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (SINK_TYPE = "S3", MODE = "KEYS_ONLY", FORMAT = "json") - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:55: Error: Unknown changefeed sink type: S3\n"); - } - - Y_UNIT_TEST(InvalidChangefeedSettings) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (SINK_TYPE = "local", FOO = "bar") - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:64: Error: Unknown changefeed setting: FOO\n"); - } - - Y_UNIT_TEST(InvalidChangefeedInitialScan) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", INITIAL_SCAN = "foo") - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:95: Error: Literal of Bool type is expected for INITIAL_SCAN\n"); - } - - Y_UNIT_TEST(InvalidChangefeedVirtualTimestamps) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", VIRTUAL_TIMESTAMPS = "foo") - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:101: Error: Literal of Bool type is expected for VIRTUAL_TIMESTAMPS\n"); - } - - Y_UNIT_TEST(InvalidChangefeedResolvedTimestamps) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", BARRIERS_INTERVAL = "foo") - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:100: Error: Literal of Interval type is expected for BARRIERS_INTERVAL\n"); - } - - Y_UNIT_TEST(InvalidChangefeedRetentionPeriod) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", RETENTION_PERIOD = "foo") - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:99: Error: Literal of Interval type is expected for RETENTION_PERIOD\n"); - } - - Y_UNIT_TEST(InvalidChangefeedTopicPartitions) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", TOPIC_MIN_ACTIVE_PARTITIONS = "foo") - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:110: Error: Literal of integer type is expected for TOPIC_MIN_ACTIVE_PARTITIONS\n"); - } - - Y_UNIT_TEST(InvalidChangefeedAwsRegion) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", AWS_REGION = true) - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:93: Error: Literal of String type is expected for AWS_REGION\n"); - } - - Y_UNIT_TEST(ErrJoinWithGroupingSetsWithoutCorrelationName) { - auto req = "USE plato;\n" - "\n" - "SELECT k1, k2, subkey\n" - "FROM T1 AS a JOIN T2 AS b USING (key)\n" - "GROUP BY GROUPING SETS(\n" - " (a.key as k1, b.subkey as k2),\n" - " (k1),\n" - " (subkey)\n" - ");"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:8:4: Error: Columns in grouping sets should have correlation name, error in key: subkey\n"); - } - - Y_UNIT_TEST(ErrJoinWithGroupByWithoutCorrelationName) { - auto req = "USE plato;\n" - "\n" - "SELECT k1, k2,\n" - " value\n" - "FROM T1 AS a JOIN T2 AS b USING (key)\n" - "GROUP BY a.key as k1, b.subkey as k2,\n" - " value;"; - ExpectFailWithError(req, - "<main>:7:5: Error: Columns in GROUP BY should have correlation name, error in key: value\n"); - } - - Y_UNIT_TEST(ErrWithMissingFrom) { - auto req = "select 1 as key where 1 > 1;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:25: Error: Filtering is not allowed without FROM\n"); - - req = "select 1 + count(*);"; - res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Aggregation is not allowed without FROM\n"); - - req = "select 1 as key, subkey + value;"; - res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:18: Error: Column reference 'subkey'\n" - "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:27: Error: Column reference 'value'\n"); - - req = "select count(1) group by key;"; - res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:26: Error: Column reference 'key'\n"); - } - - Y_UNIT_TEST(ErrWithMissingFromForWindow) { - auto req = "$c = () -> (1 + count(1) over w);\n" - "select $c();"; - ExpectFailWithError(req, - "<main>:1:9: Error: Window and aggregation functions are not allowed in this context\n" - "<main>:1:17: Error: Failed to use aggregation function Count without window specification or in wrong place\n"); - - req = "$c = () -> (1 + lead(1) over w);\n" - "select $c();"; - ExpectFailWithError(req, - "<main>:1:17: Error: Window functions are not allowed in this context\n" - "<main>:1:17: Error: Failed to use window function Lead without window specification or in wrong place\n"); - - req = "select 1 + count(1) over w window w as ();"; - ExpectFailWithError(req, - "<main>:1:1: Error: Window and aggregation functions are not allowed without FROM\n" - "<main>:1:12: Error: Failed to use aggregation function Count without window specification or in wrong place\n"); - - req = "select 1 + lead(1) over w window w as ();"; - ExpectFailWithError(req, - "<main>:1:12: Error: Window functions are not allowed without FROM\n" - "<main>:1:12: Error: Failed to use window function Lead without window specification or in wrong place\n"); - } - - Y_UNIT_TEST(ErrWithMissingFromForInplaceWindow) { - auto req = "$c = () -> (1 + count(1) over ());\n" - "select $c();"; - ExpectFailWithError(req, - "<main>:1:26: Error: Window and aggregation functions are not allowed in this context\n"); - - req = "$c = () -> (1 + lead(1) over (rows between unbounded preceding and current row));\n" - "select $c();"; - ExpectFailWithError(req, - "<main>:1:25: Error: Window and aggregation functions are not allowed in this context\n"); - - req = "select 1 + count(1) over ();"; - ExpectFailWithError(req, - "<main>:1:1: Error: Window and aggregation functions are not allowed without FROM\n" - "<main>:1:12: Error: Failed to use aggregation function Count without window specification or in wrong place\n"); - - req = "select 1 + lead(1) over (rows between current row and unbounded following);"; - ExpectFailWithError(req, - "<main>:1:12: Error: Window functions are not allowed without FROM\n" - "<main>:1:12: Error: Failed to use window function Lead without window specification or in wrong place\n"); - } - - Y_UNIT_TEST(ErrDistinctInWrongPlace) { - auto req = "select Some::Udf(distinct key) from plato.Input;"; - ExpectFailWithError(req, - "<main>:1:18: Error: DISTINCT can only be used in aggregation functions\n"); - req = "select sum(key)(distinct foo) from plato.Input;"; - ExpectFailWithError(req, - "<main>:1:17: Error: DISTINCT can only be used in aggregation functions\n"); - - req = "select len(distinct foo) from plato.Input;"; - ExpectFailWithError(req, - "<main>:1:8: Error: DISTINCT can only be used in aggregation functions\n"); - - req = "$foo = ($x) -> ($x); select $foo(distinct key) from plato.Input;"; - ExpectFailWithError(req, - "<main>:1:34: Error: DISTINCT can only be used in aggregation functions\n"); - } - - Y_UNIT_TEST(ErrForNotSingleChildInInlineAST) { - ExpectFailWithError("select YQL::\"\"", - "<main>:1:8: Error: Failed to parse YQL: expecting AST root node with single child, but got 0\n"); - ExpectFailWithError("select YQL::@@ \t@@", - "<main>:1:8: Error: Failed to parse YQL: expecting AST root node with single child, but got 0\n"); - auto req = "$lambda = YQL::@@(lambda '(x)(+ x x)) (lambda '(y)(+ y y))@@;\n" - "select ListMap([1, 2, 3], $lambda);"; - ExpectFailWithError(req, - "<main>:1:11: Error: Failed to parse YQL: expecting AST root node with single child, but got 2\n"); - } - - Y_UNIT_TEST(ErrEmptyColumnName) { - ExpectFailWithError("select * without \"\" from plato.Input", - "<main>:1:18: Error: String literal can not be used here\n"); - - ExpectFailWithError("select * without `` from plato.Input;", - "<main>:1:18: Error: Empty column name is not allowed\n"); - - ExpectFailWithErrorForAnsiLexer("select * without \"\" from plato.Input", - "<main>:1:18: Error: Empty column name is not allowed\n"); - - ExpectFailWithErrorForAnsiLexer("select * without `` from plato.Input;", - "<main>:1:18: Error: Empty column name is not allowed\n"); - } - - Y_UNIT_TEST(ErrOnNonZeroArgumentsForTableRows) { - ExpectFailWithError("$udf=\"\";process plato.Input using $udf(TableRows(k))", - "<main>:1:40: Error: TableRows requires exactly 0 arguments\n"); - } - - Y_UNIT_TEST(ErrGroupByWithAggregationFunctionAndDistinctExpr) { - ExpectFailWithError("select * from plato.Input group by count(distinct key|key)", - "<main>:1:36: Error: Unable to GROUP BY aggregated values\n"); - } - - // FIXME: check if we can get old behaviour -#if 0 - Y_UNIT_TEST(ErrWithSchemaWithColumnsWithoutType) { - ExpectFailWithError("select * from plato.Input with COLUMNs", - "<main>:1:32: Error: Expected type after COLUMNS\n" - "<main>:1:32: Error: Failed to parse table hints\n"); - - ExpectFailWithError("select * from plato.Input with scheMa", - "<main>:1:32: Error: Expected type after SCHEMA\n" - "<main>:1:32: Error: Failed to parse table hints\n"); - } -#endif - - Y_UNIT_TEST(ErrCollectPreaggregatedInListLiteralWithoutFrom) { - ExpectFailWithError("SELECT([VARIANCE(DISTINCT[])])", - "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:9: Error: Column reference '_yql_preagg_Variance0'\n"); - } - - Y_UNIT_TEST(ErrGroupBySmartParenAsTuple) { - ExpectFailWithError("SELECT * FROM plato.Input GROUP BY (k, v,)", - "<main>:1:41: Error: Unexpected trailing comma in grouping elements list\n"); - } - - Y_UNIT_TEST(HandleNestedSmartParensInGroupBy) { - ExpectFailWithError("SELECT * FROM plato.Input GROUP BY (+() as k)", - "<main>:1:37: Error: Unable to GROUP BY constant expression\n"); - } - - Y_UNIT_TEST(ErrRenameWithAddColumn) { - ExpectFailWithError("USE plato; ALTER TABLE table RENAME TO moved, ADD COLUMN addc uint64", - "<main>:1:40: Error: RENAME TO can not be used together with another table action\n"); - } - - Y_UNIT_TEST(ErrAddColumnAndRename) { - // FIXME: fix positions in ALTER TABLE - ExpectFailWithError("USE plato; ALTER TABLE table ADD COLUMN addc uint64, RENAME TO moved", - "<main>:1:46: Error: RENAME TO can not be used together with another table action\n"); - } - - Y_UNIT_TEST(InvalidUuidValue) { - ExpectFailWithError("SELECT Uuid('123e4567ae89ba12d3aa456a426614174ab0')", - "<main>:1:8: Error: Invalid value \"123e4567ae89ba12d3aa456a426614174ab0\" for type Uuid\n"); - ExpectFailWithError("SELECT Uuid('123e4567ae89b-12d3-a456-426614174000')", - "<main>:1:8: Error: Invalid value \"123e4567ae89b-12d3-a456-426614174000\" for type Uuid\n"); - } - - Y_UNIT_TEST(WindowFunctionWithoutOver) { - ExpectFailWithError("SELECT LAST_VALUE(foo) FROM plato.Input", - "<main>:1:8: Error: Can't use window function LastValue without window specification (OVER keyword is missing)\n"); - ExpectFailWithError("SELECT LAST_VALUE(foo) FROM plato.Input GROUP BY key", - "<main>:1:8: Error: Can't use window function LastValue without window specification (OVER keyword is missing)\n"); - } - - Y_UNIT_TEST(CreateAlterUserWithLoginNoLogin) { - auto reqCreateUser = SqlToYql(R"( - USE plato; - CREATE USER user1; - )"); - - UNIT_ASSERT(reqCreateUser.IsOk()); - - auto reqAlterUser = SqlToYql(R"( - USE plato; - ALTER USER user1; - )"); - - UNIT_ASSERT(!reqAlterUser.IsOk()); - UNIT_ASSERT_STRING_CONTAINS(reqAlterUser.Issues.ToString(), "Error: Unexpected token ';' : cannot match to any predicted input..."); - - auto reqPasswordAndLogin = SqlToYql(R"( - USE plato; - CREATE USER user1 PASSWORD '123' LOGIN; - )"); - - UNIT_ASSERT(reqPasswordAndLogin.IsOk()); - - auto reqPasswordAndNoLogin = SqlToYql(R"( - USE plato; - CREATE USER user1 PASSWORD '123' NOLOGIN; - )"); - - UNIT_ASSERT(reqPasswordAndNoLogin.IsOk()); - - auto reqLogin = SqlToYql(R"( - USE plato; - CREATE USER user1 LOGIN; - )"); - - UNIT_ASSERT(reqLogin.IsOk()); - - auto reqNoLogin = SqlToYql(R"( - USE plato; - CREATE USER user1 NOLOGIN; - )"); - - UNIT_ASSERT(reqNoLogin.IsOk()); - - auto reqLoginNoLogin = SqlToYql(R"( - USE plato; - CREATE USER user1 LOGIN NOLOGIN; - )"); - - UNIT_ASSERT(!reqLoginNoLogin.IsOk()); - UNIT_ASSERT_STRING_CONTAINS(reqLoginNoLogin.Issues.ToString(), "Error: Conflicting or redundant options"); - - auto reqAlterLoginNoLogin = SqlToYql(R"( - USE plato; - CREATE USER user1 LOGIN; - ALTER USER user1 NOLOGIN; - )"); - - UNIT_ASSERT(reqAlterLoginNoLogin.IsOk()); - - auto reqAlterLoginNoLoginWithPassword = SqlToYql(R"( - USE plato; - CREATE USER user1 LOGIN; - ALTER USER user1 PASSWORD '321' NOLOGIN; - )"); - - UNIT_ASSERT(reqAlterLoginNoLoginWithPassword.IsOk()); - } - - Y_UNIT_TEST(CreateUserWithHash) { - auto reqCreateUser = SqlToYql(R"( - USE plato; - CREATE USER user1 HASH '{ - "hash": "p4ffeMugohqyBwyckYCK1TjJfz3LIHbKiGL+t+oEhzw=", - "salt": "U+tzBtgo06EBQCjlARA6Jg==", - "type": "argon2id" - }'; - )"); - - UNIT_ASSERT(reqCreateUser.IsOk()); - - auto reqCreateUserWithNoLogin = SqlToYql(R"( - USE plato; - CREATE USER user1 HASH '{ - "hash": "p4ffeMugohqyBwyckYCK1TjJfz3LIHbKiGL+t+oEhzw=", - "salt": "U+tzBtgo06EBQCjlARA6Jg==", - "type": "argon2id" - }' - NOLOGIN; - )"); - - UNIT_ASSERT(reqCreateUserWithNoLogin.IsOk()); - - auto reqCreateUserWithPassword = SqlToYql(R"( - USE plato; - CREATE USER user1 HASH '{ - "hash": "p4ffeMugohqyBwyckYCK1TjJfz3LIHbKiGL+t+oEhzw=", - "salt": "U+tzBtgo06EBQCjlARA6Jg==", - "type": "argon2id" - }' - PASSWORD '123'; - )"); - - UNIT_ASSERT(!reqCreateUserWithPassword.IsOk()); - UNIT_ASSERT_STRING_CONTAINS(reqCreateUserWithPassword.Issues.ToString(), "Error: Conflicting or redundant options"); - - auto reqAlterUser = SqlToYql(R"( - USE plato; - CREATE USER user1; - ALTER USER user1 HASH '{ - "hash": "p4ffeMugohqyBwyckYCK1TjJfz3LIHbKiGL+t+oEhzw=", - "salt": "U+tzBtgo06EBQCjlARA6Jg==", - "type": "argon2id" - }'; - )"); - - UNIT_ASSERT(reqAlterUser.IsOk()); - } - - Y_UNIT_TEST(CreateAlterUserWithoutCluster) { - ExpectFailWithError("\n CREATE USER user ENCRYPTED PASSWORD 'foobar';", "<main>:2:2: Error: USE statement is missing - no default cluster is selected\n"); - ExpectFailWithError("ALTER USER CURRENT_USER RENAME TO $foo;", "<main>:1:1: Error: USE statement is missing - no default cluster is selected\n"); - } - - Y_UNIT_TEST(ModifyPermissionsWithoutCluster) { - ExpectFailWithError("\n GRANT CONNECT ON `/Root` TO user;", "<main>:2:2: Error: USE statement is missing - no default cluster is selected\n"); - ExpectFailWithError("\n REVOKE MANAGE ON `/Root` FROM user;", "<main>:2:2: Error: USE statement is missing - no default cluster is selected\n"); - } - - Y_UNIT_TEST(ReservedRoleNames) { - ExpectFailWithError("USE plato; CREATE USER current_User;", "<main>:1:24: Error: System role CURRENT_USER can not be used here\n"); - ExpectFailWithError("USE plato; ALTER USER current_User RENAME TO Current_role", "<main>:1:46: Error: System role CURRENT_ROLE can not be used here\n"); - UNIT_ASSERT(SqlToYql("USE plato; DROP GROUP IF EXISTS a, b, c, current_User;").IsOk()); - } - - Y_UNIT_TEST(DisableClassicDivisionWithError) { - ExpectFailWithError("pragma ClassicDivision = 'false'; select $foo / 30;", "<main>:1:42: Error: Unknown name: $foo\n"); - } - - Y_UNIT_TEST(AggregationOfAgrregatedDistinctExpr) { - ExpectFailWithError("select sum(sum(distinct x + 1)) from plato.Input", "<main>:1:12: Error: Aggregation of aggregated values is forbidden\n"); - } - - Y_UNIT_TEST(WarnForUnusedSqlHint) { - NYql::TAstParseResult res = SqlToYql("select * from plato.Input1 as a join /*+ merge() */ plato.Input2 as b using(key);\n" - "select --+ foo(bar)\n" - " 1;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:23: Warning: Hint foo will not be used, code: 4534\n"); - } - - Y_UNIT_TEST(WarnForDeprecatedSchema) { - NSQLTranslation::TTranslationSettings settings; - settings.ClusterMapping["s3bucket"] = NYql::S3ProviderName; - NYql::TAstParseResult res = SqlToYqlWithSettings("select * from s3bucket.`foo` with schema (col1 Int32, String as col2, Int64 as col3);", settings); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "Warning: Deprecated syntax for positional schema: please use 'column type' instead of 'type AS column', code: 4535\n"); - } - - Y_UNIT_TEST(ErrorOnColumnNameInMaxByLimit) { - ExpectFailWithError( - "SELECT AGGREGATE_BY(AsTuple(value, key), AggregationFactory(\"MAX_BY\", subkey)) FROM plato.Input;", - "<main>:1:42: Error: Source does not allow column references\n" - "<main>:1:71: Error: Column reference 'subkey'\n"); - } - - Y_UNIT_TEST(ErrorInLibraryWithTopLevelNamedSubquery) { - TString withUnusedSubq = "$unused = select max(key) from plato.Input;\n" - "\n" - "define subquery $foo() as\n" - " $count = select count(*) from plato.Input;\n" - " select * from plato.Input limit $count / 2;\n" - "end define;\n" - "export $foo;\n"; - UNIT_ASSERT(SqlToYqlWithMode(withUnusedSubq, NSQLTranslation::ESqlMode::LIBRARY).IsOk()); - - TString withTopLevelSubq = "$count = select count(*) from plato.Input;\n" - "\n" - "define subquery $foo() as\n" - " select * from plato.Input limit $count / 2;\n" - "end define;\n" - "export $foo;\n"; - auto res = SqlToYqlWithMode(withTopLevelSubq, NSQLTranslation::ESqlMode::LIBRARY); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: Named subquery can not be used as a top level statement in libraries\n"); - } - - Y_UNIT_TEST(SessionStartAndSessionStateShouldSurviveSessionWindowArgsError){ - TString query = R"( - $init = ($_row) -> (min(1, 2)); -- error: aggregation func min() can not be used here - $calculate = ($_row, $_state) -> (1); - $update = ($_row, $_state) -> (2); - SELECT - SessionStart() over w as session_start, - SessionState() over w as session_state, - FROM plato.Input as t - WINDOW w AS ( - PARTITION BY user, SessionWindow(ts + 1, $init, $update, $calculate) - ) - )"; - ExpectFailWithError(query, "<main>:2:33: Error: Aggregation function Min requires exactly 1 argument(s), given: 2\n"); - } - - Y_UNIT_TEST(ScalarContextUsage1) { - TString query = R"( - $a = (select 1 as x, 2 as y); - select 1 + $a; - )"; - ExpectFailWithError(query, "<main>:2:39: Error: Source used in expression should contain one concrete column\n" - "<main>:3:24: Error: Source is used here\n"); - } - - Y_UNIT_TEST(ScalarContextUsage2) { - TString query = R"( - use plato; - $a = (select 1 as x, 2 as y); - select * from concat($a); - )"; - ExpectFailWithError(query, "<main>:3:39: Error: Source used in expression should contain one concrete column\n" - "<main>:4:34: Error: Source is used here\n"); - } - - Y_UNIT_TEST(ScalarContextUsage3) { - TString query = R"( - use plato; - $a = (select 1 as x, 2 as y); - select * from range($a); - )"; - ExpectFailWithError(query, "<main>:3:39: Error: Source used in expression should contain one concrete column\n" - "<main>:4:33: Error: Source is used here\n"); - } - - Y_UNIT_TEST(ScalarContextUsage4) { - TString query = R"( - use plato; - $a = (select 1 as x, 2 as y); - insert into $a select 1; - )"; - ExpectFailWithError(query, "<main>:3:39: Error: Source used in expression should contain one concrete column\n" - "<main>:4:25: Error: Source is used here\n"); - } -} - -void CheckUnused(const TString& req, const TString& symbol, unsigned row, unsigned col) { - auto res = SqlToYql(req); - - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), TStringBuilder() << "<main>:" << row << ":" << col << ": Warning: Symbol " << symbol << " is not used, code: 4527\n"); -} - -Y_UNIT_TEST_SUITE(WarnUnused) { - Y_UNIT_TEST(ActionOrSubquery) { - TString req = " $a()\n" - "as select 1;\n" - "end define;\n" - "\n" - "select 1;"; - CheckUnused("define action\n" + req, "$a", 2, 3); - CheckUnused("define subquery\n" + req, "$a", 2, 3); - } - - Y_UNIT_TEST(Import) { - TString req = "import lib1 symbols\n" - " $sqr;\n" - "select 1;"; - CheckUnused(req, "$sqr", 2, 3); - - req = "import lib1 symbols\n" - " $sqr as\n" - " $sq;\n" - "select 1;"; - CheckUnused(req, "$sq", 3, 5); - } - - Y_UNIT_TEST(NamedNodeStatement) { - TString req = " $a, $a = AsTuple(1, 2);\n" - "select $a;"; - CheckUnused(req, "$a", 1, 2); - req = "$a, $b = AsTuple(1, 2);\n" - "select $a;"; - CheckUnused(req, "$b", 1, 6); - CheckUnused(" $a = 1; $a = 2; select $a;", "$a", 1, 2); - } - - Y_UNIT_TEST(Declare) { - CheckUnused("declare $a as String;select 1;", "$a", 1, 9); - } - - Y_UNIT_TEST(ActionParams) { - TString req = "define action $a($x, $y) as\n" - " select $x;\n" - "end define;\n" - "\n" - "do $a(1,2);"; - CheckUnused(req, "$y", 1, 22); - } - - Y_UNIT_TEST(SubqueryParams) { - TString req = "use plato;\n" - "define subquery $q($name, $x) as\n" - " select * from $name;\n" - "end define;\n" - "\n" - "select * from $q(\"Input\", 1);"; - CheckUnused(req, "$x", 2, 27); - } - - Y_UNIT_TEST(For) { - TString req = "define action $a() as\n" - " select 1;\n" - "end define;\n" - "\n" - "for $i in ListFromRange(1, 10)\n" - "do $a();"; - CheckUnused(req, "$i", 5, 5); - } - - Y_UNIT_TEST(LambdaParams) { - TString req = "$lambda = ($x, $y) -> ($x);\n" - "select $lambda(1, 2);"; - CheckUnused(req, "$y", 1, 16); - } - - Y_UNIT_TEST(InsideLambdaBody) { - TString req = "$lambda = () -> {\n" - " $x = 1; return 1;\n" - "};\n" - "select $lambda();"; - CheckUnused(req, "$x", 2, 3); - req = "$lambda = () -> {\n" - " $x = 1; $x = 2; return $x;\n" - "};\n" - "select $lambda();"; - CheckUnused(req, "$x", 2, 3); - } - - Y_UNIT_TEST(InsideAction) { - TString req = "define action $a() as\n" - " $x = 1; select 1;\n" - "end define;\n" - "\n" - "do $a();"; - CheckUnused(req, "$x", 2, 3); - req = "define action $a() as\n" - " $x = 1; $x = 2; select $x;\n" - "end define;\n" - "\n" - "do $a();"; - CheckUnused(req, "$x", 2, 3); - } - - Y_UNIT_TEST(NoWarnOnNestedActions) { - auto req = "pragma warning(\"error\", \"4527\");\n" - "define action $action($b) as\n" - " define action $aaa() as\n" - " select $b;\n" - " end define;\n" - " do $aaa();\n" - "end define;\n" - "\n" - "do $action(1);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(NoWarnForUsageAfterSubquery) { - auto req = "use plato;\n" - "pragma warning(\"error\", \"4527\");\n" - "\n" - "$a = 1;\n" - "\n" - "define subquery $q($table) as\n" - " select * from $table;\n" - "end define;\n" - "\n" - "select * from $q(\"Input\");\n" - "select $a;"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } -} - -Y_UNIT_TEST_SUITE(AnonymousNames) { - Y_UNIT_TEST(ReferenceAnonymousVariableIsForbidden) { - auto req = "$_ = 1; select $_;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:16: Error: Unable to reference anonymous name $_\n"); - - req = "$`_` = 1; select $`_`;"; - res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:18: Error: Unable to reference anonymous name $_\n"); - } - - Y_UNIT_TEST(Declare) { - auto req = "declare $_ as String;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:9: Error: Can not use anonymous name '$_' in DECLARE statement\n"); - } - - Y_UNIT_TEST(ActionSubquery) { - auto req = "define action $_() as select 1; end define;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:15: Error: Can not use anonymous name '$_' as ACTION name\n"); - - req = "define subquery $_() as select 1; end define;"; - res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: Can not use anonymous name '$_' as SUBQUERY name\n"); - } - - Y_UNIT_TEST(Import) { - auto req = "import lib symbols $sqr as $_;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:28: Error: Can not import anonymous name $_\n"); - } - - Y_UNIT_TEST(Export) { - auto req = "export $_;"; - auto res = SqlToYqlWithMode(req, NSQLTranslation::ESqlMode::LIBRARY); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Can not export anonymous name $_\n"); - } - - Y_UNIT_TEST(AnonymousInActionArgs) { - auto req = "pragma warning(\"error\", \"4527\");\n" - "define action $a($_, $y, $_) as\n" - " select $y;\n" - "end define;\n" - "\n" - "do $a(1,2,3);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(AnonymousInSubqueryArgs) { - auto req = "use plato;\n" - "pragma warning(\"error\", \"4527\");\n" - "define subquery $q($_, $y, $_) as\n" - " select * from $y;\n" - "end define;\n" - "\n" - "select * from $q(1,\"Input\",3);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(AnonymousInLambdaArgs) { - auto req = "pragma warning(\"error\", \"4527\");\n" - "$lambda = ($_, $x, $_) -> ($x);\n" - "select $lambda(1,2,3);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(AnonymousInFor) { - auto req = "pragma warning(\"error\", \"4527\");\n" - "evaluate for $_ in ListFromRange(1, 10) do begin select 1; end do;"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(Assignment) { - auto req = "pragma warning(\"error\", \"4527\");\n" - "$_ = 1;\n" - "$_, $x, $_ = AsTuple(1,2,3);\n" - "select $x;"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } } -Y_UNIT_TEST_SUITE(JsonValue) { - Y_UNIT_TEST(JsonValueArgumentCount) { - NYql::TAstParseResult res = SqlToYql("select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json));"); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:49: Error: Unexpected token ')' : syntax error...\n\n"); - } - - Y_UNIT_TEST(JsonValueJsonPathMustBeLiteralString) { - NYql::TAstParseResult res = SqlToYql("$jsonPath = \"strict $.key\"; select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), $jsonPath);"); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:79: Error: Unexpected token absence : Missing STRING_VALUE \n\n"); - } - - Y_UNIT_TEST(JsonValueTranslation) { - NYql::TAstParseResult res = SqlToYql("select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\");"); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"strict $.key\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("SafeCast")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("DataType 'Json")); - }; - - TWordCountHive elementStat({"JsonValue"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["JsonValue"]); - } - - Y_UNIT_TEST(JsonValueReturningSection) { - for (const auto& typeName : {"Bool", "Int64", "Double", "String"}) { - NYql::TAstParseResult res = SqlToYql( - TStringBuilder() << "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" RETURNING " << typeName << ");" - ); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"strict $.key\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("SafeCast")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("DataType 'Json")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(TStringBuilder() << "DataType '" << typeName)); - }; - - TWordCountHive elementStat({typeName}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat[typeName] > 0); - } - } - - Y_UNIT_TEST(JsonValueInvalidReturningType) { - NYql::TAstParseResult res = SqlToYql("select JSON_VALUE(CAST(@@{'key': 1238}@@ as Json), 'strict $.key' RETURNING invalid);"); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:77: Error: Unknown simple type 'invalid'\n"); - } - - Y_UNIT_TEST(JsonValueAndReturningInExpressions) { - NYql::TAstParseResult res = SqlToYql( - "USE plato\n;" - "$json_value = \"some string\";\n" - "SELECT $json_value;\n" - "SELECT 1 as json_value;\n" - "SELECT $json_value as json_value;\n" - "$returning = \"another string\";\n" - "SELECT $returning;\n" - "SELECT 1 as returning;\n" - "SELECT $returning as returning;\n" - ); - - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(JsonValueValidCaseHandlers) { - const TVector<std::pair<TString, TString>> testCases = { - {"", "'DefaultValue (Null)"}, - {"NULL", "'DefaultValue (Null)"}, - {"ERROR", "'Error (Null)"}, - {"DEFAULT 123", "'DefaultValue (Int32 '\"123\")"}, - }; - - for (const auto& onEmpty : testCases) { - for (const auto& onError : testCases) { - TStringBuilder query; - query << "$json = CAST(@@{\"key\": 1238}@@ as Json);\n" - << "SELECT JSON_VALUE($json, \"strict $.key\""; - if (!onEmpty.first.empty()) { - query << " " << onEmpty.first << " ON EMPTY"; - } - if (!onError.first.empty()) { - query << " " << onError.first << " ON ERROR"; - } - query << ");\n"; - - NYql::TAstParseResult res = SqlToYql(query); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(onEmpty.second + " " + onError.second)); - }; - - TWordCountHive elementStat({"JsonValue"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonValue"] > 0); - } - } - } - - Y_UNIT_TEST(JsonValueTooManyCaseHandlers) { - NYql::TAstParseResult res = SqlToYql( - "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" NULL ON EMPTY NULL ON ERROR NULL ON EMPTY);\n" - ); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF( - Err2Str(res), - "<main>:1:52: Error: Only 1 ON EMPTY and/or 1 ON ERROR clause is expected\n" - ); - } - - Y_UNIT_TEST(JsonValueTooManyOnEmpty) { - NYql::TAstParseResult res = SqlToYql( - "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" NULL ON EMPTY NULL ON EMPTY);\n" - ); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF( - Err2Str(res), - "<main>:1:52: Error: Only 1 ON EMPTY clause is expected\n" - ); - } - - Y_UNIT_TEST(JsonValueTooManyOnError) { - NYql::TAstParseResult res = SqlToYql( - "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" NULL ON ERROR NULL ON ERROR);\n" - ); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF( - Err2Str(res), - "<main>:1:52: Error: Only 1 ON ERROR clause is expected\n" - ); - } - - Y_UNIT_TEST(JsonValueOnEmptyAfterOnError) { - NYql::TAstParseResult res = SqlToYql( - "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" NULL ON ERROR NULL ON EMPTY);\n" - ); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF( - Err2Str(res), - "<main>:1:52: Error: ON EMPTY clause must be before ON ERROR clause\n" - ); - } - - Y_UNIT_TEST(JsonValueNullInput) { - NYql::TAstParseResult res = SqlToYql(R"(SELECT JSON_VALUE(NULL, "strict $.key");)"); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Nothing (OptionalType (DataType 'Json)))")); - }; - - TWordCountHive elementStat({"JsonValue"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonValue"] > 0); - } -} - -Y_UNIT_TEST_SUITE(JsonExists) { - Y_UNIT_TEST(JsonExistsValidHandlers) { - const TVector<std::pair<TString, TString>> testCases = { - {"", "(Just (Bool '\"false\"))"}, - {"TRUE ON ERROR", "(Just (Bool '\"true\"))"}, - {"FALSE ON ERROR", "(Just (Bool '\"false\"))"}, - {"UNKNOWN ON ERROR", "(Nothing (OptionalType (DataType 'Bool)))"}, - // NOTE: in this case we expect arguments of JsonExists callable to end immediately - // after variables. This parenthesis at the end of the expression is left on purpose - {"ERROR ON ERROR", "(Utf8 '\"strict $.key\") (JsonVariables))"}, - }; - - for (const auto& item : testCases) { - NYql::TAstParseResult res = SqlToYql( - TStringBuilder() << R"( - $json = CAST(@@{"key": 1238}@@ as Json); - SELECT JSON_EXISTS($json, "strict $.key" )" << item.first << ");\n" - ); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(item.second)); - }; - - TWordCountHive elementStat({"JsonExists"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonExists"] > 0); - } - } - - Y_UNIT_TEST(JsonExistsInvalidHandler) { - NYql::TAstParseResult res = SqlToYql(R"( - $json = CAST(@@{"key": 1238}@@ as Json); - $default = false; - SELECT JSON_EXISTS($json, "strict $.key" $default ON ERROR); - )"); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:53: Error: Unexpected token absence : Missing RPAREN \n\n"); - } - - Y_UNIT_TEST(JsonExistsNullInput) { - NYql::TAstParseResult res = SqlToYql(R"(SELECT JSON_EXISTS(NULL, "strict $.key");)"); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Nothing (OptionalType (DataType 'Json)))")); - }; - - TWordCountHive elementStat({"JsonExists"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonExists"] > 0); - } -} - -Y_UNIT_TEST_SUITE(JsonQuery) { - Y_UNIT_TEST(JsonQueryValidHandlers) { - using TTestSuite = const TVector<std::pair<TString, TString>>; - TTestSuite wrapCases = { - {"", "'NoWrap"}, - {"WITHOUT WRAPPER", "'NoWrap"}, - {"WITHOUT ARRAY WRAPPER", "'NoWrap"}, - {"WITH WRAPPER", "'Wrap"}, - {"WITH ARRAY WRAPPER", "'Wrap"}, - {"WITH UNCONDITIONAL WRAPPER", "'Wrap"}, - {"WITH UNCONDITIONAL ARRAY WRAPPER", "'Wrap"}, - {"WITH CONDITIONAL WRAPPER", "'ConditionalWrap"}, - {"WITH CONDITIONAL ARRAY WRAPPER", "'ConditionalWrap"}, - }; - TTestSuite handlerCases = { - {"", "'Null"}, - {"ERROR", "'Error"}, - {"NULL", "'Null"}, - {"EMPTY ARRAY", "'EmptyArray"}, - {"EMPTY OBJECT", "'EmptyObject"}, - }; - - for (const auto& wrap : wrapCases) { - for (const auto& onError : handlerCases) { - for (const auto& onEmpty : handlerCases) { - TStringBuilder query; - query << R"($json = CAST(@@{"key": [123]}@@ as Json); - SELECT JSON_QUERY($json, "strict $.key" )" << wrap.first; - if (!onEmpty.first.empty()) { - if (wrap.first.StartsWith("WITH ")) { - continue; - } - query << " " << onEmpty.first << " ON EMPTY"; - } - if (!onError.first.empty()) { - query << " " << onError.first << " ON ERROR"; - } - query << ");\n"; - - NYql::TAstParseResult res = SqlToYql(query); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - const TString args = TStringBuilder() << wrap.second << " " << onEmpty.second << " " << onError.second; - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(args)); - }; - - Cout << wrap.first << " " << onEmpty.first << " " << onError.first << Endl; - - TWordCountHive elementStat({"JsonQuery"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonQuery"] > 0); - } - } - } - } - - Y_UNIT_TEST(JsonQueryOnEmptyWithWrapper) { - NYql::TAstParseResult res = SqlToYql(R"( - $json = CAST(@@{"key": 1238}@@ as Json); - SELECT JSON_QUERY($json, "strict $" WITH ARRAY WRAPPER EMPTY ARRAY ON EMPTY); - )"); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:38: Error: ON EMPTY is prohibited because WRAPPER clause is specified\n"); - } - - Y_UNIT_TEST(JsonQueryNullInput) { - NYql::TAstParseResult res = SqlToYql(R"(SELECT JSON_QUERY(NULL, "strict $.key");)"); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Nothing (OptionalType (DataType 'Json)))")); - }; - - TWordCountHive elementStat({"JsonQuery"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonQuery"] > 0); - } -} - -Y_UNIT_TEST_SUITE(JsonPassing) { - Y_UNIT_TEST(SupportedVariableTypes) { - const TVector<TString> functions = {"JSON_EXISTS", "JSON_VALUE", "JSON_QUERY"}; - - for (const auto& function : functions) { - const auto query = Sprintf(R"( - pragma CompactNamedExprs; - $json = CAST(@@{"key": 1238}@@ as Json); - SELECT %s( - $json, - "strict $.key" - PASSING - "string" as var1, - 1.234 as var2, - CAST(1 as Int64) as var3, - true as var4, - $json as var5 - ))", - function.data() - ); - NYql::TAstParseResult res = SqlToYql(query); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var1" (String '"string")))"), "Cannot find `var1`"); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var2" (Double '"1.234")))"), "Cannot find `var2`"); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var3" (SafeCast (Int32 '"1") (DataType 'Int64))))"), "Cannot find `var3`"); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var4" (Bool '"true")))"), "Cannot find `var4`"); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var5" namedexprnode0))"), "Cannot find `var5`"); - }; - - TWordCountHive elementStat({"JsonVariables"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonVariables"] > 0); - } - } - - Y_UNIT_TEST(ValidVariableNames) { - const TVector<TString> functions = {"JSON_EXISTS", "JSON_VALUE", "JSON_QUERY"}; - - for (const auto& function : functions) { - const auto query = Sprintf(R"( - $json = CAST(@@{"key": 1238}@@ as Json); - SELECT %s( - $json, - "strict $.key" - PASSING - "one" as var1, - "two" as "VaR2", - "three" as `var3`, - "four" as VaR4 - ))", - function.data() - ); - NYql::TAstParseResult res = SqlToYql(query); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var1" (String '"one")))"), "Cannot find `var1`"); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"VaR2" (String '"two")))"), "Cannot find `VaR2`"); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var3" (String '"three")))"), "Cannot find `var3`"); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"VaR4" (String '"four")))"), "Cannot find `VaR4`"); - }; - - TWordCountHive elementStat({"JsonVariables"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonVariables"] > 0); - } - } -} - -Y_UNIT_TEST_SUITE(MigrationToJsonApi) { - Y_UNIT_TEST(WarningOnDeprecatedJsonUdf) { - NYql::TAstParseResult res = SqlToYql(R"( - $json = CAST(@@{"key": 1234}@@ as Json); - SELECT Json::Parse($json); - )"); - - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:26: Warning: Json UDF is deprecated. Please use JSON API instead, code: 4506\n"); - } -} - -Y_UNIT_TEST_SUITE(AnsiIdentsNegative) { - Y_UNIT_TEST(EnableAnsiLexerFromRequestSpecialComments) { - auto req = "\n" - "\t --!ansi_lexer \n" - "-- Some comment\n" - "-- another comment\n" - "pragma SimpleColumns;\n" - "\n" - "select 1, '''' as empty;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - } - - Y_UNIT_TEST(AnsiLexerShouldNotBeEnabledHere) { - auto req = "$str = '\n" - "--!ansi_lexer\n" - "--!syntax_v1\n" - "';\n" - "\n" - "select 1, $str, \"\" as empty;"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - } - - Y_UNIT_TEST(DoubleQuotesInDictsTuplesOrLists) { - auto req = "$d = { 'a': 1, \"b\": 2, 'c': 3,};"; - - auto res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:16: Error: Column reference \"b\" is not allowed in current scope\n"); - - req = "$t = (1, 2, \"a\");"; - - res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:13: Error: Column reference \"a\" is not allowed in current scope\n"); - - req = "$l = ['a', 'b', \"c\"];"; - - res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: Column reference \"c\" is not allowed in current scope\n"); - } - - Y_UNIT_TEST(MultilineComments) { - auto req = "/*/**/ select 1;"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:16: Error: Unexpected character : syntax error...\n\n"); - - req = "/*\n" - "--/*\n" - "*/ select 1;"; - res = SqlToYql(req); - UNIT_ASSERT(res.Root); - res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:12: Error: Unexpected character : syntax error...\n\n"); - - req = "/*\n" - "/*\n" - "--*/\n" - "*/ select 1;"; - res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:0: Error: Unexpected token '*' : cannot match to any predicted input...\n\n"); - res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(res.Root); - } -} - -Y_UNIT_TEST_SUITE(AnsiOptionalAs) { - Y_UNIT_TEST(OptionalAsInProjection) { - UNIT_ASSERT(SqlToYql("PRAGMA AnsiOptionalAs; SELECT a b, c FROM plato.Input;").IsOk()); - ExpectFailWithError("PRAGMA DisableAnsiOptionalAs;\n" - "SELECT a b, c FROM plato.Input;", - "<main>:2:10: Error: Expecting mandatory AS here. Did you miss comma? Please add PRAGMA AnsiOptionalAs; for ANSI compatibility\n"); - } - - Y_UNIT_TEST(OptionalAsWithKeywords) { - UNIT_ASSERT(SqlToYql("PRAGMA AnsiOptionalAs; SELECT a type, b data, c source FROM plato.Input;").IsOk()); - } -} - -Y_UNIT_TEST_SUITE(SessionWindowNegative) { - Y_UNIT_TEST(SessionWindowWithoutSource) { - ExpectFailWithError("SELECT 1 + SessionWindow(ts, 32);", - "<main>:1:12: Error: SessionWindow requires data source\n"); - } - - Y_UNIT_TEST(SessionWindowInProjection) { - ExpectFailWithError("SELECT 1 + SessionWindow(ts, 32) from plato.Input;", - "<main>:1:12: Error: SessionWindow can only be used as a top-level GROUP BY / PARTITION BY expression\n"); - } - - Y_UNIT_TEST(SessionWindowWithNonConstSecondArg) { - ExpectFailWithError( - "SELECT key, session_start FROM plato.Input\n" - "GROUP BY SessionWindow(ts, 32 + subkey) as session_start, key;", - - "<main>:2:10: Error: Source does not allow column references\n" - "<main>:2:33: Error: Column reference 'subkey'\n"); - } - - Y_UNIT_TEST(SessionWindowWithWrongNumberOfArgs) { - ExpectFailWithError("SELECT * FROM plato.Input GROUP BY SessionWindow()", - "<main>:1:36: Error: SessionWindow requires either two or four arguments\n"); - ExpectFailWithError("SELECT * FROM plato.Input GROUP BY SessionWindow(key, subkey, 100)", - "<main>:1:36: Error: SessionWindow requires either two or four arguments\n"); - } - - Y_UNIT_TEST(DuplicateSessionWindow) { - ExpectFailWithError( - "SELECT\n" - " *\n" - "FROM plato.Input\n" - "GROUP BY\n" - " SessionWindow(ts, 10),\n" - " user,\n" - " SessionWindow(ts, 20)\n" - ";", - - "<main>:7:5: Error: Duplicate session window specification:\n" - "<main>:5:5: Error: Previous session window is declared here\n"); - - ExpectFailWithError( - "SELECT\n" - " MIN(key) over w\n" - "FROM plato.Input\n" - "WINDOW w AS (\n" - " PARTITION BY SessionWindow(ts, 10), user,\n" - " SessionWindow(ts, 20)\n" - ");", - - "<main>:6:5: Error: Duplicate session window specification:\n" - "<main>:5:18: Error: Previous session window is declared here\n"); - } - - Y_UNIT_TEST(SessionStartStateWithoutSource) { - ExpectFailWithError("SELECT 1 + SessionStart();", - "<main>:1:12: Error: SessionStart requires data source\n"); - ExpectFailWithError("SELECT 1 + SessionState();", - "<main>:1:12: Error: SessionState requires data source\n"); - } - - Y_UNIT_TEST(SessionStartStateWithoutGroupByOrWindow) { - ExpectFailWithError("SELECT 1 + SessionStart() from plato.Input;", - "<main>:1:12: Error: SessionStart can not be used without aggregation by SessionWindow\n"); - ExpectFailWithError("SELECT 1 + SessionState() from plato.Input;", - "<main>:1:12: Error: SessionState can not be used without aggregation by SessionWindow\n"); - } - - Y_UNIT_TEST(SessionStartStateWithGroupByWithoutSession) { - ExpectFailWithError("SELECT 1 + SessionStart() from plato.Input group by user;", - "<main>:1:12: Error: SessionStart can not be used here: SessionWindow specification is missing in GROUP BY\n"); - ExpectFailWithError("SELECT 1 + SessionState() from plato.Input group by user;", - "<main>:1:12: Error: SessionState can not be used here: SessionWindow specification is missing in GROUP BY\n"); - } - - Y_UNIT_TEST(SessionStartStateWithoutOverWithWindowWithoutSession) { - ExpectFailWithError("SELECT 1 + SessionStart(), MIN(key) over w from plato.Input window w as ()", - "<main>:1:12: Error: SessionStart can not be used without aggregation by SessionWindow. Maybe you forgot to add OVER `window_name`?\n"); - ExpectFailWithError("SELECT 1 + SessionState(), MIN(key) over w from plato.Input window w as ()", - "<main>:1:12: Error: SessionState can not be used without aggregation by SessionWindow. Maybe you forgot to add OVER `window_name`?\n"); - } - - Y_UNIT_TEST(SessionStartStateWithWindowWithoutSession) { - ExpectFailWithError("SELECT 1 + SessionStart() over w, MIN(key) over w from plato.Input window w as ()", - "<main>:1:12: Error: SessionStart can not be used with window w: SessionWindow specification is missing in PARTITION BY\n"); - ExpectFailWithError("SELECT 1 + SessionState() over w, MIN(key) over w from plato.Input window w as ()", - "<main>:1:12: Error: SessionState can not be used with window w: SessionWindow specification is missing in PARTITION BY\n"); - } - - Y_UNIT_TEST(SessionStartStateWithSessionedWindow) { - ExpectFailWithError("SELECT 1 + SessionStart(), MIN(key) over w from plato.Input group by key window w as (partition by SessionWindow(ts, 1)) ", - "<main>:1:12: Error: SessionStart can not be used here: SessionWindow specification is missing in GROUP BY. Maybe you forgot to add OVER `window_name`?\n"); - ExpectFailWithError("SELECT 1 + SessionState(), MIN(key) over w from plato.Input group by key window w as (partition by SessionWindow(ts, 1)) ", - "<main>:1:12: Error: SessionState can not be used here: SessionWindow specification is missing in GROUP BY. Maybe you forgot to add OVER `window_name`?\n"); - } - - Y_UNIT_TEST(AggregationBySessionStateIsNotSupportedYet) { - ExpectFailWithError("SELECT SOME(1 + SessionState()), key from plato.Input group by key, SessionWindow(ts, 1);", - "<main>:1:17: Error: SessionState with GROUP BY is not supported yet\n"); - } - - Y_UNIT_TEST(SessionWindowInRtmr) { - NYql::TAstParseResult res = SqlToYql( - "SELECT * FROM plato.Input GROUP BY SessionWindow(ts, 10);", - 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:54: Error: Streaming group by query must have a hopping window specification.\n"); - - res = SqlToYql(R"( - SELECT key, SUM(value) AS value FROM plato.Input - GROUP BY key, HOP(subkey, "PT10S", "PT30S", "PT20S"), SessionWindow(ts, 10); - )", 10, TString(NYql::RtmrProviderName)); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:13: Error: SessionWindow is unsupported for streaming sources\n"); - } -} - -Y_UNIT_TEST_SUITE(LibraSqlSugar) { - auto makeResult = [](TStringBuf settings) { - return SqlToYql( - TStringBuilder() - << settings - << "\n$udf1 = MyLibra::MakeLibraPreprocessor($settings);" - << "\n$udf2 = CustomLibra::MakeLibraPreprocessor($settings);" - << "\nPROCESS plato.Input USING $udf1(TableRow())" - << "\nUNION ALL" - << "\nPROCESS plato.Input USING $udf2(TableRow());" - ); - }; - - Y_UNIT_TEST(EmptySettings) { - auto res = makeResult(R"( - $settings = AsStruct(); - )"); - UNIT_ASSERT(res.IsOk()); - } - - Y_UNIT_TEST(OnlyEntities) { - auto res = makeResult(R"( - $settings = AsStruct( - AsList("A", "B", "C") AS Entities - ); - )"); - UNIT_ASSERT(res.IsOk()); - } - - Y_UNIT_TEST(EntitiesWithStrategy) { - auto res = makeResult(R"( - $settings = AsStruct( - AsList("A", "B", "C") AS Entities, - "blacklist" AS EntitiesStrategy - ); - )"); - UNIT_ASSERT(res.IsOk()); - } - - Y_UNIT_TEST(AllSettings) { - auto res = makeResult(R"( - $settings = AsStruct( - AsList("A", "B", "C") AS Entities, - "whitelist" AS EntitiesStrategy, - "path" AS BlockstatDict, - false AS ParseWithFat, - "map" AS Mode - ); - )"); - UNIT_ASSERT(res.IsOk()); - } - - Y_UNIT_TEST(BadStrategy) { - auto res = makeResult(R"( - $settings = AsStruct("bad" AS EntitiesStrategy); - )"); - UNIT_ASSERT_STRING_CONTAINS( - Err2Str(res), - "Error: MakeLibraPreprocessor got invalid entities strategy: expected 'whitelist' or 'blacklist'" - ); - } - - Y_UNIT_TEST(BadEntities) { - auto res = makeResult(R"( - $settings = AsStruct(AsList("A", 1) AS Entities); - )"); - UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "Error: MakeLibraPreprocessor entity must be string literal"); - } -} - -Y_UNIT_TEST_SUITE(TrailingQuestionsNegative) { - Y_UNIT_TEST(Basic) { - ExpectFailWithError("SELECT 1?;", "<main>:1:9: Error: Unexpected token '?' at the end of expression\n"); - ExpectFailWithError("SELECT 1? + 1;", "<main>:1:10: Error: Unexpected token '+' : cannot match to any predicted input...\n\n"); - ExpectFailWithError("SELECT 1 + 1??? < 2", "<main>:1:13: Error: Unexpected token '?' at the end of expression\n"); - ExpectFailWithError("SELECT 1? > 2? > 3?", - "<main>:1:11: Error: Unexpected token '?' at the end of expression\n" - "<main>:1:16: Error: Unexpected token '?' at the end of expression\n" - "<main>:1:21: Error: Unexpected token '?' at the end of expression\n"); - } - - Y_UNIT_TEST(SmartParen) { - ExpectFailWithError("$x = 1; SELECT (Int32?, $x?)", "<main>:1:27: Error: Unexpected token '?' at the end of expression\n"); - ExpectFailWithError("SELECT (Int32, foo?)", "<main>:1:19: Error: Unexpected token '?' at the end of expression\n"); - } - - Y_UNIT_TEST(LambdaOptArgs) { - ExpectFailWithError("$l = ($x, $y?, $z??, $t?) -> ($x);", "<main>:1:18: Error: Expecting at most one '?' token here (for optional lambda parameters), but got 2\n"); - } -} - -Y_UNIT_TEST_SUITE(FlexibleTypes) { - Y_UNIT_TEST(AssumeOrderByType) { - UNIT_ASSERT(SqlToYql("PRAGMA FlexibleTypes; SELECT 1 AS int32 ASSUME ORDER BY int32").IsOk()); - } - - Y_UNIT_TEST(GroupingSets) { - UNIT_ASSERT(SqlToYql("PRAGMA FlexibleTypes; SELECT COUNT(*) AS cnt, text, uuid FROM plato.Input GROUP BY GROUPING SETS((uuid), (uuid, text));").IsOk()); - } - - Y_UNIT_TEST(WeakField) { - UNIT_ASSERT(SqlToYql("PRAGMA FlexibleTypes; SELECT WeakField(text, string) as text FROM plato.Input").IsOk()); - } - - Y_UNIT_TEST(Aggregation1) { - TString q = - "PRAGMA FlexibleTypes;\n" - "$foo = ($x, $const, $type) -> ($x || $const || FormatType($type));\n" - "SELECT $foo(SOME(x), 'aaa', String) FROM plato.Input GROUP BY y;"; - UNIT_ASSERT(SqlToYql(q).IsOk()); - } - - Y_UNIT_TEST(Aggregation2) { - TString q = - "PRAGMA FlexibleTypes;\n" - "SELECT 1 + String + MAX(key) FROM plato.Input;"; - UNIT_ASSERT(SqlToYql(q).IsOk()); - } -} - -Y_UNIT_TEST_SUITE(ExternalDeclares) { - Y_UNIT_TEST(BasicUsage) { - NSQLTranslation::TTranslationSettings settings; - settings.DeclaredNamedExprs["foo"] = "String"; - auto res = SqlToYqlWithSettings("select $foo;", settings); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "declare") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"__((declare "$foo" (DataType 'String)))__")); - } - }; - - TWordCountHive elementStat = {{TString("declare"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["declare"]); - } - - Y_UNIT_TEST(DeclareOverrides) { - NSQLTranslation::TTranslationSettings settings; - settings.DeclaredNamedExprs["foo"] = "String"; - auto res = SqlToYqlWithSettings("declare $foo as Int32; select $foo;", settings); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "declare") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"__((declare "$foo" (DataType 'Int32)))__")); - } - }; - - TWordCountHive elementStat = {{TString("declare"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["declare"]); - } - - Y_UNIT_TEST(UnusedDeclareDoesNotProduceWarning) { - NSQLTranslation::TTranslationSettings settings; - settings.DeclaredNamedExprs["foo"] = "String"; - auto res = SqlToYqlWithSettings("select 1;", settings); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "declare") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"__((declare "$foo" (DataType 'String)))__")); - } - }; - - TWordCountHive elementStat = {{TString("declare"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["declare"]); - } - - Y_UNIT_TEST(DeclaresWithInvalidTypesFails) { - NSQLTranslation::TTranslationSettings settings; - settings.DeclaredNamedExprs["foo"] = "List<BadType>"; - auto res = SqlToYqlWithSettings("select 1;", settings); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - "<main>:0:5: Error: Unknown type: 'BadType'\n" - "<main>: Error: Failed to parse type for externally declared name 'foo'\n"); - } -} - -Y_UNIT_TEST_SUITE(ExternalDataSource) { - Y_UNIT_TEST(CreateExternalDataSourceWithAuthNone) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="NONE" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"NONE") '('"location" '"my-bucket") '('"source_type" '"ObjectStorage"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceWithAuthServiceAccount) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="SERVICE_ACCOUNT", - SERVICE_ACCOUNT_ID="sa", - SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"SERVICE_ACCOUNT") '('"location" '"my-bucket") '('"service_account_id" '"sa") '('"service_account_secret_name" '"sa_secret_name") '('"source_type" '"ObjectStorage"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceWithBasic) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="BASIC", - LOGIN="admin", - PASSWORD_SECRET_NAME="secret_name" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"BASIC") '('"location" '"protocol://host:port/") '('"login" '"admin") '('"password_secret_name" '"secret_name") '('"source_type" '"PostgreSQL"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceWithMdbBasic) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="MDB_BASIC", - SERVICE_ACCOUNT_ID="sa", - SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name", - LOGIN="admin", - PASSWORD_SECRET_NAME="secret_name" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"MDB_BASIC") '('"location" '"protocol://host:port/") '('"login" '"admin") '('"password_secret_name" '"secret_name") '('"service_account_id" '"sa") '('"service_account_secret_name" '"sa_secret_name") '('"source_type" '"PostgreSQL"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceWithAws) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="AWS", - AWS_ACCESS_KEY_ID_SECRET_NAME="secred_id_name", - AWS_SECRET_ACCESS_KEY_SECRET_NAME="secret_key_name", - AWS_REGION="ru-central-1" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"AWS") '('"aws_access_key_id_secret_name" '"secred_id_name") '('"aws_region" '"ru-central-1") '('"aws_secret_access_key_secret_name" '"secret_key_name") '('"location" '"protocol://host:port/") '('"source_type" '"PostgreSQL"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceWithToken) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="YT", - LOCATION="protocol://host:port/", - AUTH_METHOD="TOKEN", - TOKEN_SECRET_NAME="token_name" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"TOKEN") '('"location" '"protocol://host:port/") '('"source_type" '"YT") '('"token_secret_name" '"token_name"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceWithTablePrefix) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - pragma TablePathPrefix='/aba'; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="NONE" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, "/aba/MyDataSource"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceIfNotExists) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE IF NOT EXISTS MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="NONE" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"NONE") '('"location" '"my-bucket") '('"source_type" '"ObjectStorage"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObjectIfNotExists")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(AlterExternalDataSource) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - ALTER EXTERNAL DATA SOURCE MyDataSource - SET (SOURCE_TYPE = "ObjectStorage", Login = "Admin"), - SET Location "bucket", - RESET (Auth_Method, Service_Account_Id, Service_Account_Secret_Name); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alterObject))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('features '('('"location" '"bucket") '('"login" '"Admin") '('"source_type" '"ObjectStorage"))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('resetFeatures '('"auth_method" '"service_account_id" '"service_account_secret_name")))#"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceOrReplace) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - CREATE OR REPLACE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="NONE" - ); - )"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"NONE") '('"location" '"my-bucket") '('"source_type" '"ObjectStorage"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObjectOrReplace")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateOrReplaceForUnsupportedTableTypesShouldFail) { - ExpectFailWithError(R"sql( - USE plato; - CREATE OR REPLACE TABLE t (a int32 not null, primary key(a, a)); - )sql" , "<main>:3:23: Error: OR REPLACE feature is supported only for EXTERNAL DATA SOURCE and EXTERNAL TABLE\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE OR REPLACE TABLE t ( - Key Uint64, - Value1 String, - PRIMARY KEY (Key) - ) - WITH ( - STORE = COLUMN, - AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = 10 - ); - )sql" , "<main>:3:23: Error: OR REPLACE feature is supported only for EXTERNAL DATA SOURCE and EXTERNAL TABLE\n"); - } - - Y_UNIT_TEST(CreateExternalDataSourceWithBadArguments) { - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource; - )sql" , "<main>:3:56: Error: Unexpected token ';' : syntax error...\n\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - LOCATION="my-bucket", - AUTH_METHOD="NONE" - ); - )sql" , "<main>:5:33: Error: SOURCE_TYPE requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket" - ); - )sql" , "<main>:5:30: Error: AUTH_METHOD requires key\n"); - - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="NONE1" - ); - )sql" , "<main>:6:33: Error: Unknown AUTH_METHOD = NONE1\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="SERVICE_ACCOUNT" - ); - )sql" , "<main>:6:33: Error: SERVICE_ACCOUNT_ID requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="SERVICE_ACCOUNT", - SERVICE_ACCOUNT_ID="s1" - ); - )sql" , "<main>:7:40: Error: SERVICE_ACCOUNT_SECRET_NAME requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="SERVICE_ACCOUNT", - SERVICE_ACCOUNT_SECRET_NAME="s1" - ); - )sql" , "<main>:7:49: Error: SERVICE_ACCOUNT_ID requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="BASIC", - LOGIN="admin" - ); - )sql" , "<main>:7:27: Error: PASSWORD_SECRET_NAME requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="BASIC", - PASSWORD_SECRET_NAME="secret_name" - ); - )sql" , "<main>:7:42: Error: LOGIN requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="MDB_BASIC", - SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name", - LOGIN="admin", - PASSWORD_SECRET_NAME="secret_name" - ); - )sql" , "<main>:9:42: Error: SERVICE_ACCOUNT_ID requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="MDB_BASIC", - SERVICE_ACCOUNT_ID="sa", - LOGIN="admin", - PASSWORD_SECRET_NAME="secret_name" - ); - )sql" , "<main>:9:42: Error: SERVICE_ACCOUNT_SECRET_NAME requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="MDB_BASIC", - SERVICE_ACCOUNT_ID="sa", - SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name", - PASSWORD_SECRET_NAME="secret_name" - ); - )sql" , "<main>:9:42: Error: LOGIN requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="MDB_BASIC", - SERVICE_ACCOUNT_ID="sa", - SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name", - LOGIN="admin" - ); - )sql" , "<main>:9:27: Error: PASSWORD_SECRET_NAME requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="AWS", - AWS_SECRET_ACCESS_KEY_SECRET_NAME="secret_key_name", - AWS_REGION="ru-central-1" - ); - )sql" , "<main>:8:32: Error: AWS_ACCESS_KEY_ID_SECRET_NAME requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="AWS", - AWS_ACCESS_KEY_ID_SECRET_NAME="secred_id_name", - AWS_REGION="ru-central-1" - ); - )sql" , "<main>:8:32: Error: AWS_SECRET_ACCESS_KEY_SECRET_NAME requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="AWS", - AWS_SECRET_ACCESS_KEY_SECRET_NAME="secret_key_name", - AWS_ACCESS_KEY_ID_SECRET_NAME="secred_id_name" - ); - )sql" , "<main>:8:51: Error: AWS_REGION requires key\n"); - } - - Y_UNIT_TEST(DropExternalDataSourceWithTablePrefix) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - DROP EXTERNAL DATA SOURCE MyDataSource; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropExternalDataSource) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - pragma TablePathPrefix='/aba'; - DROP EXTERNAL DATA SOURCE MyDataSource; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, "/aba/MyDataSource"); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropExternalDataSourceIfExists) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - DROP EXTERNAL DATA SOURCE IF EXISTS MyDataSource; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, "MyDataSource"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObjectIfExists")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } -} - -Y_UNIT_TEST_SUITE(ExternalTable) { - Y_UNIT_TEST(CreateExternalTable) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL TABLE mytable ( - a int - ) WITH ( - DATA_SOURCE="/Root/mydatasource", - LOCATION="/folder1/*" - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('data_source_path (String '"/Root/mydatasource")) '('location (String '"/folder1/*")))) '('tableType 'externalTable)))))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tablescheme")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalTableWithTablePrefix) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - pragma TablePathPrefix='/aba'; - CREATE EXTERNAL TABLE mytable ( - a int - ) WITH ( - DATA_SOURCE="mydatasource", - LOCATION="/folder1/*" - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, "/aba/mydatasource"); - UNIT_ASSERT_STRING_CONTAINS(line, "/aba/mytable"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tablescheme")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalTableObjectStorage) { - auto res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL TABLE mytable ( - a int, - year Int - ) WITH ( - DATA_SOURCE="/Root/mydatasource", - LOCATION="/folder1/*", - FORMAT="json_as_string", - `projection.enabled`="true", - `projection.year.type`="integer", - `projection.year.min`="2010", - `projection.year.max`="2022", - `projection.year.interval`="1", - `projection.month.type`="integer", - `projection.month.min`="1", - `projection.month.max`="12", - `projection.month.interval`="1", - `projection.month.digits`="2", - `storage.location.template`="${year}/${month}", - PARTITONED_BY = "[year, month]" - ); - )sql"); - UNIT_ASSERT_C(res.IsOk(), res.Issues.ToString()); - } - - Y_UNIT_TEST(CreateExternalTableIfNotExists) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL TABLE IF NOT EXISTS mytable ( - a int - ) WITH ( - DATA_SOURCE="/Root/mydatasource", - LOCATION="/folder1/*" - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('data_source_path (String '"/Root/mydatasource")) '('location (String '"/folder1/*")))) '('tableType 'externalTable)))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, "create_if_not_exists"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalTableOrReplace) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - CREATE OR REPLACE EXTERNAL TABLE mytable ( - a int - ) WITH ( - DATA_SOURCE="/Root/mydatasource", - LOCATION="/folder1/*" - ); - )"); - UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('data_source_path (String '"/Root/mydatasource")) '('location (String '"/folder1/*")))) '('tableType 'externalTable)))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, "create_or_replace"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(AlterExternalTableAddColumn) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - ALTER EXTERNAL TABLE mytable - ADD COLUMN my_column int32, - RESET (LOCATION); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('actions '('('addColumns '('('"my_column" (AsOptionalType (DataType 'Int32))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('setTableSettings '('('location)))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('tableType 'externalTable))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alter))#"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(AlterExternalTableDropColumn) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - ALTER EXTERNAL TABLE mytable - DROP COLUMN my_column, - SET (Location = "abc", Other_Prop = "42"), - SET x 'y'; - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('actions '('('dropColumns '('"my_column")#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('setTableSettings '('('location (String '"abc")) '('Other_Prop (String '"42")) '('x (String '"y")))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('tableType 'externalTable))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alter))#"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalTableWithBadArguments) { - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL TABLE mytable; - )sql" , "<main>:3:45: Error: Unexpected token ';' : syntax error...\n\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL TABLE mytable ( - a int - ); - )sql" , "<main>:4:23: Error: DATA_SOURCE requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL TABLE mytable ( - a int - ) WITH ( - DATA_SOURCE="/Root/mydatasource" - ); - )sql" , "<main>:6:33: Error: LOCATION requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL TABLE mytable ( - a int - ) WITH ( - LOCATION="/folder1/*" - ); - )sql" , "<main>:6:30: Error: DATA_SOURCE requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL TABLE mytable ( - a int, - PRIMARY KEY(a) - ) WITH ( - DATA_SOURCE="/Root/mydatasource", - LOCATION="/folder1/*" - ); - )sql" , "<main>:8:30: Error: PRIMARY KEY is not supported for external table\n"); - } - - Y_UNIT_TEST(DropExternalTable) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - DROP EXTERNAL TABLE MyExternalTable; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("tablescheme")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropExternalTableWithTablePrefix) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - pragma TablePathPrefix='/aba'; - DROP EXTERNAL TABLE MyExternalTable; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, "/aba/MyExternalTable"); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'tablescheme")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropExternalTableIfExists) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - DROP EXTERNAL TABLE IF EXISTS MyExternalTable; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("tablescheme")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("drop_if_exists")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } -} - -Y_UNIT_TEST_SUITE(TopicsDDL) { - void TestQuery(const TString& query, bool expectOk = true) { - TStringBuilder finalQuery; - - finalQuery << "use plato;" << Endl << query; - auto res = SqlToYql(finalQuery, 10, "kikimr"); - if (expectOk) { - UNIT_ASSERT_C(res.IsOk(), res.Issues.ToString()); - } else { - UNIT_ASSERT(!res.IsOk()); - } - } - - Y_UNIT_TEST(CreateTopicSimple) { - TestQuery(R"( - CREATE TOPIC topic1; - )"); - TestQuery(R"( - CREATE TOPIC `cluster1.topic1`; - )"); - TestQuery(R"( - CREATE TOPIC topic1 WITH (metering_mode = "str_value", partition_count_limit = 123, retention_period = Interval('PT1H')); - )"); - } - - Y_UNIT_TEST(CreateTopicConsumer) { - TestQuery(R"( - CREATE TOPIC topic1 (CONSUMER cons1); - )"); - TestQuery(R"( - CREATE TOPIC topic1 (CONSUMER cons1, CONSUMER cons2 WITH (important = false)); - )"); - TestQuery(R"( - CREATE TOPIC topic1 (CONSUMER cons1, CONSUMER cons2 WITH (important = false)) WITH (supported_codecs = "1,2,3"); - )"); - } - - Y_UNIT_TEST(AlterTopicSimple) { - TestQuery(R"( - ALTER TOPIC topic1 SET (retention_period = Interval('PT1H')); - )"); - TestQuery(R"( - ALTER TOPIC topic1 SET (retention_storage_mb = 3, partition_count_limit = 50); - )"); - TestQuery(R"( - ALTER TOPIC topic1 RESET (supported_codecs, retention_period); - )"); - TestQuery(R"( - ALTER TOPIC topic1 RESET (partition_write_speed_bytes_per_second), - SET (partition_write_burst_bytes = 11111, min_active_partitions = 1); - )"); - } - Y_UNIT_TEST(AlterTopicConsumer) { - TestQuery(R"( - ALTER TOPIC topic1 ADD CONSUMER consumer1, - ADD CONSUMER consumer2 WITH (important = false, supported_codecs = "RAW"), - ALTER CONSUMER consumer3 SET (important = false, read_from = 1), - ALTER CONSUMER consumer3 RESET (supported_codecs), - DROP CONSUMER consumer4, - SET (partition_count_limit = 11, retention_period = Interval('PT1H')), - RESET(metering_mode) - )"); - } - Y_UNIT_TEST(DropTopic) { - TestQuery(R"( - DROP TOPIC topic1; - )"); - } - - Y_UNIT_TEST(TopicBadRequests) { - TestQuery(R"( - CREATE TOPIC topic1(); - )", false); - TestQuery(R"( - CREATE TOPIC topic1 SET setting1 = value1; - )", false); - TestQuery(R"( - ALTER TOPIC topic1 SET setting1 value1; - )", false); - TestQuery(R"( - ALTER TOPIC topic1 RESET setting1; - )", false); - - TestQuery(R"( - ALTER TOPIC topic1 DROP CONSUMER consumer4 WITH (k1 = v1); - )", false); - - TestQuery(R"( - CREATE TOPIC topic1 WITH (retention_period = 123); - )", false); - TestQuery(R"( - CREATE TOPIC topic1 (CONSUMER cons1, CONSUMER cons1 WITH (important = false)); - )", false); - TestQuery(R"( - CREATE TOPIC topic1 (CONSUMER cons1 WITH (bad_option = false)); - )", false); - TestQuery(R"( - ALTER TOPIC topic1 ADD CONSUMER cons1, ALTER CONSUMER cons1 RESET (important); - )", false); - TestQuery(R"( - ALTER TOPIC topic1 ADD CONSUMER consumer1, - ALTER CONSUMER consumer3 SET (supported_codecs = "RAW", read_from = 1), - ALTER CONSUMER consumer3 RESET (supported_codecs); - )", false); - TestQuery(R"( - ALTER TOPIC topic1 ADD CONSUMER consumer1, - ALTER CONSUMER consumer3 SET (supported_codecs = "RAW", read_from = 1), - ALTER CONSUMER consumer3 SET (read_from = 2); - )", false); - } - - Y_UNIT_TEST(TopicWithPrefix) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - PRAGMA TablePathPrefix = '/database/path/to/tables'; - ALTER TOPIC `my_table/my_feed` ADD CONSUMER `my_consumer`; - )"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("/database/path/to/tables/my_table/my_feed"), 0}, {"topic", 0}}; - VerifyProgram(res, elementStat); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["topic"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["/database/path/to/tables/my_table/my_feed"]); - } -} - -Y_UNIT_TEST_SUITE(BlockEnginePragma) { - Y_UNIT_TEST(Basic) { - const TVector<TString> values = {"auto", "force", "disable"}; - for (const auto& value : values) { - const auto query = TStringBuilder() << "pragma Blockengine='" << value << "'; select 1;"; - NYql::TAstParseResult res = SqlToYql(query); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_STRING_CONTAINS(line, TStringBuilder() << R"(Configure! world (DataSource '"config") '"BlockEngine" '")" << value << "\""); - }; - - TWordCountHive elementStat({"BlockEngine"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["BlockEngine"] == ((value == "disable") ? 0 : 1)); - } - } - - Y_UNIT_TEST(UnknownSetting) { - ExpectFailWithError("use plato; pragma BlockEngine='foo';", - "<main>:1:31: Error: Expected `disable|auto|force' argument for: BlockEngine\n"); - } -} - -Y_UNIT_TEST_SUITE(TViewSyntaxTest) { - Y_UNIT_TEST(CreateViewSimple) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - CREATE VIEW TheView WITH (security_invoker = TRUE) AS SELECT 1; - )" - ); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - } - - Y_UNIT_TEST(CreateViewIfNotExists) { - constexpr const char* name = "TheView"; - NYql::TAstParseResult res = SqlToYql(std::format(R"( - USE plato; - CREATE VIEW IF NOT EXISTS {} WITH (security_invoker = TRUE) AS SELECT 1; - )", name - )); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_STRING_CONTAINS(line, name); - UNIT_ASSERT_STRING_CONTAINS(line, "createObjectIfNotExists"); - } - }; - - TWordCountHive elementStat = { {"Write!"} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); - } - - Y_UNIT_TEST(CreateViewFromTable) { - constexpr const char* path = "/PathPrefix/TheView"; - constexpr const char* query = R"( - SELECT * FROM SomeTable - )"; - - NYql::TAstParseResult res = SqlToYql(std::format(R"( - USE plato; - CREATE VIEW `{}` WITH (security_invoker = TRUE) AS {}; - )", - path, - query - ) - ); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_STRING_CONTAINS(line, path); - UNIT_ASSERT_STRING_CONTAINS(line, "createObject"); - } - }; - TWordCountHive elementStat = { {"Write!"} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); - } - - Y_UNIT_TEST(CheckReconstructedQuery) { - constexpr const char* path = "/PathPrefix/TheView"; - constexpr const char* query = R"( - SELECT * FROM FirstTable JOIN SecondTable ON FirstTable.key == SecondTable.key - )"; - - NYql::TAstParseResult res = SqlToYql(std::format(R"( - USE plato; - CREATE VIEW `{}` WITH (security_invoker = TRUE) AS {}; - )", - path, - query - ) - ); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TString reconstructedQuery = ToString(Tokenize(query)); - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - if (word == "query_text") { - UNIT_ASSERT_STRING_CONTAINS(line, reconstructedQuery); - } - }; - TWordCountHive elementStat = { {"Write!"} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); - } - - Y_UNIT_TEST(DropView) { - constexpr const char* path = "/PathPrefix/TheView"; - NYql::TAstParseResult res = SqlToYql(std::format(R"( - USE plato; - DROP VIEW `{}`; - )", - path - ) - ); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_STRING_CONTAINS(line, path); - UNIT_ASSERT_STRING_CONTAINS(line, "dropObject"); - } - }; - TWordCountHive elementStat = { {"Write!"} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); - } - - Y_UNIT_TEST(DropViewIfExists) { - constexpr const char* name = "TheView"; - NYql::TAstParseResult res = SqlToYql(std::format(R"( - USE plato; - DROP VIEW IF EXISTS {}; - )", name - )); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_STRING_CONTAINS(line, name); - UNIT_ASSERT_STRING_CONTAINS(line, "dropObjectIfExists"); - } - }; - - TWordCountHive elementStat = { {"Write!"} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); - } - - Y_UNIT_TEST(CreateViewWithTablePrefix) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - PRAGMA TablePathPrefix='/PathPrefix'; - CREATE VIEW TheView WITH (security_invoker = TRUE) AS SELECT 1; - )" - ); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_STRING_CONTAINS(line, "/PathPrefix/TheView"); - UNIT_ASSERT_STRING_CONTAINS(line, "createObject"); - } - }; - - TWordCountHive elementStat = { {"Write!"} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); - } - - Y_UNIT_TEST(DropViewWithTablePrefix) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - PRAGMA TablePathPrefix='/PathPrefix'; - DROP VIEW TheView; - )" - ); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, "/PathPrefix/TheView"); - UNIT_ASSERT_STRING_CONTAINS(line, "dropObject"); - } - }; - - TWordCountHive elementStat = { {"Write!"} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); - } - - Y_UNIT_TEST(YtAlternativeSchemaSyntax) { - NYql::TAstParseResult res = SqlToYql(R"( - SELECT * FROM plato.Input WITH schema(y Int32, x String not null); - )"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "userschema") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__('('('"userschema" (StructType '('"y" (AsOptionalType (DataType 'Int32))) '('"x" (DataType 'String))))))__")); - } - }; - - TWordCountHive elementStat = {{TString("userschema"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["userschema"]); - } - - Y_UNIT_TEST(UseViewAndFullColumnId) { - NYql::TAstParseResult res = SqlToYql("USE plato; SELECT Input.x FROM Input VIEW uitzicht;"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("SqlAccess"), 0}, {"SqlProjectItem", 0}, {"Read!", 0}}; - VerifyProgram(res, elementStat); - UNIT_ASSERT_VALUES_EQUAL(0, elementStat["SqlAccess"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Read!"]); - } -} - -Y_UNIT_TEST_SUITE(CompactNamedExprs) { - Y_UNIT_TEST(SourceCallablesInWrongContext) { - TString query = R"( - pragma CompactNamedExprs; - $foo = %s(); - select $foo from plato.Input; - )"; - - THashMap<TString, TString> errs = { - {"TableRow", "<main>:3:20: Error: TableRow requires data source\n"}, - {"JoinTableRow", "<main>:3:20: Error: JoinTableRow requires data source\n"}, - {"TableRecordIndex", "<main>:3:20: Error: Unable to use function: TableRecord without source\n"}, - {"TablePath", "<main>:3:20: Error: Unable to use function: TablePath without source\n"}, - {"SystemMetadata", "<main>:3:20: Error: Unable to use function: SystemMetadata without source\n"}, - }; - - for (TString callable : { "TableRow", "JoinTableRow", "TableRecordIndex", "TablePath", "SystemMetadata"}) { - auto req = Sprintf(query.c_str(), callable.c_str()); - ExpectFailWithError(req, errs[callable]); - } - } - - Y_UNIT_TEST(ValidateUnusedExprs) { - TString query = R"( - pragma warning("disable", "4527"); - pragma CompactNamedExprs; - pragma ValidateUnusedExprs; - - $foo = count(1); - select 1; - )"; - ExpectFailWithError(query, "<main>:6:20: Error: Aggregation is not allowed in this context\n"); - query = R"( - pragma warning("disable", "4527"); - pragma CompactNamedExprs; - pragma ValidateUnusedExprs; - - define subquery $x() as - select count(1, 2); - end define; - select 1; - )"; - ExpectFailWithError(query, "<main>:7:24: Error: Aggregation function Count requires exactly 1 argument(s), given: 2\n"); - } - - Y_UNIT_TEST(DisableValidateUnusedExprs) { - TString query = R"( - pragma warning("disable", "4527"); - pragma CompactNamedExprs; - pragma DisableValidateUnusedExprs; - - $foo = count(1); - select 1; - )"; - SqlToYql(query).IsOk(); - query = R"( - pragma warning("disable", "4527"); - pragma CompactNamedExprs; - pragma DisableValidateUnusedExprs; - - define subquery $x() as - select count(1, 2); - end define; - select 1; - )"; - SqlToYql(query).IsOk(); - } -} - -Y_UNIT_TEST_SUITE(ResourcePool) { - Y_UNIT_TEST(CreateResourcePool) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE RESOURCE POOL MyResourcePool WITH ( - CONCURRENT_QUERY_LIMIT=20, - QUERY_CANCEL_AFTER_SECONDS=86400, - QUEUE_TYPE="FIFO" - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"concurrent_query_limit" (Int32 '"20")) '('"query_cancel_after_seconds" (Int32 '"86400")) '('"queue_type" '"FIFO"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateResourcePoolWithBadArguments) { - ExpectFailWithError(R"sql( - USE plato; - CREATE RESOURCE POOL MyResourcePool; - )sql" , "<main>:3:51: Error: Unexpected token ';' : syntax error...\n\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE RESOURCE POOL MyResourcePool WITH ( - DUPLICATE_SETTING="first_value", - DUPLICATE_SETTING="second_value" - ); - )sql" , "<main>:5:21: Error: DUPLICATE_SETTING duplicate keys\n"); - } - - Y_UNIT_TEST(AlterResourcePool) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - ALTER RESOURCE POOL MyResourcePool - SET (CONCURRENT_QUERY_LIMIT = 30, Weight = 5, QUEUE_TYPE = "UNORDERED"), - RESET (Query_Cancel_After_Seconds, Query_Count_Limit); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alterObject))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('features '('('"concurrent_query_limit" (Int32 '"30")) '('"queue_type" '"UNORDERED") '('"weight" (Int32 '"5")))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('resetFeatures '('"query_cancel_after_seconds" '"query_count_limit")))#"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropResourcePool) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - DROP RESOURCE POOL MyResourcePool; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } -} - -Y_UNIT_TEST_SUITE(BackupCollection) { - Y_UNIT_TEST(CreateBackupCollection) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE BACKUP COLLECTION TestCollection WITH ( - STORAGE="local", - TAG="test" -- for testing purposes, not a real thing - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"storage" (String '"local")))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"tag" (String '"test"))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('entries '()))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'create")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateBackupCollectionWithDatabase) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE BACKUP COLLECTION TestCollection DATABASE WITH ( - STORAGE="local", - TAG="test" -- for testing purposes, not a real thing - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"storage" (String '"local")))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"tag" (String '"test"))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('entries '('('('type 'database)))))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'create")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateBackupCollectionWithTables) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE BACKUP COLLECTION TestCollection ( - TABLE someTable, - TABLE `prefix/anotherTable` - ) WITH ( - STORAGE="local", - TAG="test" -- for testing purposes, not a real thing - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"storage" (String '"local")))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"tag" (String '"test"))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('entries '('('('type 'table) '('path '"someTable")) '('('type 'table) '('path '"prefix/anotherTable")))))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'create")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateBackupCollectionWithBadArguments) { - ExpectFailWithError(R"sql( - USE plato; - CREATE BACKUP COLLECTION TestCollection; - )sql" , "<main>:3:55: Error: Unexpected token ';' : syntax error...\n\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE BACKUP COLLECTION TABLE TestCollection; - )sql" , "<main>:3:47: Error: Unexpected token 'TestCollection' : syntax error...\n\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE BACKUP COLLECTION DATABASE `test` TestCollection; - )sql" , "<main>:3:50: Error: Unexpected token '`test`' : syntax error...\n\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE BACKUP COLLECTION TestCollection WITH ( - DUPLICATE_SETTING="first_value", - DUPLICATE_SETTING="second_value" - ); - )sql" , "<main>:5:21: Error: DUPLICATE_SETTING duplicate keys\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE BACKUP COLLECTION TestCollection WITH ( - INT_SETTING=1 - ); - )sql" , "<main>:4:21: Error: INT_SETTING value should be a string literal\n"); - } - - Y_UNIT_TEST(AlterBackupCollection) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - ALTER BACKUP COLLECTION TestCollection - SET (STORAGE="remote"), -- also just for test - SET (TAG1 = "123"), - RESET (TAG2, TAG3); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alter))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"storage" (String '"remote")))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"tag1" (String '"123"))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('resetSettings '('"tag2" '"tag3")))#"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(AlterBackupCollectionEntries) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - ALTER BACKUP COLLECTION TestCollection - DROP TABLE `test`, - ADD DATABASE; - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alter))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('alterEntries)#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('type 'table) '('path '"test") '('action 'drop)))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('type 'database) '('action 'add)))#"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropBackupCollection) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - DROP BACKUP COLLECTION TestCollection; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("drop")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } -} - -Y_UNIT_TEST_SUITE(ResourcePoolClassifier) { - Y_UNIT_TEST(CreateResourcePoolClassifier) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( - RANK=20, - RESOURCE_POOL='wgUserQueries', - MEMBER_NAME='yandex_query@abc' - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"member_name" '"yandex_query@abc") '('"rank" (Int32 '"20")) '('"resource_pool" '"wgUserQueries"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateResourcePoolClassifierWithBadArguments) { - ExpectFailWithError(R"sql( - USE plato; - CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier; - )sql" , "<main>:3:72: Error: Unexpected token ';' : syntax error...\n\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( - DUPLICATE_SETTING="first_value", - DUPLICATE_SETTING="second_value" - ); - )sql" , "<main>:5:21: Error: DUPLICATE_SETTING duplicate keys\n"); - } - - Y_UNIT_TEST(AlterResourcePoolClassifier) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - ALTER RESOURCE POOL CLASSIFIER MyResourcePoolClassifier - SET (RANK = 30, Weight = 5, MEMBER_NAME = "test@user"), - RESET (Resource_Pool); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alterObject))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('features '('('"member_name" '"test@user") '('"rank" (Int32 '"30")) '('"weight" (Int32 '"5")))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('resetFeatures '('"resource_pool")))#"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropResourcePoolClassifier) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - DROP RESOURCE POOL CLASSIFIER MyResourcePoolClassifier; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(BacktickMatching) { - auto req = "select\n" - " 1 as `Schema has \\`RealCost\\``\n" - " -- foo`bar"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - - req = "select 1 as `a``b`, 2 as ````, 3 as `\\x60a\\x60`, 4 as ```b```, 5 as `\\`c\\``"; - res = SqlToYql(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - } -} - -Y_UNIT_TEST_SUITE(OlapPartitionCount) { - Y_UNIT_TEST(CorrectUsage) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE TABLE `mytable` (id Uint32, PRIMARY KEY (id)) - PARTITION BY HASH(id) - WITH (STORE = COLUMN, PARTITION_COUNT = 8); - )sql"); - - UNIT_ASSERT_C(res.IsOk(), res.Issues.ToString()); - } - - Y_UNIT_TEST(UseWithoutColumnStore) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE TABLE `mytable` (id Uint32, PRIMARY KEY (id)) - WITH (PARTITION_COUNT = 8); - )sql"); - - UNIT_ASSERT(!res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 1); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "PARTITION_COUNT can be used only with STORE=COLUMN"); - } -} - -Y_UNIT_TEST_SUITE(Backup) { - Y_UNIT_TEST(Simple) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - BACKUP TestCollection; - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'Incremental")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'backup")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(Incremental) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - BACKUP TestCollection INCREMENTAL; - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'backupIncremental")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } -} - -Y_UNIT_TEST_SUITE(Restore) { - Y_UNIT_TEST(Simple) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - RESTORE TestCollection; - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'restore")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(AtPoint) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - RESTORE TestCollection AT '2024-06-16_20-14-02'; - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('at '"2024-06-16_20-14-02")#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'restore")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } -} - -Y_UNIT_TEST_SUITE(ColumnFamily) { - Y_UNIT_TEST(CompressionLevelCorrectUsage) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - CREATE TABLE tableName ( - Key Uint32 FAMILY default, - Value String FAMILY family1, - PRIMARY KEY (Key), - FAMILY default ( - DATA = "test", - COMPRESSION = "lz4", - COMPRESSION_LEVEL = 5 - ), - FAMILY family1 ( - DATA = "test", - COMPRESSION = "lz4", - COMPRESSION_LEVEL = 3 - ) - ); - )"); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("compression_level")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("5")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("3")); - } - }; - - TWordCountHive elementStat = { { TString("Write"), 0 }, { TString("compression_level"), 0 } }; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["compression_level"]); - } - - Y_UNIT_TEST(FieldDataIsNotString) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - CREATE TABLE tableName ( - Key Uint32 FAMILY default, - PRIMARY KEY (Key), - FAMILY default ( - DATA = 1, - COMPRESSION = "lz4", - COMPRESSION_LEVEL = 5 - ) - ); - )"); - UNIT_ASSERT(!res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 1); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "DATA value should be a string literal"); - } - - Y_UNIT_TEST(FieldCompressionIsNotString) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - CREATE TABLE tableName ( - Key Uint32 FAMILY default, - PRIMARY KEY (Key), - FAMILY default ( - DATA = "test", - COMPRESSION = 2, - COMPRESSION_LEVEL = 5 - ), - ); - )"); - UNIT_ASSERT(!res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 1); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "COMPRESSION value should be a string literal"); - } - - Y_UNIT_TEST(FieldCompressionLevelIsNotInteger) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - CREATE TABLE tableName ( - Key Uint32 FAMILY default, - PRIMARY KEY (Key), - FAMILY default ( - DATA = "test", - COMPRESSION = "lz4", - COMPRESSION_LEVEL = "5" - ) - ); - )"); - UNIT_ASSERT(!res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 1); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "COMPRESSION_LEVEL value should be an integer"); - } - - Y_UNIT_TEST(AlterCompressionCorrectUsage) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION "lz4"; - )"); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - } - - Y_UNIT_TEST(AlterCompressionFieldIsNotString) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION lz4; - )"); - UNIT_ASSERT(!res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 1); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "Unexpected token 'lz4' : cannot match to any predicted input"); - } - - Y_UNIT_TEST(AlterCompressionLevelCorrectUsage) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION_LEVEL 5; - )"); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - } - - Y_UNIT_TEST(AlterCompressionLevelFieldIsNotInteger) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION_LEVEL "5"; - )"); - UNIT_ASSERT(!res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 1); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "COMPRESSION_LEVEL value should be an integer"); - } -} +#define ANTLR_VER 3 +#include "sql_ut_common.h" Y_UNIT_TEST_SUITE(QuerySplit) { Y_UNIT_TEST(Simple) { @@ -8207,42 +95,3 @@ Y_UNIT_TEST_SUITE(QuerySplit) { };)"); } } - -Y_UNIT_TEST_SUITE(MatchRecognizeMeasuresAggregation) { - Y_UNIT_TEST(InsideSelect) { - ExpectFailWithError(R"sql( - SELECT FIRST(0), LAST(1); - )sql", - "<main>:2:20: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - "<main>:2:30: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - ); - } - - Y_UNIT_TEST(OutsideSelect) { - ExpectFailWithError(R"sql( - $lambda = ($x) -> (FIRST($x) + LAST($x)); - SELECT $lambda(x) FROM plato.Input; - )sql", - "<main>:2:32: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - "<main>:2:44: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - ); - } - - Y_UNIT_TEST(AsAggregateFunction) { - ExpectFailWithError(R"sql( - SELECT FIRST(x), LAST(x) FROM plato.Input; - )sql", - "<main>:2:20: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - "<main>:2:30: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - ); - } - - Y_UNIT_TEST(AsWindowFunction) { - ExpectFailWithError(R"sql( - SELECT FIRST(x) OVER(), LAST(x) OVER() FROM plato.Input; - )sql", - "<main>:2:20: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - "<main>:2:37: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - ); - } -} diff --git a/yql/essentials/sql/v1/sql_ut_antlr4.cpp b/yql/essentials/sql/v1/sql_ut_antlr4.cpp index 6d54a4f409..18480d048c 100644 --- a/yql/essentials/sql/v1/sql_ut_antlr4.cpp +++ b/yql/essentials/sql/v1/sql_ut_antlr4.cpp @@ -30,8093 +30,10 @@ TParsedTokenList Tokenize(const TString& query) { return tokens; } -TString ToString(const TParsedTokenList& tokens) { - TStringBuilder reconstructedQuery; - for (const auto& token : tokens) { - if (token.Name == "WS" || token.Name == "EOF") { - continue; - } - if (!reconstructedQuery.empty()) { - reconstructedQuery << ' '; - } - reconstructedQuery << token.Content; - } - return reconstructedQuery; -} - -} - -Y_UNIT_TEST_SUITE(AnsiMode) { - Y_UNIT_TEST(PragmaAnsi) { - UNIT_ASSERT(SqlToYql("PRAGMA ANSI 2016;").IsOk()); - } -} - -Y_UNIT_TEST_SUITE(SqlParsingOnly) { - ///This function is used in BACKWARD COMPATIBILITY tests below that LIMIT the sets of token that CAN NOT be used - ///as identifiers in different contexts in a SQL request - ///\return list of tokens that failed this check - TVector<TString> ValidateTokens(const THashSet<TString>& forbidden, const std::function<TString (const TString& )>& makeRequest) { - THashMap<TString, bool> allTokens; - for (const auto& t: NSQLFormat::GetKeywords()) { - allTokens[t] = !forbidden.contains((t)); - } - for (const auto& f: forbidden) { - UNIT_ASSERT(allTokens.contains(f)); //check that forbidden list contains tokens only(argument check) - } - TVector<TString> failed; - for (const auto& [token, allowed]: allTokens) { - if (SqlToYql(makeRequest(token)).IsOk() != allowed) - failed.push_back(token); - } - return failed; - } - - Y_UNIT_TEST(TokensAsColumnName) { //id_expr - auto failed = ValidateTokens({ - "ALL", "ANY", "AS", "ASSUME", "ASYMMETRIC", "AUTOMAP", "BETWEEN", "BITCAST", - "CALLABLE", "CASE", "CAST", "CUBE", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", - "DICT", "DISTINCT", "ENUM", "ERASE", "EXCEPT", "EXISTS", "FLOW", "FROM", "FULL", - "HAVING", "HOP", "INTERSECT", "JSON_EXISTS", "JSON_QUERY", "JSON_VALUE", "LIMIT", "LIST", - "NOT", "OPTIONAL", "PROCESS", "REDUCE", "REPEATABLE", "RESOURCE", "RETURN", "RETURNING", "ROLLUP", - "SELECT", "SET", "STREAM", "STRUCT", "SYMMETRIC", "TAGGED", "TUPLE", "UNBOUNDED", - "UNION", "VARIANT", "WHEN", "WHERE", "WINDOW", "WITHOUT" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT " << token << " FROM Plato.Input"; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsWithoutColumnName) { //id_without - auto failed = ValidateTokens({ - "ALL", "AS", "ASSUME", "ASYMMETRIC", "AUTOMAP", "BETWEEN", "BITCAST", - "CALLABLE", "CASE", "CAST", "CUBE", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", - "DICT", "DISTINCT", "EMPTY_ACTION", "ENUM", "EXCEPT", "EXISTS", "FALSE", "FLOW", "FROM", "FULL", - "HAVING", "HOP", "INTERSECT", "JSON_EXISTS", "JSON_QUERY", "JSON_VALUE", "LIMIT", "LIST", - "NOT", "NULL", "OPTIONAL", "PROCESS", "REDUCE", "REPEATABLE", "RESOURCE", "RETURN", "RETURNING", "ROLLUP", - "SELECT", "SET", "STRUCT", "SYMMETRIC", "TAGGED", "TRUE", "TUPLE", "UNBOUNDED", - "UNION", "VARIANT", "WHEN", "WHERE", "WINDOW", "WITHOUT" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT * WITHOUT " << token << " FROM Plato.Input"; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsColumnNameInAddColumn) { //id_schema - auto failed = ValidateTokens({ - "ANY", "AUTOMAP", "CALLABLE", "COLUMN", "DICT", "ENUM", "ERASE", "FALSE", "FLOW", - "LIST", "OPTIONAL", "REPEATABLE", "RESOURCE", - "SET", "STREAM", "STRUCT", "TAGGED", "TRUE", "TUPLE", "VARIANT" - }, - [](const TString& token){ - TStringBuilder req; - req << "ALTER TABLE Plato.Input ADD COLUMN " << token << " Bool"; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsColumnAlias) { - auto failed = ValidateTokens({ - "AUTOMAP", "FALSE", - "REPEATABLE", "TRUE" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT Col as " << token << " FROM Plato.Input"; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsTableName) { //id_table_or_type - auto failed = ValidateTokens({ - "ANY", "AUTOMAP", "COLUMN", "ERASE", "FALSE", - "REPEATABLE", "STREAM", "TRUE" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT * FROM Plato." << token; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsTableAlias) { //id_table - auto failed = ValidateTokens({ - "AUTOMAP", "CALLABLE", "DICT", "ENUM","FALSE", "FLOW", - "LIST", "OPTIONAL", "REPEATABLE", "RESOURCE", - "SET", "STRUCT", "TAGGED", "TRUE", "TUPLE", "VARIANT" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT * FROM Plato.Input AS " << token; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsHints) { //id_hint - auto failed = ValidateTokens({ - "AUTOMAP", "CALLABLE", "COLUMNS", "DICT", "ENUM", "FALSE", "FLOW", - "LIST", "OPTIONAL", "REPEATABLE", "RESOURCE", - "SCHEMA", "SET", "STRUCT", "TAGGED", "TRUE", "TUPLE", "VARIANT" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT * FROM Plato.Input WITH " << token; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsWindow) { //id_window - auto failed = ValidateTokens({ - "AUTOMAP", "CALLABLE", "DICT", "ENUM", "FALSE", "FLOW", "GROUPS", "LIST", "OPTIONAL", - "RANGE", "REPEATABLE", "RESOURCE", "ROWS", "SET", "STRUCT", "TAGGED" ,"TRUE", "TUPLE", "VARIANT" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT * FROM Plato.Input WINDOW " << token << " AS ()"; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TokensAsIdExprIn) { //id_expr_in - auto failed = ValidateTokens({ - "ALL", "ANY", "AS", "ASSUME", "ASYMMETRIC", "AUTOMAP", "BETWEEN", "BITCAST", - "CALLABLE", "CASE", "CAST", "COMPACT", "CUBE", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", - "DICT", "DISTINCT", "ENUM", "ERASE", "EXCEPT", "EXISTS", "FLOW", "FROM", "FULL", - "HAVING", "HOP", "INTERSECT", "JSON_EXISTS", "JSON_QUERY", "JSON_VALUE", "LIMIT", "LIST", - "NOT", "OPTIONAL", "PROCESS", "REDUCE", "REPEATABLE", "RESOURCE", "RETURN", "RETURNING", "ROLLUP", - "SELECT", "SET", "STREAM", "STRUCT", "SYMMETRIC", "TAGGED", "TUPLE", "UNBOUNDED", - "UNION", "VARIANT", "WHEN", "WHERE", "WINDOW", "WITHOUT" - }, - [](const TString& token){ - TStringBuilder req; - req << "SELECT * FROM Plato.Input WHERE q IN " << token; - return req; - } - ); - UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); - } - - Y_UNIT_TEST(TableHints) { - UNIT_ASSERT(SqlToYql("SELECT * FROM plato.Input WITH INFER_SCHEMA").IsOk()); - UNIT_ASSERT(SqlToYql("SELECT * FROM plato.Input WITH (INFER_SCHEMA)").IsOk()); - } - - Y_UNIT_TEST(InNoHints) { - TString query = "SELECT * FROM plato.Input WHERE key IN (1,2,3)"; - - VerifySqlInHints(query, { "'('('warnNoAnsi))" }, {}); - VerifySqlInHints(query, { "'()" }, false); - VerifySqlInHints(query, { "'('('ansi))" }, true); - } - - Y_UNIT_TEST(InHintCompact) { - // should parse COMPACT as hint - TString query = "SELECT * FROM plato.Input WHERE key IN COMPACT(1, 2, 3)"; - - VerifySqlInHints(query, { "'('isCompact)" }); - } - - Y_UNIT_TEST(InHintSubquery) { - // should parse tableSource as hint - TString query = "$subq = (SELECT key FROM plato.Input); SELECT * FROM plato.Input WHERE key IN $subq"; - - VerifySqlInHints(query, { "'('tableSource)" }); - } - - Y_UNIT_TEST(InHintCompactSubquery) { - TString query = "$subq = (SELECT key FROM plato.Input); SELECT * FROM plato.Input WHERE key IN COMPACT $subq"; - - VerifySqlInHints(query, { "'('isCompact)", "'('tableSource)" }); - } - - Y_UNIT_TEST(CompactKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("SELECT COMPACT FROM plato.Input WHERE COMPACT IN COMPACT(1, 2, 3)").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT * FROM COMPACT").IsOk()); - } - - Y_UNIT_TEST(FamilyKeywordNotReservedForNames) { - // FIXME: check if we can get old behaviour - //UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE FAMILY (FAMILY Uint32, PRIMARY KEY (FAMILY));").IsOk()); - //UNIT_ASSERT(SqlToYql("USE plato; SELECT FAMILY FROM FAMILY").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT FAMILY FROM Input").IsOk()); - } - - Y_UNIT_TEST(ResetKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE RESET (RESET Uint32, PRIMARY KEY (RESET));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT RESET FROM RESET").IsOk()); - } - - Y_UNIT_TEST(SyncKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE SYNC (SYNC Uint32, PRIMARY KEY (SYNC));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT SYNC FROM SYNC").IsOk()); - } - - Y_UNIT_TEST(AsyncKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE ASYNC (ASYNC Uint32, PRIMARY KEY (ASYNC));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT ASYNC FROM ASYNC").IsOk()); - } - - Y_UNIT_TEST(DisableKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE DISABLE (DISABLE Uint32, PRIMARY KEY (DISABLE));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT DISABLE FROM DISABLE").IsOk()); - } - - Y_UNIT_TEST(ChangefeedKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE CHANGEFEED (CHANGEFEED Uint32, PRIMARY KEY (CHANGEFEED));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT CHANGEFEED FROM CHANGEFEED").IsOk()); - } - - Y_UNIT_TEST(ReplicationKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE REPLICATION (REPLICATION Uint32, PRIMARY KEY (REPLICATION));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT REPLICATION FROM REPLICATION").IsOk()); - } - - Y_UNIT_TEST(TransferKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE TRANSFER (TRANSFER Uint32, PRIMARY KEY (TRANSFER));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT TRANSFER FROM TRANSFER").IsOk()); - } - - Y_UNIT_TEST(SecondsKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE SECONDS (SECONDS Uint32, PRIMARY KEY (SECONDS));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT SECONDS FROM SECONDS").IsOk()); - } - - Y_UNIT_TEST(MillisecondsKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE MILLISECONDS (MILLISECONDS Uint32, PRIMARY KEY (MILLISECONDS));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT MILLISECONDS FROM MILLISECONDS").IsOk()); - } - - Y_UNIT_TEST(MicrosecondsKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE MICROSECONDS (MICROSECONDS Uint32, PRIMARY KEY (MICROSECONDS));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT MICROSECONDS FROM MICROSECONDS").IsOk()); - } - - Y_UNIT_TEST(NanosecondsKeywordNotReservedForNames) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE NANOSECONDS (NANOSECONDS Uint32, PRIMARY KEY (NANOSECONDS));").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT NANOSECONDS FROM NANOSECONDS").IsOk()); - } - - Y_UNIT_TEST(Jubilee) { - NYql::TAstParseResult res = SqlToYql("USE plato; INSERT INTO Arcadia (r2000000) VALUES (\"2M GET!!!\");"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(QualifiedAsteriskBefore) { - NYql::TAstParseResult res = SqlToYql( - "PRAGMA DisableSimpleColumns;" - "select interested_table.*, LENGTH(value) AS megahelpful_len from plato.Input as interested_table;" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - static bool seenStar = false; - if (word == "FlattenMembers") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("interested_table.")); - } else if (word == "SqlProjectItem") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("megahelpful_len"))); - UNIT_ASSERT_VALUES_EQUAL(seenStar, true); - } else if (word == "SqlProjectStarItem") { - seenStar = true; - } - }; - TWordCountHive elementStat = {{TString("FlattenMembers"), 0}, {TString("SqlProjectItem"), 0}, {TString("SqlProjectStarItem"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["FlattenMembers"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectStarItem"]); - } - - Y_UNIT_TEST(QualifiedAsteriskAfter) { - NYql::TAstParseResult res = SqlToYql( - "PRAGMA DisableSimpleColumns;" - "select LENGTH(value) AS megahelpful_len, interested_table.* from plato.Input as interested_table;" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - static bool seenStar = false; - if (word == "FlattenMembers") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("interested_table.")); - } else if (word == "SqlProjectItem") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("megahelpful_len"))); - UNIT_ASSERT_VALUES_EQUAL(seenStar, false); - } else if (word == "SqlProjectStarItem") { - seenStar = true; - } - }; - TWordCountHive elementStat = {{TString("FlattenMembers"), 0}, {TString("SqlProjectItem"), 0}, {TString("SqlProjectStarItem"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["FlattenMembers"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectStarItem"]); - } - - Y_UNIT_TEST(QualifiedMembers) { - NYql::TAstParseResult res = SqlToYql("select interested_table.key, interested_table.value from plato.Input as interested_table;"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - const bool fieldKey = TString::npos != line.find(Quote("key")); - const bool fieldValue = TString::npos != line.find(Quote("value")); - const bool refOnTable = TString::npos != line.find("interested_table."); - if (word == "SqlProjectItem") { - UNIT_ASSERT(fieldKey || fieldValue); - UNIT_ASSERT(!refOnTable); - } else if (word == "Write!") { - UNIT_ASSERT(fieldKey && fieldValue && !refOnTable); - } - }; - TWordCountHive elementStat = {{TString("SqlProjectStarItem"), 0}, {TString("SqlProjectItem"), 0}, {TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(0, elementStat["SqlProjectStarItem"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(ExplainQueryPlan) { - UNIT_ASSERT(SqlToYql("EXPLAIN SELECT 1;").IsOk()); - UNIT_ASSERT(SqlToYql("EXPLAIN QUERY PLAN SELECT 1;").IsOk()); - } - - Y_UNIT_TEST(JoinParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - "PRAGMA DisableSimpleColumns;" - " SELECT table_bb.*, table_aa.key as megakey" - " FROM plato.Input AS table_aa" - " JOIN plato.Input AS table_bb" - " ON table_aa.value == table_bb.value;" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "SelectMembers") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("table_aa.")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table_bb.")); - } else if (word == "SqlProjectItem") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("megakey"))); - } else if (word == "SqlColumn") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("table_aa"))); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("key"))); - } - }; - TWordCountHive elementStat = {{TString("SqlProjectItem"), 0}, {TString("SqlProjectStarItem"), 0}, {TString("SelectMembers"), 0}, {TString("SqlColumn"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectStarItem"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SelectMembers"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlColumn"]); - } - - Y_UNIT_TEST(Join3Table) { - NYql::TAstParseResult res = SqlToYql( - " PRAGMA DisableSimpleColumns;" - " SELECT table_bb.*, table_aa.key as gigakey, table_cc.* " - " FROM plato.Input AS table_aa" - " JOIN plato.Input AS table_bb ON table_aa.key == table_bb.key" - " JOIN plato.Input AS table_cc ON table_aa.subkey == table_cc.subkey;" - ); - Err2Str(res); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "SelectMembers") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("table_aa.")); - UNIT_ASSERT(line.find("table_bb.") != TString::npos || line.find("table_cc.") != TString::npos); - } else if (word == "SqlProjectItem") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("gigakey"))); - } else if (word == "SqlColumn") { - const auto posTableAA = line.find(Quote("table_aa")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, posTableAA); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("key"))); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("table_aa", posTableAA + 3)); - } - }; - TWordCountHive elementStat = {{TString("SqlProjectItem"), 0}, {TString("SqlProjectStarItem"), 0}, {TString("SelectMembers"), 0}, {TString("SqlColumn"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlProjectStarItem"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SelectMembers"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlColumn"]); - } - - Y_UNIT_TEST(DisabledJoinCartesianProduct) { - NYql::TAstParseResult res = SqlToYql("pragma DisableAnsiImplicitCrossJoin; use plato; select * from A,B,C"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:67: Error: Cartesian product of tables is disabled. Please use explicit CROSS JOIN or enable it via PRAGMA AnsiImplicitCrossJoin\n"); - } - - Y_UNIT_TEST(JoinCartesianProduct) { - NYql::TAstParseResult res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; select * from A,B,C"); - UNIT_ASSERT(res.Root); - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "EquiJoin") { - auto pos = line.find("Cross"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, pos); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Cross", pos + 1)); - } - }; - TWordCountHive elementStat = {{TString("EquiJoin"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["EquiJoin"]); - } - - Y_UNIT_TEST(CreateAlterUserWithLoginNoLogin) { - auto reqCreateUser = SqlToYql(R"( - USE plato; - CREATE USER user1; - )"); - - UNIT_ASSERT(reqCreateUser.IsOk()); - - auto reqAlterUser = SqlToYql(R"( - USE plato; - ALTER USER user1; - )"); - - UNIT_ASSERT(!reqAlterUser.IsOk()); - UNIT_ASSERT_STRING_CONTAINS(reqAlterUser.Issues.ToString(), "Error: mismatched input ';' expecting {ENCRYPTED, HASH, LOGIN, NOLOGIN, PASSWORD, RENAME, WITH}"); - - auto reqPasswordAndLogin = SqlToYql(R"( - USE plato; - CREATE USER user1 LOgin; - )"); - - UNIT_ASSERT(reqPasswordAndLogin.IsOk()); - - auto reqPasswordAndNoLogin = SqlToYql(R"( - USE plato; - CREATE USER user1 PASSWORD '123' NOLOGIN; - )"); - - UNIT_ASSERT(reqPasswordAndNoLogin.IsOk()); - - auto reqLogin = SqlToYql(R"( - USE plato; - CREATE USER user1 LOGIN; - )"); - - UNIT_ASSERT(reqLogin.IsOk()); - - auto reqNoLogin = SqlToYql(R"( - USE plato; - CREATE USER user1 NOLOGIN; - )"); - - UNIT_ASSERT(reqNoLogin.IsOk()); - - auto reqLoginNoLogin = SqlToYql(R"( - USE plato; - CREATE USER user1 LOGIN NOLOGIN; - )"); - - UNIT_ASSERT(!reqLoginNoLogin.IsOk()); - UNIT_ASSERT_STRING_CONTAINS(reqLoginNoLogin.Issues.ToString(), "Error: Conflicting or redundant options"); - - auto reqAlterLoginNoLogin = SqlToYql(R"( - USE plato; - CREATE USER user1 LOGIN; - ALTER USER user1 NOLOGIN; - )"); - - UNIT_ASSERT(reqAlterLoginNoLogin.IsOk()); - - auto reqAlterLoginNoLoginWithPassword = SqlToYql(R"( - USE plato; - CREATE USER user1 LOGIN; - ALTER USER user1 PASSWORD '321' NOLOGIN; - )"); - - UNIT_ASSERT(reqAlterLoginNoLoginWithPassword.IsOk()); - } - - Y_UNIT_TEST(CreateUserWithHash) { - auto reqCreateUser = SqlToYql(R"( - USE plato; - CREATE USER user1 HASH '{ - "hash": "p4ffeMugohqyBwyckYCK1TjJfz3LIHbKiGL+t+oEhzw=", - "salt": "U+tzBtgo06EBQCjlARA6Jg==", - "type": "argon2id" - }'; - )"); - - UNIT_ASSERT(reqCreateUser.IsOk()); - - auto reqCreateUserWithNoLogin = SqlToYql(R"( - USE plato; - CREATE USER user1 HASH '{ - "hash": "p4ffeMugohqyBwyckYCK1TjJfz3LIHbKiGL+t+oEhzw=", - "salt": "U+tzBtgo06EBQCjlARA6Jg==", - "type": "argon2id" - }' - NOLOGIN; - )"); - - UNIT_ASSERT(reqCreateUserWithNoLogin.IsOk()); - - auto reqCreateUserWithPassword = SqlToYql(R"( - USE plato; - CREATE USER user1 HASH '{ - "hash": "p4ffeMugohqyBwyckYCK1TjJfz3LIHbKiGL+t+oEhzw=", - "salt": "U+tzBtgo06EBQCjlARA6Jg==", - "type": "argon2id" - }' - PASSWORD '123'; - )"); - - UNIT_ASSERT(!reqCreateUserWithPassword.IsOk()); - UNIT_ASSERT_STRING_CONTAINS(reqCreateUserWithPassword.Issues.ToString(), "Error: Conflicting or redundant options"); - - auto reqAlterUser = SqlToYql(R"( - USE plato; - CREATE USER user1; - ALTER USER user1 HASH '{ - "hash": "p4ffeMugohqyBwyckYCK1TjJfz3LIHbKiGL+t+oEhzw=", - "salt": "U+tzBtgo06EBQCjlARA6Jg==", - "type": "argon2id" - }'; - )"); - - UNIT_ASSERT(reqAlterUser.IsOk()); - } - - Y_UNIT_TEST(JoinWithoutConcreteColumns) { - NYql::TAstParseResult res = SqlToYql( - " use plato;" - " SELECT a.v, b.value" - " FROM `Input1` VIEW `ksv` AS a" - " JOIN `Input2` AS b" - " ON a.k == b.key;" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "SqlProjectItem") { - UNIT_ASSERT(line.find(Quote("a.v")) != TString::npos || line.find(Quote("b.value")) != TString::npos); - } else if (word == "SqlColumn") { - const auto posTableA = line.find(Quote("a")); - const auto posTableB = line.find(Quote("b")); - if (posTableA != TString::npos) { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("v"))); - } else { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, posTableB); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("value"))); - } - } - }; - TWordCountHive elementStat = {{TString("SqlProjectStarItem"), 0}, {TString("SqlProjectItem"), 0}, {TString("SqlColumn"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(0, elementStat["SqlProjectStarItem"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlColumn"]); - } - - Y_UNIT_TEST(JoinWithSameValues) { - NYql::TAstParseResult res = SqlToYql("SELECT a.value, b.value FROM plato.Input AS a JOIN plato.Input as b ON a.key == b.key;"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "SqlProjectItem") { - const bool isValueFromA = TString::npos != line.find(Quote("a.value")); - const bool isValueFromB = TString::npos != line.find(Quote("b.value")); - UNIT_ASSERT(isValueFromA || isValueFromB); - } if (word == "Write!") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("a.a.")); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("b.b.")); - } - }; - TWordCountHive elementStat = {{TString("SqlProjectStarItem"), 0}, {TString("SqlProjectItem"), 0}, {"Write!", 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(0, elementStat["SqlProjectStarItem"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(SameColumnsForDifferentTables) { - NYql::TAstParseResult res = SqlToYql("SELECT a.key, b.key FROM plato.Input as a JOIN plato.Input as b on a.key==b.key;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SameColumnsForDifferentTablesFullJoin) { - NYql::TAstParseResult res = SqlToYql("SELECT a.key, b.key, a.value, b.value FROM plato.Input AS a FULL JOIN plato.Input AS b USING(key);"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(JoinStreamLookupStrategyHint) { - { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ StreamLookup() */ plato.Input AS b USING(key);"); - UNIT_ASSERT(res.Root); - } - //case insensitive - { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ streamlookup() */ plato.Input AS b USING(key);"); - UNIT_ASSERT(res.Root); - } - } - - Y_UNIT_TEST(JoinConflictingStrategyHint) { - { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ StreamLookup() */ /*+ Merge() */ plato.Input AS b USING(key);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:91: Error: Conflicting join strategy hints\n"); - } - } - - Y_UNIT_TEST(JoinDuplicatingStrategyHint) { - { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ StreamLookup() */ /*+ StreamLookup() */ plato.Input AS b USING(key);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:98: Error: Duplicate join strategy hint\n"); - } - } - - Y_UNIT_TEST(WarnCrossJoinStrategyHint) { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a CROSS JOIN /*+ merge() */ plato.Input AS b;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:32: Warning: Non-default join strategy will not be used for CROSS JOIN, code: 4534\n"); - } - - Y_UNIT_TEST(WarnCartesianProductStrategyHint) { - NYql::TAstParseResult res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; SELECT * FROM A, /*+ merge() */ B;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:74: Warning: Non-default join strategy will not be used for CROSS JOIN, code: 4534\n"); - } - - Y_UNIT_TEST(WarnUnknownJoinStrategyHint) { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ xmerge() */ plato.Input AS b USING (key);"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:41: Warning: Unsupported join hint: xmerge, code: 4534\n"); - } - - Y_UNIT_TEST(ReverseLabels) { - NYql::TAstParseResult res = SqlToYql("select in.key as subkey, subkey as key from plato.Input as in;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(AutogenerationAliasWithoutCollisionConflict1) { - NYql::TAstParseResult res = SqlToYql("select LENGTH(Value), key as column1 from plato.Input;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(AutogenerationAliasWithoutCollision2Conflict2) { - NYql::TAstParseResult res = SqlToYql("select key as column0, LENGTH(Value) from plato.Input;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(InputAliasForQualifiedAsterisk) { - NYql::TAstParseResult res = SqlToYql("use plato; select zyuzya.*, key from plato.Input as zyuzya;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectSupportsResultColumnsWithTrailingComma) { - NYql::TAstParseResult res = SqlToYql("select a, b, c, from plato.Input;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectOrderByLabeledColumn) { - NYql::TAstParseResult res = SqlToYql("pragma DisableOrderedColumns; select key as goal from plato.Input order by goal"); - UNIT_ASSERT(res.Root); - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "DataSource") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("plato")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Input")); - - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("goal")); - } else if (word == "Sort") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("goal")); - - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("key")); - } - }; - TWordCountHive elementStat = {{TString("DataSource"), 0}, {TString("Sort"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["DataSource"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Sort"]); - } - - Y_UNIT_TEST(SelectOrderBySimpleExpr) { - NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by a + a"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectAssumeOrderByTableRowAccess) { - NYql::TAstParseResult res = SqlToYql("$key = 'foo';select * from plato.Input assume order by TableRow().$key"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectOrderByDuplicateLabels) { - NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by a, a"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectOrderByExpression) { - NYql::TAstParseResult res = SqlToYql("select * from plato.Input as i order by cast(key as uint32) + cast(subkey as uint32)"); - UNIT_ASSERT(res.Root); - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Sort") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"+MayWarn\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("key")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("subkey")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Bool 'true)")); - - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("i.key")); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("i.subkey")); - } - }; - TWordCountHive elementStat = {{TString("Sort"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Sort"]); - } - - Y_UNIT_TEST(SelectOrderByExpressionDesc) { - NYql::TAstParseResult res = SqlToYql("pragma disablesimplecolumns; select i.*, key, subkey from plato.Input as i order by cast(i.key as uint32) - cast(i.subkey as uint32) desc"); - UNIT_ASSERT(res.Root); - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Sort") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"-MayWarn\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"key\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"subkey\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Bool 'false)")); - } else if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'columns")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"key\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"subkey\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("prefix")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"i.\"")); - } - }; - TWordCountHive elementStat = {{TString("Sort"), 0}, {TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Sort"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(SelectOrderByExpressionAsc) { - NYql::TAstParseResult res = SqlToYql("select i.key, i.subkey from plato.Input as i order by cast(key as uint32) % cast(i.subkey as uint32) asc"); - UNIT_ASSERT(res.Root); - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Sort") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"%MayWarn\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"key\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"subkey\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Bool 'true)")); - } else if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'columns")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"key\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"subkey\"")); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("i.")); - } - }; - TWordCountHive elementStat = {{TString("Sort"), 0}, {TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Sort"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(ReferenceToKeyInSubselect) { - NYql::TAstParseResult res = SqlToYql("select b.key from (select a.key from plato.Input as a) as b;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(OrderByCastValue) { - NYql::TAstParseResult res = SqlToYql("select i.key, i.subkey from plato.Input as i order by cast(key as uint32) desc;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(GroupByCastValue) { - NYql::TAstParseResult res = SqlToYql("select count(1) from plato.Input as i group by cast(key as uint8);"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(KeywordInSelectColumns) { - NYql::TAstParseResult res = SqlToYql("select in, s.check from (select 1 as in, \"test\" as check) as s;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectAllGroupBy) { - NYql::TAstParseResult res = SqlToYql("select * from plato.Input group by subkey;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(CreateObjectWithFeatures) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT secretId (TYPE SECRET) WITH (Key1=Value1, K2=V2);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('\"K2\" '\"V2\") '('\"Key1\" '\"Value1\")")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(CreateObjectIfNotExists) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT IF NOT EXISTS secretId (TYPE SECRET) WITH (Key1=Value1, K2=V2);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObjectIfNotExists")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(CreateObjectWithFeaturesStrings) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT secretId (TYPE SECRET) WITH (Key1=\"Value1\", K2='V2', K3=V3, K4='', K5=`aaa`, K6='a\\'aa');"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('\"K2\" '\"V2\") '('\"K3\" '\"V3\") '('\"K4\" '\"\") '('\"K5\" '\"aaa\") '('\"K6\" '\"a'aa\") '('\"Key1\" '\"Value1\")")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}, {TString("SECRET"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - } - - Y_UNIT_TEST(UpsertObjectWithFeatures) { - NYql::TAstParseResult res = SqlToYql("USE plato; UPSERT OBJECT secretId (TYPE SECRET) WITH (Key1=Value1, K2=V2);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('\"K2\" '\"V2\") '('\"Key1\" '\"Value1\")")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("upsertObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(CreateObjectWithFeaturesAndFlags) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT secretId (TYPE SECRET) WITH (Key1=Value1, K2=V2, RECURSE);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('\"Key1\" '\"Value1\") '('\"RECURSE\")")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(Select1Type) { - NYql::TAstParseResult res = SqlToYql("SELECT 1 type;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectTableType) { - NYql::TAstParseResult res = SqlToYql("USE plato; SELECT * from T type;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(CreateObjectNoFeatures) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT secretId (TYPE SECRET);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(AlterObjectWithFeatures) { - NYql::TAstParseResult res = SqlToYql( - "USE plato;\n" - "declare $path as String;\n" - "ALTER OBJECT secretId (TYPE SECRET) SET (Key1=$path, K2=V2);" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"Key1\" (EvaluateAtom \"$path\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"K2\" '\"V2\"")); - - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alterObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(AlterObjectNoFeatures) { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER OBJECT secretId (TYPE SECRET);"); - UNIT_ASSERT(!res.Root); - } - - Y_UNIT_TEST(DropObjectNoFeatures) { - NYql::TAstParseResult res = SqlToYql("USE plato; DROP OBJECT secretId (TYPE SECRET);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(DropObjectWithFeatures) { - NYql::TAstParseResult res = SqlToYql("USE plato; DROP OBJECT secretId (TYPE SECRET) WITH (A, B, C);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(DropObjectWithOneOption) { - NYql::TAstParseResult res = SqlToYql("USE plato; DROP OBJECT secretId (TYPE SECRET) WITH OVERRIDE;"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"OVERRIDE\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(DropObjectIfExists) { - NYql::TAstParseResult res = SqlToYql("USE plato; DROP OBJECT IF EXISTS secretId (TYPE SECRET);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObjectIfExists")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); - } - - Y_UNIT_TEST(PrimaryKeyParseCorrect) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE tableName (Key Uint32, Subkey Int64, Value String, PRIMARY KEY (Key, Subkey));"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"Key\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"Subkey\"")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}, {TString("primarykey"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["primarykey"]); - } - - Y_UNIT_TEST(AlterDatabaseAst) { - NYql::TAstParseResult request = SqlToYql("USE plato; ALTER DATABASE `/Root/test` OWNER TO user1;"); - UNIT_ASSERT(request.IsOk()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - Y_UNUSED(word); - - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find( - R"(let world (Write! world sink (Key '('databasePath (String '"/Root/test"))) (Void) '('('mode 'alterDatabase) '('owner '"user1"))))" - )); - }; - - TWordCountHive elementStat({TString("\'mode \'alterDatabase")}); - VerifyProgram(request, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["\'mode \'alterDatabase"]); - } - - Y_UNIT_TEST(CreateTableNonNullableYqlTypeAstCorrect) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32 not null);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (DataType 'Int32) '('columnConstrains '('('not_null))) '())))))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTableNullableYqlTypeAstCorrect) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTableNonNullablePgTypeAstCorrect) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a pg_int4 not null);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (PgType '_int4) '('columnConstrains '('('not_null))) '())))))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTableNullablePgTypeAstCorrect) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a pg_int4);"); - UNIT_ASSERT(res.Root); - - res.Root->PrettyPrintTo(Cout, PRETTY_FLAGS); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (PgType '_int4)) '('columnConstrains '()) '()))))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTableNullPkColumnsAreAllowed) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32, primary key(a));"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))) '('primarykey '('"a")))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTableNotNullPkColumnsAreIdempotentAstCorrect) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32 not null, primary key(a));"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (DataType 'Int32) '('columnConstrains '('('not_null))) '()))) '('primarykey '('"a"))))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTableWithIfNotExists) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE IF NOT EXISTS t (a int32, primary key(a));"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create_if_not_exists) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))) '('primarykey '('"a")))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTempTable) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TEMP TABLE t (a int32, primary key(a));"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))) '('primarykey '('"a")) '('temporary))))__"), line); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTemporaryTable) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TEMPORARY TABLE t (a int32, primary key(a));"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, - line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))) '('primarykey '('"a")) '('temporary))))__"), line); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - } - - Y_UNIT_TEST(CreateTableWithoutTypes) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a, primary key(a));"); - UNIT_ASSERT(!res.Root); - } - - Y_UNIT_TEST(CreateTableAsSelectWithTypes) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32, primary key(a)) AS SELECT * FROM ts;"); - UNIT_ASSERT(!res.Root); - } - - Y_UNIT_TEST(CreateTableAsSelect) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a, b, primary key(a)) AS SELECT * FROM ts;"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((let world (Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a") '('"b"))) '('primarykey '('"a"))))))__")); - } - if (word == "Read!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Read! world (DataSource '"yt" '"plato") (MrTableConcat (Key '('table (String '"ts")))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}, {TString("Read!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Read!"]); - } - - Y_UNIT_TEST(CreateTableAsSelectOnlyPrimary) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (primary key(a)) AS SELECT * FROM ts;"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((let world (Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '()) '('primarykey '('"a"))))))__")); - } - if (word == "Read!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Read! world (DataSource '"yt" '"plato") (MrTableConcat (Key '('table (String '"ts")))))__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}, {TString("Read!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Read!"]); - } - - Y_UNIT_TEST(CreateTableAsValuesFail) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a, primary key(a)) AS VALUES (1), (2);"); - UNIT_ASSERT(!res.Root); - } - - Y_UNIT_TEST(CreateTableDuplicatedPkColumnsFail) { - NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32 not null, primary key(a, a));"); - UNIT_ASSERT(!res.Root); - } - - Y_UNIT_TEST(DeleteFromTableByKey) { - NYql::TAstParseResult res = SqlToYql("delete from plato.Input where key = 200;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DeleteFromTable) { - NYql::TAstParseResult res = SqlToYql("delete from plato.Input;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DeleteFromTableBatch) { - NYql::TAstParseResult res = SqlToYql("batch delete from plato.Input;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete)")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('is_batch 'true)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DeleteFromTableOnValues) { - NYql::TAstParseResult res = SqlToYql("delete from plato.Input on (key) values (1);", - 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete_on)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DeleteFromTableOnSelect) { - NYql::TAstParseResult res = SqlToYql( - "delete from plato.Input on select key from plato.Input where value > 0;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete_on)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DeleteFromTableOnBatch) { - NYql::TAstParseResult res = SqlToYql("batch delete from plato.Input on (key) values (1);", - 10, "kikimr"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:6: Error: BATCH DELETE is unsupported with ON\n"); - } - - Y_UNIT_TEST(UpdateByValues) { - NYql::TAstParseResult res = SqlToYql("update plato.Input set key = 777, value = 'cool' where key = 200;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update)")); - } else if (word == "AsStruct") { - const bool isKey = line.find("key") != TString::npos; - const bool isValue = line.find("value") != TString::npos; - UNIT_ASSERT(isKey || isValue); - if (isKey) { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("777"))); - } else if (isValue) { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("cool"))); - } - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}, {TString("AsStruct"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["AsStruct"]); - } - - Y_UNIT_TEST(UpdateByValuesBatch) { - NYql::TAstParseResult res = SqlToYql("batch update plato.Input set key = 777, value = 'cool' where key = 200;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update)")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('is_batch 'true)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(UpdateByMultiValues) { - NYql::TAstParseResult res = SqlToYql("update plato.Input set (key, value, subkey) = ('2','ddd',':') where key = 200;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update)")); - } else if (word == "AsStruct") { - const bool isKey = line.find("key") != TString::npos; - const bool isSubkey = line.find("subkey") != TString::npos; - const bool isValue = line.find("value") != TString::npos; - UNIT_ASSERT(isKey || isSubkey || isValue); - if (isKey && !isSubkey) { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("2"))); - } else if (isSubkey) { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote(":"))); - } else if (isValue) { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("ddd"))); - } - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}, {TString("AsStruct"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["AsStruct"]); - } - - Y_UNIT_TEST(UpdateBySelect) { - NYql::TAstParseResult res = SqlToYql("update plato.Input set (key, value, subkey) = (select key, value, subkey from plato.Input where key = 911) where key = 200;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - int lineIndex = 0; - int writeLineIndex = -1; - bool found = false; - - TVerifyLineFunc verifyLine = [&lineIndex, &writeLineIndex, &found](const TString& word, const TString& line) { - if (word == "Write") { - writeLineIndex = lineIndex; - found = line.find("('mode 'update)") != TString::npos; - } else if (word == "mode") { - found |= lineIndex == writeLineIndex + 1 && line.find("('mode 'update)") != TString::npos; - UNIT_ASSERT(found); - } - - ++lineIndex; - }; - - TWordCountHive elementStat = {{TString("Write"), 0}, {TString("mode"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(UpdateSelfModifyAll) { - NYql::TAstParseResult res = SqlToYql("update plato.Input set subkey = subkey + 's';", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update)")); - } else if (word == "AsStruct") { - const bool isSubkey = line.find("subkey") != TString::npos; - UNIT_ASSERT(isSubkey); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("subkey"))); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("s"))); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}, {TString("AsStruct"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["AsStruct"]); - } - - Y_UNIT_TEST(UpdateOnValues) { - NYql::TAstParseResult res = SqlToYql("update plato.Input on (key, value) values (5, 'cool')", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update_on)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(UpdateOnSelect) { - NYql::TAstParseResult res = SqlToYql( - "update plato.Input on select key, value + 1 as value from plato.Input", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update_on)")); - } - }; - - TWordCountHive elementStat = {{TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(UpdateOnBatch) { - NYql::TAstParseResult res = SqlToYql("batch update plato.Input on (key, value) values (5, 'cool')", 10, "kikimr"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:6: Error: BATCH UPDATE is unsupported with ON\n"); - } - - Y_UNIT_TEST(UnionAllTest) { - NYql::TAstParseResult res = SqlToYql("PRAGMA DisableEmitUnionMerge; SELECT key FROM plato.Input UNION ALL select subkey FROM plato.Input;"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("UnionAll"), 0}}; - VerifyProgram(res, elementStat, {}); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["UnionAll"]); - } - - Y_UNIT_TEST(UnionAllMergeTest) { - NYql::TAstParseResult res = SqlToYql("PRAGMA EmitUnionMerge; SELECT key FROM plato.Input UNION ALL select subkey FROM plato.Input;"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("UnionMerge"), 0}}; - VerifyProgram(res, elementStat, {}); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["UnionMerge"]); - } - - Y_UNIT_TEST(UnionTest) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input UNION select subkey FROM plato.Input;"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("Union"), 0}}; - VerifyProgram(res, elementStat, {}); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Union"]); - } - - Y_UNIT_TEST(UnionAggregationTest) { - NYql::TAstParseResult res = SqlToYql(R"( - PRAGMA DisableEmitUnionMerge; - SELECT 1 - UNION ALL - SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 - UNION - SELECT 1 UNION SELECT 1 UNION SELECT 1 UNION SELECT 1 - UNION ALL - SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1; - )"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("Union"), 0}, {TString("UnionAll"), 0}}; - VerifyProgram(res, elementStat, {}); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["UnionAll"]); - UNIT_ASSERT_VALUES_EQUAL(3, elementStat["Union"]); - } - - Y_UNIT_TEST(UnionMergeAggregationTest) { - NYql::TAstParseResult res = SqlToYql(R"( - PRAGMA EmitUnionMerge; - SELECT 1 - UNION ALL - SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 - UNION - SELECT 1 UNION SELECT 1 UNION SELECT 1 UNION SELECT 1 - UNION ALL - SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1; - )"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("Union"), 0}, {TString("UnionMerge"), 0}}; - VerifyProgram(res, elementStat, {}); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["UnionMerge"]); - UNIT_ASSERT_VALUES_EQUAL(3, elementStat["Union"]); - } - - Y_UNIT_TEST(DeclareDecimalParameter) { - NYql::TAstParseResult res = SqlToYql("declare $value as Decimal(22,9); select $value as cnt;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SimpleGroupBy) { - NYql::TAstParseResult res = SqlToYql("select count(1),z from plato.Input group by key as z order by z;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(EmptyColumnName0) { - /// Now it's parsed well and error occur on validate step like "4:31:Empty struct member name is not allowed" in "4:31:Function: AddMember" - NYql::TAstParseResult res = SqlToYql("insert into plato.Output (``, list1) values (0, AsList(0, 1, 2));"); - /// Verify that parsed well without crash - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(KikimrRollback) { - NYql::TAstParseResult res = SqlToYql("use plato; select * from Input; rollback;", 10, "kikimr"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("rollback"), 0}}; - VerifyProgram(res, elementStat); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["rollback"]); - } - - Y_UNIT_TEST(PragmaFile) { - NYql::TAstParseResult res = SqlToYql(R"(pragma file("HW", "sbr:181041334");)"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString(R"((let world (Configure! world (DataSource '"config") '"AddFileByUrl" '"HW" '"sbr:181041334")))"), 0}}; - VerifyProgram(res, elementStat); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat.cbegin()->second); - } - - Y_UNIT_TEST(DoNotCrashOnNamedInFilter) { - NYql::TAstParseResult res = SqlToYql("USE plato; $all = ($table_name) -> { return true; }; SELECT * FROM FILTER(Input, $all)"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(PragmasFileAndUdfOrder) { - NYql::TAstParseResult res = SqlToYql(R"( - PRAGMA file("libvideoplayers_udf.so", "https://proxy.sandbox.yandex-team.ru/235185290"); - PRAGMA udf("libvideoplayers_udf.so"); - )"); - UNIT_ASSERT(res.Root); - - const auto programm = GetPrettyPrint(res); - const auto file = programm.find("AddFileByUrl"); - const auto udfs = programm.find("ImportUdfs"); - UNIT_ASSERT(file < udfs); - } - - Y_UNIT_TEST(ProcessUserType) { - NYql::TAstParseResult res = SqlToYql("process plato.Input using Kikimr::PushData(TableRows());", 1, TString(NYql::KikimrProviderName)); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Kikimr.PushData") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("TupleType")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("TypeOf")); - } - }; - - TWordCountHive elementStat = {{TString("Kikimr.PushData"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Kikimr.PushData"]); - } - - Y_UNIT_TEST(ProcessUserTypeAuth) { - NYql::TAstParseResult res = SqlToYql("process plato.Input using YDB::PushData(TableRows(), AsTuple('oauth', SecureParam('api:oauth')));", 1, TString(NYql::KikimrProviderName)); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "YDB.PushData") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("TupleType")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("TypeOf")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("api:oauth")); - } - }; - - TWordCountHive elementStat = {{TString("YDB.PushData"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["YDB.PushData"]); - } - - Y_UNIT_TEST(SelectStreamRtmr) { - NYql::TAstParseResult res = SqlToYql( - "USE plato; INSERT INTO Output SELECT STREAM key FROM Input;", - 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(res.Root); - - res = SqlToYql( - "USE plato; INSERT INTO Output SELECT key FROM Input;", - 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectStreamRtmrJoinWithYt) { - NYql::TAstParseResult res = SqlToYql( - "USE plato; INSERT INTO Output SELECT STREAM key FROM Input LEFT JOIN hahn.ttt as t ON Input.key = t.Name;", - 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SelectStreamNonRtmr) { - NYql::TAstParseResult res = SqlToYql( - "USE plato; INSERT INTO Output SELECT STREAM key FROM Input;", - 10); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:31: Error: SELECT STREAM is unsupported for non-streaming sources\n"); - } - - Y_UNIT_TEST(GroupByHopRtmr) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; INSERT INTO Output SELECT key, SUM(value) AS value FROM Input - GROUP BY key, HOP(subkey, "PT10S", "PT30S", "PT20S"); - )", 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(GroupByHopRtmrSubquery) { - // 'use plato' intentially avoided - NYql::TAstParseResult res = SqlToYql(R"( - SELECT COUNT(*) AS value FROM (SELECT * FROM plato.Input) - GROUP BY HOP(Data, "PT10S", "PT30S", "PT20S") - )", 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(GroupByHopRtmrSubqueryBinding) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - $q = SELECT * FROM Input; - INSERT INTO Output SELECT STREAM * FROM ( - SELECT COUNT(*) AS value FROM $q - GROUP BY HOP(Data, "PT10S", "PT30S", "PT20S") - ); - )", 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(GroupByNoHopRtmr) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; INSERT INTO Output SELECT STREAM key, SUM(value) AS value FROM Input - GROUP BY key; - )", 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:22: Error: Streaming group by query must have a hopping window specification.\n"); - } - - Y_UNIT_TEST(KikimrInserts) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - INSERT INTO Output SELECT key, value FROM Input; - INSERT OR ABORT INTO Output SELECT key, value FROM Input; - INSERT OR IGNORE INTO Output SELECT key, value FROM Input; - INSERT OR REVERT INTO Output SELECT key, value FROM Input; - )", 10, TString(NYql::KikimrProviderName)); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(WarnMissingIsBeforeNotNull) { - NYql::TAstParseResult res = SqlToYql("select 1 NOT NULL"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Warning: Missing IS keyword before NOT NULL, code: 4507\n"); - } - - Y_UNIT_TEST(Subqueries) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - $sq1 = (SELECT * FROM plato.Input); - - $sq2 = SELECT * FROM plato.Input; - - $squ1 = ( - SELECT * FROM plato.Input - UNION ALL - SELECT * FROM plato.Input - ); - - $squ2 = - SELECT * FROM plato.Input - UNION ALL - SELECT * FROM plato.Input; - - $squ3 = ( - (SELECT * FROM plato.Input) - UNION ALL - (SELECT * FROM plato.Input) - ); - - SELECT * FROM $sq1; - SELECT * FROM $sq2; - SELECT * FROM $squ1; - SELECT * FROM $squ2; - SELECT * FROM $squ3; - )"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(SubqueriesJoin) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - - $left = SELECT * FROM plato.Input1 WHERE value != "BadValue"; - $right = SELECT * FROM plato.Input2; - - SELECT * FROM $left AS l - JOIN $right AS r - ON l.key == r.key; - )"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(AnyInBackticksAsTableName) { - NYql::TAstParseResult res = SqlToYql("use plato; select * from `any`;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(AnyJoinForTableAndSubQuery) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - - $r = SELECT * FROM plato.Input2; - - SELECT * FROM ANY plato.Input1 AS l - LEFT JOIN ANY $r AS r - USING (key); - )"); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "EquiJoin") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('left 'any)")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('right 'any)")); - } - }; - - TWordCountHive elementStat = {{TString("left"), 0}, {TString("right"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["left"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["right"]); - } - - Y_UNIT_TEST(AnyJoinForTableAndTableSource) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - - $r = AsList( - AsStruct("aaa" as key, "bbb" as subkey, "ccc" as value) - ); - - SELECT * FROM ANY plato.Input1 AS l - LEFT JOIN ANY AS_TABLE($r) AS r - USING (key); - )"); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "EquiJoin") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('left 'any)")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('right 'any)")); - } - }; - - TWordCountHive elementStat = {{TString("left"), 0}, {TString("right"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["left"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["right"]); - } - - Y_UNIT_TEST(AnyJoinNested) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - - FROM ANY Input1 as a - JOIN Input2 as b ON a.key = b.key - LEFT JOIN ANY Input3 as c ON a.key = c.key - RIGHT JOIN ANY Input4 as d ON d.key = b.key - CROSS JOIN Input5 - SELECT *; - )"); - - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("left"), 0}, {TString("right"), 0}}; - VerifyProgram(res, elementStat); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["left"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["right"]); - } - - Y_UNIT_TEST(InlineAction) { - NYql::TAstParseResult res = SqlToYql( - "do begin\n" - " select 1\n" - "; end do\n"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), ""); - } - - Y_UNIT_TEST(FlattenByCorrelationName) { - UNIT_ASSERT(SqlToYql("select * from plato.Input as t flatten by t.x").IsOk()); - UNIT_ASSERT(SqlToYql("select * from plato.Input as t flatten by t -- same as flatten by t.t").IsOk()); - } - - Y_UNIT_TEST(DiscoveryMode) { - UNIT_ASSERT(SqlToYqlWithMode("insert into plato.Output select * from plato.Input", NSQLTranslation::ESqlMode::DISCOVERY).IsOk()); - UNIT_ASSERT(SqlToYqlWithMode("select * from plato.concat(Input1, Input2)", NSQLTranslation::ESqlMode::DISCOVERY).IsOk()); - UNIT_ASSERT(SqlToYqlWithMode("select * from plato.each(AsList(\"Input1\", \"Input2\"))", NSQLTranslation::ESqlMode::DISCOVERY).IsOk()); - } - - Y_UNIT_TEST(CubeWithAutoGeneratedLikeColumnName) { - UNIT_ASSERT(SqlToYql("select key,subkey,group from plato.Input group by cube(key,subkey,group)").IsOk()); - } - - Y_UNIT_TEST(CubeWithAutoGeneratedLikeAlias) { - UNIT_ASSERT(SqlToYql("select key,subkey,group from plato.Input group by cube(key,subkey,value as group)").IsOk()); - } - - Y_UNIT_TEST(FilterCanBeUsedAsColumnIdOrBind) { - UNIT_ASSERT(SqlToYql("select filter from plato.Input").IsOk()); - UNIT_ASSERT(SqlToYql("select 1 as filter").IsOk()); - UNIT_ASSERT(SqlToYql("$filter = 1; select $filter").IsOk()); - } - - Y_UNIT_TEST(DuplicateSemicolonsAreAllowedBetweenTopLevelStatements) { - UNIT_ASSERT(SqlToYql(";;select 1; ; select 2;/*comment*/;select 3;;--comment\n;select 4;;").IsOk()); - } - - Y_UNIT_TEST(DuplicateAndMissingTrailingSemicolonsAreAllowedBetweenActionStatements) { - TString req = - "define action $action($b,$c) as\n" - " ;;$d = $b + $c;\n" - " select $b;\n" - " select $c;;\n" - " select $d,\n" - "end define;\n" - "\n" - "do $action(1,2);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(DuplicateAndMissingTrailingSemicolonsAreAllowedBetweenInlineActionStatements) { - TString req = - "do begin\n" - " ;select 1,\n" - "end do;\n" - "evaluate for $i in AsList(1,2,3) do begin\n" - " select $i;;\n" - " select $i + $i;;\n" - "end do;"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(DuplicateSemicolonsAreAllowedBetweenLambdaStatements) { - TString req = - "$x=1;\n" - "$foo = ($a, $b)->{\n" - " ;;$v = $a + $b;\n" - " $bar = ($c) -> {; return $c << $x};;\n" - " return $bar($v);;\n" - "};\n" - "select $foo(1,2);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(StringLiteralWithEscapedBackslash) { - NYql::TAstParseResult res1 = SqlToYql(R"foo(SELECT 'a\\';)foo"); - NYql::TAstParseResult res2 = SqlToYql(R"foo(SELECT "a\\";)foo"); - UNIT_ASSERT(res1.Root); - UNIT_ASSERT(res2.Root); - - TWordCountHive elementStat = {{TString("a\\"), 0}}; - - VerifyProgram(res1, elementStat); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["a\\"]); - - VerifyProgram(res2, elementStat); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["a\\"]); - } - - Y_UNIT_TEST(StringMultiLineLiteralWithEscapes) { - UNIT_ASSERT(SqlToYql("SELECT @@@foo@@@@bar@@@").IsOk()); - UNIT_ASSERT(SqlToYql("SELECT @@@@@@@@@").IsOk()); - } - - Y_UNIT_TEST(StringMultiLineLiteralConsequitiveAt) { - UNIT_ASSERT(!SqlToYql("SELECT @").IsOk()); - UNIT_ASSERT(!SqlToYql("SELECT @@").IsOk()); - UNIT_ASSERT(!SqlToYql("SELECT @@@").IsOk()); - UNIT_ASSERT( SqlToYql("SELECT @@@@").IsOk()); - UNIT_ASSERT( SqlToYql("SELECT @@@@@").IsOk()); - - UNIT_ASSERT(!SqlToYql("SELECT @@@@@@").IsOk()); - UNIT_ASSERT(!SqlToYql("SELECT @@@@@@@").IsOk()); - - UNIT_ASSERT( SqlToYql("SELECT @@@@@@@@").IsOk()); - UNIT_ASSERT( SqlToYql("SELECT @@@@@@@@@").IsOk()); - UNIT_ASSERT(!SqlToYql("SELECT @@@@@@@@@@").IsOk()); - } - - Y_UNIT_TEST(ConstnessForListDictSetCreate) { - auto req = "$foo = ($x, $y) -> (\"aaaa\");\n" - "\n" - "select\n" - " $foo(sum(key), ListCreate(String)),\n" - " $foo(sum(key), DictCreate(String, String)),\n" - " $foo(sum(key), SetCreate(String)),\n" - "from (select 1 as key);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(CanUseEmptyTupleInWindowPartitionBy) { - auto req = "select sum(key) over w\n" - "from plato.Input\n" - "window w as (partition compact by (), (subkey), (), value || value as dvalue);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(DenyAnsiOrderByLimitLegacyMode) { - auto req = "pragma DisableAnsiOrderByLimitInUnionAll;\n" - "use plato;\n" - "\n" - "select * from Input order by key limit 10\n" - "union all\n" - "select * from Input order by key limit 1;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: DisableAnsiOrderByLimitInUnionAll pragma is deprecated and no longer supported\n"); - } - - Y_UNIT_TEST(ReduceUsingUdfWithShortcutsWorks) { - auto req = "use plato;\n" - "\n" - "$arg = 'foo';\n" - "$func = XXX::YYY($arg);\n" - "\n" - "REDUCE Input ON key using $func(subkey);\n" - "REDUCE Input ON key using $func(UUU::VVV(TableRow()));\n"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - req = "use plato;\n" - "\n" - "$arg = 'foo';\n" - "$func = XXX::YYY($arg);\n" - "\n" - "REDUCE Input ON key using all $func(subkey);\n" - "REDUCE Input ON key using all $func(UUU::VVV(TableRow()));"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(YsonDisableStrict) { - UNIT_ASSERT(SqlToYql("pragma yson.DisableStrict = \"false\";").IsOk()); - UNIT_ASSERT(SqlToYql("pragma yson.DisableStrict;").IsOk()); - } - - Y_UNIT_TEST(YsonStrict) { - UNIT_ASSERT(SqlToYql("pragma yson.Strict = \"false\";").IsOk()); - UNIT_ASSERT(SqlToYql("pragma yson.Strict;").IsOk()); - } - - Y_UNIT_TEST(JoinByTuple) { - auto req = "use plato;\n" - "\n" - "select * from T1 as a\n" - "join T2 as b\n" - "on AsTuple(a.key, a.subkey) = AsTuple(b.key, b.subkey);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(JoinByStruct) { - auto req = "use plato;\n" - "\n" - "select * from T1 as a\n" - "join T2 as b\n" - "on AsStruct(a.key as k, a.subkey as sk) = AsStruct(b.key as k, b.subkey as sk);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(JoinByUdf) { - auto req = "use plato;\n" - "\n" - "select a.align\n" - "from T1 as a\n" - "join T2 as b\n" - "on Yson::SerializeJsonEncodeUtf8(a.align)=b.align;"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(EscapedIdentifierAsLambdaArg) { - auto req = "$f = ($`foo bar`, $x) -> { return $`foo bar` + $x; };\n" - "\n" - "select $f(1, 2);"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(lambda '(\"$foo bar\" \"$x\")"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(UdfSyntaxSugarOnlyCallable) { - auto req = "SELECT Udf(DateTime::FromString)('2022-01-01');"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType)))"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(UdfSyntaxSugarTypeNoRun) { - auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, 'foo' as TypeConfig)('2022-01-01');"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\")"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(UdfSyntaxSugarRunNoType) { - auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, Void() as RunConfig)('2022-01-01');"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"\" (Void))"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(UdfSyntaxSugarFullTest) { - auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, 'foo' as TypeConfig, Void() As RunConfig)('2022-01-01');"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\" (Void))"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(UdfSyntaxSugarOtherRunConfigs) { - auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, 'foo' as TypeConfig, '55' As RunConfig)('2022-01-01');"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\" (String '\"55\"))"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(UdfSyntaxSugarOtherRunConfigs2) { - auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, 'foo' as TypeConfig, AsTuple(32, 'no', AsStruct(1e-9 As SomeFloat)) As RunConfig)('2022-01-01');"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\" '((Int32 '\"32\") (String '\"no\") (AsStruct '('\"SomeFloat\" (Double '\"1e-9\")))))"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(UdfSyntaxSugarOptional) { - auto req = "SELECT Udf(DateTime::FromString, String?, Int32??, Tuple<Int32, Float>, \"foo\" as TypeConfig, Void() As RunConfig)(\"2022-01-01\");"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - const auto programm = GetPrettyPrint(res); - auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (OptionalType (DataType 'String)) (OptionalType (OptionalType (DataType 'Int32))) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\" (Void))"; - UNIT_ASSERT(programm.find(expected) != TString::npos); - } - - Y_UNIT_TEST(CompactionPolicyParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) - WITH ( COMPACTION_POLICY = "SomeCompactionPreset" );)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("compactionPolicy")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("SomeCompactionPreset")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(AutoPartitioningBySizeParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) - WITH ( AUTO_PARTITIONING_BY_SIZE = ENABLED );)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("autoPartitioningBySize")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("ENABLED")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(UniformPartitionsParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) - WITH ( UNIFORM_PARTITIONS = 16 );)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("uniformPartitions")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("16")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DateTimeTtlParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, CreatedAt Timestamp, PRIMARY KEY (Key)) - WITH (TTL = Interval("P1D") On CreatedAt);)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(IntTtlParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, CreatedAt Uint32, PRIMARY KEY (Key)) - WITH (TTL = Interval("P1D") On CreatedAt AS SECONDS);)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("columnUnit")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("seconds")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(TtlTieringParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, CreatedAt Uint32, PRIMARY KEY (Key)) - WITH (TTL = - Interval("P1D") TO EXTERNAL DATA SOURCE Tier1, - Interval("P2D") TO EXTERNAL DATA SOURCE Tier2, - Interval("P30D") DELETE - On CreatedAt AS SECONDS);)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("storageName")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier1")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier2")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("172800000")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("2592000000")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("columnUnit")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("seconds")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(TtlTieringWithOtherActionsParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - ALTER TABLE tableName - ADD FAMILY cold (DATA = "rot"), - SET TTL - Interval("P1D") TO EXTERNAL DATA SOURCE Tier1, - Interval("P2D") TO EXTERNAL DATA SOURCE Tier2, - Interval("P30D") DELETE - ON CreatedAt, - ALTER COLUMN payload_v2 SET FAMILY cold, - ALTER FAMILY default SET DATA "ssd" - ;)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("addColumnFamilies")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("cold")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alterColumnFamilies")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("default")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("storageName")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier1")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier2")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("172800000")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("2592000000")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(TieringParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) - WITH ( TIERING = 'my_tiering' );)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiering")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("my_tiering")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(StoreExternalBlobsParseCorrect) { - NYql::TAstParseResult res = SqlToYql( - R"( USE plato; - CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) - WITH ( STORE_EXTERNAL_BLOBS = ENABLED );)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("storeExternalBlobs")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("ENABLED")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DefaultValueColumn2) { - auto res = SqlToYql(R"( use plato; - $lambda = () -> { - RETURN CAST(RandomUuid(2) as String) - }; - - CREATE TABLE tableName ( - Key Uint32 DEFAULT RandomNumber(1), - Value String DEFAULT $lambda, - PRIMARY KEY (Key) - ); - )"); - - UNIT_ASSERT_C(res.Root, Err2Str(res)); - - const auto program = GetPrettyPrint(res); - - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("RandomNumber")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("RandomUuid")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("columnConstrains")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("columnConstrains")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("Write")); - -#if 0 - Cerr << program << Endl; -#endif - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DefaultValueColumn3) { - auto res = SqlToYql(R"( use plato; - - CREATE TABLE tableName ( - database_id Utf8, - cloud_id Utf8, - global_id Utf8 DEFAULT database_id || "=====", - PRIMARY KEY (database_id) - ); - )"); - - UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:6:40: Error: Column reference \"database_id\" is not allowed in current scope\n"); - UNIT_ASSERT(!res.Root); - } - - Y_UNIT_TEST(DefaultValueColumn) { - auto res = SqlToYql(R"( use plato; - CREATE TABLE tableName ( - Key Uint32 FAMILY cold DEFAULT 5, - Value String FAMILY default DEFAULT "empty", - PRIMARY KEY (Key), - FAMILY default ( - DATA = "test", - COMPRESSION = "lz4" - ), - FAMILY cold ( - DATA = "test", - COMPRESSION = "off" - ) - ); - )"); - - UNIT_ASSERT_C(res.Root, Err2Str(res)); - -#if 0 - const auto program = GetPrettyPrint(res); - Cerr << program << Endl; -#endif - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("default")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("columnConstrains")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("columnFamilies")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(ChangefeedParseCorrect) { - auto res = SqlToYql(R"( USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH ( - MODE = 'KEYS_ONLY', - FORMAT = 'json', - INITIAL_SCAN = TRUE, - VIRTUAL_TIMESTAMPS = FALSE, - BARRIERS_INTERVAL = Interval("PT1S"), - RETENTION_PERIOD = Interval("P1D"), - TOPIC_MIN_ACTIVE_PARTITIONS = 10, - AWS_REGION = 'aws:region' - ) - ); - )"); - UNIT_ASSERT_C(res.Root, Err2Str(res)); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("changefeed")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("mode")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("KEYS_ONLY")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("format")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("json")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("initial_scan")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("true")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("virtual_timestamps")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("false")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("barriers_interval")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("retention_period")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("topic_min_active_partitions")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("aws_region")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("aws:region")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CloneForAsTableWorksWithCube) { - UNIT_ASSERT(SqlToYql("SELECT * FROM AS_TABLE([<|k1:1, k2:1|>]) GROUP BY CUBE(k1, k2);").IsOk()); - } - - Y_UNIT_TEST(WindowPartitionByColumnProperlyEscaped) { - NYql::TAstParseResult res = SqlToYql("SELECT SUM(key) OVER w FROM plato.Input WINDOW w AS (PARTITION BY `column with space`);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "CalcOverWindow") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"column with space\"")); - } - }; - - TWordCountHive elementStat = { {TString("CalcOverWindow"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["CalcOverWindow"]); - } - - Y_UNIT_TEST(WindowPartitionByExpressionWithoutAliasesAreAllowed) { - NYql::TAstParseResult res = SqlToYql("SELECT SUM(key) OVER w FROM plato.Input as i WINDOW w AS (PARTITION BY ii.subkey);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "AddMember") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("AddMember row 'group_w_0 (SqlAccess 'struct (Member row '\"ii\")")); - } - if (word == "CalcOverWindow") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("CalcOverWindow core '('\"group_w_0\")")); - } - }; - - TWordCountHive elementStat = { {TString("CalcOverWindow"), 0}, {TString("AddMember"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["CalcOverWindow"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["AddMember"]); - } - - Y_UNIT_TEST(PqReadByAfterUse) { - ExpectFailWithError("use plato; pragma PqReadBy='plato2';", - "<main>:1:28: Error: Cluster in PqReadPqBy pragma differs from cluster specified in USE statement: plato2 != plato\n"); - - UNIT_ASSERT(SqlToYql("pragma PqReadBy='plato2';").IsOk()); - UNIT_ASSERT(SqlToYql("pragma PqReadBy='plato2'; use plato;").IsOk()); - UNIT_ASSERT(SqlToYql("$x='plato'; use rtmr:$x; pragma PqReadBy='plato2';").IsOk()); - UNIT_ASSERT(SqlToYql("use plato; pragma PqReadBy='dq';").IsOk()); - } - - Y_UNIT_TEST(MrObject) { - NYql::TAstParseResult res = SqlToYql( - "declare $path as String;\n" - "select * from plato.object($path, `format`, \"comp\" || \"ression\" as compression, 1 as bar) with schema (Int32 as y, String as x)" - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "MrObject") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((MrObject (EvaluateAtom "$path") '"format" '('('"compression" (Concat (String '"comp") (String '"ression"))) '('"bar" (Int32 '"1")))))__")); - } else if (word == "userschema") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__('('('"userschema" (StructType '('"y" (DataType 'Int32)) '('"x" (DataType 'String))) '('"y" '"x"))))__")); - } - }; - - TWordCountHive elementStat = {{TString("MrObject"), 0}, {TString("userschema"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["MrObject"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["userschema"]); - } - - Y_UNIT_TEST(TableBindings) { - NSQLTranslation::TTranslationSettings settings = GetSettingsWithS3Binding("foo"); - NYql::TAstParseResult res = SqlToYqlWithSettings( - "select * from bindings.foo", - settings - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "MrObject") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((MrTableConcat (Key '('table (String '"path")))) (Void) '('('"bar" '"1") '('"compression" '"ccompression") '('"format" '"format") '('"partitionedby" '"key" '"subkey") '('"userschema" (SqlTypeFromYson)__")); - } - }; - - TWordCountHive elementStat = {{TString("MrTableConcat"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["MrTableConcat"]); - - settings.DefaultCluster = "plato"; - settings.BindingsMode = NSQLTranslation::EBindingsMode::DISABLED; - res = SqlToYqlWithSettings( - "select * from bindings.foo", - settings - ); - UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:1:15: Error: Please remove 'bindings.' from your query, the support for this syntax has ended, code: 4601\n"); - UNIT_ASSERT(!res.Root); - - settings.BindingsMode = NSQLTranslation::EBindingsMode::DROP; - res = SqlToYqlWithSettings( - "select * from bindings.foo", - settings - ); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine2 = [](const TString& word, const TString& line) { - if (word == "MrTableConcat") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((MrTableConcat (Key '('table (String '"foo")))) (Void) '())))__")); - } - }; - - TWordCountHive elementStat2 = {{TString("MrTableConcat"), 0}}; - VerifyProgram(res, elementStat2, verifyLine2); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat2["MrTableConcat"]); - - settings.BindingsMode = NSQLTranslation::EBindingsMode::DROP_WITH_WARNING; - res = SqlToYqlWithSettings( - "select * from bindings.foo", - settings - ); - UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:1:15: Warning: Please remove 'bindings.' from your query, the support for this syntax will be dropped soon, code: 4538\n"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat3 = {{TString("MrTableConcat"), 0}}; - VerifyProgram(res, elementStat3, verifyLine2); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat3["MrTableConcat"]); - } - - Y_UNIT_TEST(TableBindingsWithInsert) { - NSQLTranslation::TTranslationSettings settings = GetSettingsWithS3Binding("foo"); - NYql::TAstParseResult res = SqlToYqlWithSettings( - "insert into bindings.foo with truncate (x, y) values (1, 2);", - settings - ); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('table (String '"path"))) values '('('"bar" '"1") '('"compression" '"ccompression") '('"format" '"format") '('"partitionedby" '"key" '"subkey") '('"userschema" (SqlTypeFromYson)__")); - } - }; - - TWordCountHive elementStat = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); - - settings.DefaultCluster = "plato"; - settings.BindingsMode = NSQLTranslation::EBindingsMode::DISABLED; - res = SqlToYqlWithSettings( - "insert into bindings.foo with truncate (x, y) values (1, 2);", - settings - ); - UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:1:13: Error: Please remove 'bindings.' from your query, the support for this syntax has ended, code: 4601\n"); - UNIT_ASSERT(!res.Root); - - settings.BindingsMode = NSQLTranslation::EBindingsMode::DROP; - res = SqlToYqlWithSettings( - "insert into bindings.foo with truncate (x, y) values (1, 2);", - settings - ); - UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), ""); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine2 = [](const TString& word, const TString& line) { - if (word == "Write!") { - //UNIT_ASSERT_VALUES_EQUAL(line, ""); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__((Write! world sink (Key '('table (String '"foo"))) values '('('mode 'renew)))__")); - } - }; - - TWordCountHive elementStat2 = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat2, verifyLine2); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat2["Write!"]); - - settings.BindingsMode = NSQLTranslation::EBindingsMode::DROP_WITH_WARNING; - res = SqlToYqlWithSettings( - "insert into bindings.foo with truncate (x, y) values (1, 2);", - settings - ); - UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:1:13: Warning: Please remove 'bindings.' from your query, the support for this syntax will be dropped soon, code: 4538\n"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat3 = {{TString("Write!"), 0}}; - VerifyProgram(res, elementStat3, verifyLine2); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat3["Write!"]); - } - - Y_UNIT_TEST(TrailingCommaInWithout) { - UNIT_ASSERT(SqlToYql("SELECT * WITHOUT stream, FROM plato.Input").IsOk()); - UNIT_ASSERT(SqlToYql("SELECT a.* WITHOUT a.intersect, FROM plato.Input AS a").IsOk()); - UNIT_ASSERT(SqlToYql("SELECT a.* WITHOUT col1, col2, a.col3, FROM plato.Input AS a").IsOk()); - } - - Y_UNIT_TEST(NoStackOverflowOnBigCaseStatement) { - TStringBuilder req; - req << "select case 1 + 123"; - for (size_t i = 0; i < 20000; ++i) { - req << " when " << i << " then " << i + 1; - } - req << " else 100500 end;"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(CollectPreaggregatedInListLiteral) { - UNIT_ASSERT(SqlToYql("SELECT [COUNT(DISTINCT a+b)] FROM plato.Input").IsOk()); - } - - Y_UNIT_TEST(SmartParenInGroupByClause) { - UNIT_ASSERT(SqlToYql("SELECT * FROM plato.Input GROUP BY (k, v)").IsOk()); - } - - Y_UNIT_TEST(AlterTableRenameToIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table RENAME TO moved").IsOk()); - } - - Y_UNIT_TEST(AlterTableAddDropColumnIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table ADD COLUMN addc uint64, DROP COLUMN dropc, ADD addagain uint64").IsOk()); - } - - Y_UNIT_TEST(AlterTableSetTTLIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table SET (TTL = Interval(\"PT3H\") ON column)").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table SET (TTL = Interval(\"PT3H\") ON column AS SECONDS)").IsOk()); - } - - Y_UNIT_TEST(AlterTableSetTieringIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table SET (TIERING = 'my_tiering')").IsOk()); - } - - Y_UNIT_TEST(AlterTableAddChangefeedIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table ADD CHANGEFEED feed WITH (MODE = 'UPDATES', FORMAT = 'json')").IsOk()); - } - - Y_UNIT_TEST(AlterTableAlterChangefeedIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table ALTER CHANGEFEED feed DISABLE").IsOk()); - ExpectFailWithError("USE plato; ALTER TABLE table ALTER CHANGEFEED feed SET (FORMAT = 'proto');", - "<main>:1:57: Error: FORMAT alter is not supported\n"); - } - - Y_UNIT_TEST(AlterTableDropChangefeedIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table DROP CHANGEFEED feed").IsOk()); - } - - Y_UNIT_TEST(AlterTableSetPartitioningIsCorrect) { - UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table SET (AUTO_PARTITIONING_BY_SIZE = DISABLED)").IsOk()); - } - - Y_UNIT_TEST(AlterTableAddIndexWithIsNotSupported) { - ExpectFailWithError("USE plato; ALTER TABLE table ADD INDEX idx GLOBAL ON (col) WITH (a=b)", - "<main>:1:40: Error: with: alternative is not implemented yet: \n"); - } - - Y_UNIT_TEST(AlterTableAddIndexLocalIsNotSupported) { - ExpectFailWithError("USE plato; ALTER TABLE table ADD INDEX idx LOCAL ON (col)", - "<main>:1:40: Error: local: alternative is not implemented yet: \n"); - } - - Y_UNIT_TEST(CreateTableAddIndexVector) { - const auto result = SqlToYql(R"(USE plato; - CREATE TABLE table ( - pk INT32 NOT NULL, - col String, - INDEX idx GLOBAL USING vector_kmeans_tree - ON (col) COVER (col) - WITH (distance=cosine, vector_type=float, vector_dimension=1024,), - PRIMARY KEY (pk)) - )"); - UNIT_ASSERT_C(result.IsOk(), result.Issues.ToString()); - } - - Y_UNIT_TEST(AlterTableAddIndexVector) { - const auto result = SqlToYql(R"(USE plato; - ALTER TABLE table ADD INDEX idx - GLOBAL USING vector_kmeans_tree - ON (col) COVER (col) - WITH (distance=cosine, vector_type="float", vector_dimension=1024) - )"); - UNIT_ASSERT_C(result.IsOk(), result.Issues.ToString()); - } - - Y_UNIT_TEST(AlterTableAddIndexUnknownSubtype) { - ExpectFailWithError("USE plato; ALTER TABLE table ADD INDEX idx GLOBAL USING unknown ON (col)", - "<main>:1:57: Error: UNKNOWN index subtype is not supported\n"); - } - - Y_UNIT_TEST(AlterTableAddIndexMissedParameter) { - ExpectFailWithError(R"(USE plato; - ALTER TABLE table ADD INDEX idx - GLOBAL USING vector_kmeans_tree - ON (col) - WITH (distance=cosine, vector_type=float) - )", - "<main>:5:52: Error: vector_dimension should be set\n"); - } - - Y_UNIT_TEST(AlterTableAlterIndexSetPartitioningIsCorrect) { - const auto result = SqlToYql("USE plato; ALTER TABLE table ALTER INDEX index SET AUTO_PARTITIONING_MIN_PARTITIONS_COUNT 10"); - UNIT_ASSERT_C(result.IsOk(), result.Issues.ToString()); - } - - Y_UNIT_TEST(AlterTableAlterIndexSetMultiplePartitioningSettings) { - const auto result = SqlToYql("USE plato; ALTER TABLE table ALTER INDEX index SET " - "(AUTO_PARTITIONING_BY_LOAD = ENABLED, AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = 10)" - ); - UNIT_ASSERT_C(result.IsOk(), result.Issues.ToString()); - } - - Y_UNIT_TEST(AlterTableAlterIndexResetPartitioningIsNotSupported) { - ExpectFailWithError("USE plato; ALTER TABLE table ALTER INDEX index RESET (AUTO_PARTITIONING_MIN_PARTITIONS_COUNT)", - "<main>:1:55: Error: AUTO_PARTITIONING_MIN_PARTITIONS_COUNT reset is not supported\n" - ); - } - - Y_UNIT_TEST(AlterTableAlterColumnDropNotNullAstCorrect) { - auto reqSetNull = SqlToYql(R"( - USE plato; - CREATE TABLE tableName ( - id Uint32, - val Uint32 NOT NULL, - PRIMARY KEY (id) - ); - - COMMIT; - ALTER TABLE tableName ALTER COLUMN val DROP NOT NULL; - )"); - - UNIT_ASSERT(reqSetNull.IsOk()); - UNIT_ASSERT(reqSetNull.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - Y_UNUSED(word); - - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find( - R"(let world (Write! world sink (Key '('tablescheme (String '"tableName"))) (Void) '('('mode 'alter) '('actions '('('alterColumns '('('"val" '('changeColumnConstraints '('('drop_not_null)))))))))))" - )); - }; - - TWordCountHive elementStat({TString("\'mode \'alter")}); - VerifyProgram(reqSetNull, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["\'mode \'alter"]); - } - - Y_UNIT_TEST(AlterSequence) { - UNIT_ASSERT(SqlToYql(R"( - USE plato; - ALTER SEQUENCE sequence START WITH 10 INCREMENT 2 RESTART WITH 5; - )").IsOk()); - UNIT_ASSERT(SqlToYql(R"( - USE plato; - ALTER SEQUENCE sequence INCREMENT 2; - )").IsOk()); - UNIT_ASSERT(SqlToYql(R"( - USE plato; - ALTER SEQUENCE sequence INCREMENT 2 START 1000; - )").IsOk()); - UNIT_ASSERT(SqlToYql(R"( - USE plato; - ALTER SEQUENCE sequence RESTART START 1000; - )").IsOk()); - UNIT_ASSERT(SqlToYql(R"( - USE plato; - ALTER SEQUENCE IF EXISTS sequence INCREMENT 1000 START 100 RESTART; - )").IsOk()); - UNIT_ASSERT(SqlToYql(R"( - USE plato; - ALTER SEQUENCE IF EXISTS sequence RESTART 1000 START WITH 100 INCREMENT BY 7; - )").IsOk()); - } - - Y_UNIT_TEST(AlterSequenceIncorrect) { - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence START WITH 10 INCREMENT 2 RESTART WITH 5 RESTART;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:75: Error: Restart value defined more than once\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence START WITH 10 INCREMENT 2 START 100 RESTART WITH 5;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:60: Error: Start value defined more than once\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence INCREMENT BY 7 START WITH 10 INCREMENT 2 RESTART WITH 5 RESTART;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:62: Error: Increment defined more than once\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 100 START WITH 10 INCREMENT 2 RESTART WITH 5;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:77: Error: Restart value defined more than once\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1234234543563435151456 START WITH 10 INCREMENT 2;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:49: Error: Failed to parse number from string: 1234234543563435151456, number limit overflow\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1 START WITH 9223372036854775817 INCREMENT 4;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Start value: 9223372036854775817 cannot be greater than max value: 9223372036854775807\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 9223372036854775827 START WITH 5 INCREMENT 4;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Restart value: 9223372036854775827 cannot be greater than max value: 9223372036854775807\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1 START WITH 4 INCREMENT 0;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Increment must not be zero\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 0 START WITH 4 INCREMENT 1;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Restart value: 0 cannot be less than min value: 1\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1 START WITH 0 INCREMENT 1;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Start value: 0 cannot be less than min value: 1\n"); - } - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1 START WITH 1 INCREMENT 9223372036854775837;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Increment: 9223372036854775837 cannot be greater than max value: 9223372036854775807\n"); - } - } - - Y_UNIT_TEST(AlterSequenceCorrect) { - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence START WITH 10 INCREMENT 2 RESTART WITH 5;"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("sequence")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter")); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("alter_if_exists")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("start")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("increment")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("restart")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE IF EXISTS sequence INCREMENT 2 RESTART;"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("sequence")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter_if_exists")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("increment")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("restart")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - { - NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE IF EXISTS sequence START 10 INCREMENT BY 2;"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("sequence")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter_if_exists")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("start")); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("restart")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("increment")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - } - - Y_UNIT_TEST(ShowCreateTable) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - SHOW CREATE TABLE user; - )"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Read") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("showCreateTable")); - } - }; - - TWordCountHive elementStat = {{TString("Read"), 0}, {TString("showCreateTable"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Read"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["showCreateTable"]); - } - - Y_UNIT_TEST(OptionalAliases) { - UNIT_ASSERT(SqlToYql("USE plato; SELECT foo FROM (SELECT key foo FROM Input);").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT a.x FROM Input1 a JOIN Input2 b ON a.key = b.key;").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; SELECT a.x FROM (VALUES (1,2), (3,4)) a(x,key) JOIN Input b ON a.key = b.key;").IsOk()); - } - - Y_UNIT_TEST(TableNameConstness) { - UNIT_ASSERT(SqlToYql("USE plato; $path = 'foo'; SELECT TableName($path), count(*) FROM Input;").IsOk()); - UNIT_ASSERT(SqlToYql("$path = 'foo'; SELECT TableName($path, 'yt'), count(*) FROM plato.Input;").IsOk()); - ExpectFailWithError("USE plato; SELECT TableName(), count(*) FROM plato.Input;", - "<main>:1:19: Error: Expression has to be an aggregation function or key column, because aggregation is used elsewhere in this subquery\n"); - } - - Y_UNIT_TEST(UseShouldWorkAsColumnName) { - UNIT_ASSERT(SqlToYql("select use from (select 1 as use);").IsOk()); - } - - Y_UNIT_TEST(TrueFalseWorkAfterDollar) { - UNIT_ASSERT(SqlToYql("$ true = false; SELECT $ true or false;").IsOk()); - UNIT_ASSERT(SqlToYql("$False = 0; SELECT $False;").IsOk()); - } - - Y_UNIT_TEST(WithSchemaEquals) { - UNIT_ASSERT(SqlToYql("select * from plato.T with schema Struct<a:Int32, b:String>;").IsOk()); - UNIT_ASSERT(SqlToYql("select * from plato.T with columns = Struct<a:Int32, b:String>;").IsOk()); - } - - Y_UNIT_TEST(WithNonStructSchemaS3) { - NSQLTranslation::TTranslationSettings settings; - settings.ClusterMapping["s3bucket"] = NYql::S3ProviderName; - UNIT_ASSERT(SqlToYqlWithSettings("select * from s3bucket.`foo` with schema (col1 Int32, String as col2, Int64 as col3);", settings).IsOk()); - } - - Y_UNIT_TEST(AllowNestedTuplesInGroupBy) { - NYql::TAstParseResult res = SqlToYql("select count(*) from plato.Input group by 1 + (x, y, z);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Aggregate core '('\"group0\")")); - }; - - TWordCountHive elementStat({"Aggregate"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["Aggregate"] == 1); - } - - Y_UNIT_TEST(AllowGroupByWithParens) { - NYql::TAstParseResult res = SqlToYql("select count(*) from plato.Input group by (x, y as alias1, z);"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Aggregate core '('\"x\" '\"alias1\" '\"z\")")); - }; - - TWordCountHive elementStat({"Aggregate"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["Aggregate"] == 1); - } - - Y_UNIT_TEST(CreateAsyncReplicationParseCorrect) { - auto req = R"( - USE plato; - CREATE ASYNC REPLICATION MyReplication - FOR table1 AS table2, table3 AS table4 - WITH ( - CONNECTION_STRING = "grpc://localhost:2135/?database=/MyDatabase", - ENDPOINT = "localhost:2135", - DATABASE = "/MyDatabase" - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("MyReplication")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("create")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table1")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table2")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table3")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table4")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("connection_string")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("grpc://localhost:2135/?database=/MyDatabase")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("endpoint")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("localhost:2135")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("database")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("/MyDatabase")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateAsyncReplicationUnsupportedSettings) { - auto reqTpl = R"( - USE plato; - CREATE ASYNC REPLICATION MyReplication - FOR table1 AS table2, table3 AS table4 - WITH ( - %s = "%s" - ) - )"; - - auto settings = THashMap<TString, TString>{ - {"STATE", "DONE"}, - {"FAILOVER_MODE", "FORCE"}, - }; - - for (const auto& [k, v] : settings) { - auto req = Sprintf(reqTpl, k.c_str(), v.c_str()); - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), Sprintf("<main>:6:%zu: Error: %s is not supported in CREATE\n", 20 + k.size(), k.c_str())); - } - } - - Y_UNIT_TEST(AsyncReplicationInvalidCommitInterval) { - auto req = R"( - USE plato; - CREATE ASYNC REPLICATION MyReplication - FOR table1 AS table2, table3 AS table4 - WITH ( - COMMIT_INTERVAL = "FOO" - ); - )"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:6:35: Error: Literal of Interval type is expected for COMMIT_INTERVAL\n"); - } - - Y_UNIT_TEST(AlterAsyncReplicationParseCorrect) { - auto req = R"( - USE plato; - ALTER ASYNC REPLICATION MyReplication - SET ( - STATE = "DONE", - FAILOVER_MODE = "FORCE" - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("MyReplication")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("state")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("DONE")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("failover_mode")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("FORCE")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(AlterAsyncReplicationSettings) { - auto reqTpl = R"( - USE plato; - ALTER ASYNC REPLICATION MyReplication - SET ( - %s = "%s" - ) - )"; - - auto settings = THashMap<TString, TString>{ - {"connection_string", "grpc://localhost:2135/?database=/MyDatabase"}, - {"endpoint", "localhost:2135"}, - {"database", "/MyDatabase"}, - {"token", "foo"}, - {"token_secret_name", "foo_secret_name"}, - {"user", "user"}, - {"password", "bar"}, - {"password_secret_name", "bar_secret_name"}, - }; - - for (const auto& [k, v] : settings) { - auto req = Sprintf(reqTpl, k.c_str(), v.c_str()); - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&k, &v](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("MyReplication")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(k)); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(v)); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - } - - Y_UNIT_TEST(AlterAsyncReplicationUnsupportedSettings) { - { - auto req = R"( - USE plato; - ALTER ASYNC REPLICATION MyReplication SET (CONSISTENCY_LEVEL = "GLOBAL"); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:80: Error: CONSISTENCY_LEVEL is not supported in ALTER\n"); - } - { - auto req = R"( - USE plato; - ALTER ASYNC REPLICATION MyReplication SET (COMMIT_INTERVAL = Interval("PT10S")); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:87: Error: COMMIT_INTERVAL is not supported in ALTER\n"); - } - } - - Y_UNIT_TEST(AsyncReplicationInvalidSettings) { - auto req = R"( - USE plato; - ALTER ASYNC REPLICATION MyReplication SET (FOO = "BAR"); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:62: Error: Unknown replication setting: FOO\n"); - } - - Y_UNIT_TEST(DropAsyncReplicationParseCorrect) { - auto req = R"( - USE plato; - DROP ASYNC REPLICATION MyReplication; - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("MyReplication")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("drop")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropAsyncReplicationCascade) { - auto req = R"( - USE plato; - DROP ASYNC REPLICATION MyReplication CASCADE; - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropCascade")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(PragmaCompactGroupBy) { - auto req = "PRAGMA CompactGroupBy; SELECT key, COUNT(*) FROM plato.Input GROUP BY key;"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Aggregate") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('compact)")); - } - }; - - TWordCountHive elementStat = { {TString("Aggregate"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Aggregate"]); - } - - Y_UNIT_TEST(PragmaDisableCompactGroupBy) { - auto req = "PRAGMA DisableCompactGroupBy; SELECT key, COUNT(*) FROM plato.Input GROUP /*+ compact() */ BY key;"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Aggregate") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'('compact)")); - } - }; - - TWordCountHive elementStat = { {TString("Aggregate"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Aggregate"]); - } - - Y_UNIT_TEST(AutoSampleWorksWithNamedSubquery) { - UNIT_ASSERT(SqlToYql("$src = select * from plato.Input; select * from $src sample 0.2").IsOk()); - } - - Y_UNIT_TEST(AutoSampleWorksWithSubquery) { - UNIT_ASSERT(SqlToYql("select * from (select * from plato.Input) sample 0.2").IsOk()); - } - - Y_UNIT_TEST(CreateTableTrailingComma) { - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE tableName (Key Uint32, PRIMARY KEY (Key),);").IsOk()); - UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE tableName (Key Uint32,);").IsOk()); - } - - Y_UNIT_TEST(BetweenSymmetric) { - UNIT_ASSERT(SqlToYql("select 3 between symmetric 5 and 4;").IsOk()); - UNIT_ASSERT(SqlToYql("select 3 between asymmetric 5 and 4;").IsOk()); - UNIT_ASSERT(SqlToYql("use plato; select key between symmetric and and and from Input;").IsOk()); - UNIT_ASSERT(SqlToYql("use plato; select key between and and and from Input;").IsOk()); - } -} - -Y_UNIT_TEST_SUITE(ExternalFunction) { - Y_UNIT_TEST(ValidUseFunctions) { - - UNIT_ASSERT(SqlToYql( - "PROCESS plato.Input" - " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo', <|a: 123, b: a + 641|>)" - " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>," - " CONCURRENCY=3, OPTIMIZE_FOR='CALLS'").IsOk()); - - // use CALLS without quotes, as keyword - UNIT_ASSERT(SqlToYql( - "PROCESS plato.Input" - " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo')" - " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>," - " OPTIMIZE_FOR=CALLS").IsOk()); - - UNIT_ASSERT(SqlToYql( - "PROCESS plato.Input" - " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo', TableRow())" - " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>," - " CONCURRENCY=3").IsOk()); - - UNIT_ASSERT(SqlToYql( - "PROCESS plato.Input" - " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo')" - " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>," - " CONCURRENCY=3, BATCH_SIZE=1000000, CONNECTION='yc-folder34fse-con'," - " INIT=[0, 900]").IsOk()); - - UNIT_ASSERT(SqlToYql( - "PROCESS plato.Input" - " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'bar', TableRow())" - " WITH UNKNOWN_PARAM_1='837747712', UNKNOWN_PARAM_2=Tuple<Uint16, Utf8>," - " INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>").IsOk()); - } - - - Y_UNIT_TEST(InValidUseFunctions) { - ExpectFailWithError("PROCESS plato.Input USING some::udf(*) WITH INPUT_TYPE=Struct<a:Int32>", - "<main>:1:33: Error: PROCESS without USING EXTERNAL FUNCTION doesn't allow WITH block\n"); - - ExpectFailWithError("PROCESS plato.Input USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'jhhjfh88134d')" - " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>" - " ASSUME ORDER BY key", - "<main>:1:129: Error: PROCESS with USING EXTERNAL FUNCTION doesn't allow ASSUME block\n"); - - ExpectFailWithError("PROCESS plato.Input USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo', 'bar', 'baz')", - "<main>:1:15: Error: EXTERNAL FUNCTION requires from 2 to 3 arguments, but got: 4\n"); - - ExpectFailWithError("PROCESS plato.Input\n" - " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo', <|field_1: a1, field_b: b1|>)\n" - " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>,\n" - " CONCURRENCY=3, BATCH_SIZE=1000000, CONNECTION='yc-folder34fse-con',\n" - " CONCURRENCY=5, INPUT_TYPE=Struct<b:Bool>,\n" - " INIT=[0, 900]\n", - "<main>:5:2: Error: WITH \"CONCURRENCY\" clause should be specified only once\n" - "<main>:5:17: Error: WITH \"INPUT_TYPE\" clause should be specified only once\n"); - } } -Y_UNIT_TEST_SUITE(SqlToYQLErrors) { - Y_UNIT_TEST(UdfSyntaxSugarMissingCall) { - auto req = "SELECT Udf(DateTime::FromString, \"foo\" as RunConfig);"; - auto res = SqlToYql(req); - TString a1 = Err2Str(res); - TString a2("<main>:1:8: Error: Abstract Udf Node can't be used as a part of expression.\n"); - UNIT_ASSERT_NO_DIFF(a1, a2); - } - - Y_UNIT_TEST(UdfSyntaxSugarIsNotCallable) { - auto req = "SELECT Udf(123, \"foo\" as RunConfig);"; - auto res = SqlToYql(req); - TString a1 = Err2Str(res); - TString a2("<main>:1:8: Error: Udf: first argument must be a callable, like Foo::Bar\n"); - UNIT_ASSERT_NO_DIFF(a1, a2); - } - - Y_UNIT_TEST(UdfSyntaxSugarNoArgs) { - auto req = "SELECT Udf()();"; - auto res = SqlToYql(req); - TString a1 = Err2Str(res); - TString a2("<main>:1:8: Error: Udf: expected at least one argument\n"); - UNIT_ASSERT_NO_DIFF(a1, a2); - } - - Y_UNIT_TEST(StrayUTF8) { - /// 'c' in plato is russian here - NYql::TAstParseResult res = SqlToYql("select * from сedar.Input"); - UNIT_ASSERT(!res.Root); - - TString a1 = Err2Str(res); - TString a2(R"foo(<main>:1:14: Error: token recognition error at: 'с' -)foo"); - - UNIT_ASSERT_NO_DIFF(a1, a2); - } - - Y_UNIT_TEST(IvalidStringLiteralWithEscapedBackslash) { - NYql::TAstParseResult res1 = SqlToYql(R"foo($bar = 'a\\'b';)foo"); - NYql::TAstParseResult res2 = SqlToYql(R"foo($bar = "a\\"b";)foo"); - UNIT_ASSERT(!res1.Root); - UNIT_ASSERT(!res2.Root); - - UNIT_ASSERT_NO_DIFF(Err2Str(res1), "<main>:1:13: Error: token recognition error at: '';'\n"); - UNIT_ASSERT_NO_DIFF(Err2Str(res2), "<main>:1:13: Error: token recognition error at: '\";'\n"); - } - - Y_UNIT_TEST(InvalidHexInStringLiteral) { - NYql::TAstParseResult res = SqlToYql("select \"foo\\x1\\xfe\""); - UNIT_ASSERT(!res.Root); - TString a1 = Err2Str(res); - TString a2 = "<main>:1:15: Error: Failed to parse string literal: Invalid hexadecimal value\n"; - - UNIT_ASSERT_NO_DIFF(a1, a2); - } - - Y_UNIT_TEST(InvalidOctalInMultilineStringLiteral) { - NYql::TAstParseResult res = SqlToYql("select \"foo\n" - "bar\n" - "\\01\""); - UNIT_ASSERT(!res.Root); - TString a1 = Err2Str(res); - TString a2 = "<main>:3:4: Error: Failed to parse string literal: Invalid octal value\n"; - - UNIT_ASSERT_NO_DIFF(a1, a2); - } - - Y_UNIT_TEST(InvalidDoubleAtString) { - NYql::TAstParseResult res = SqlToYql("select @@@@@@"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: extraneous input '@' expecting {<EOF>, ';'}\n"); - } - - Y_UNIT_TEST(InvalidDoubleAtStringWhichWasAcceptedEarlier) { - NYql::TAstParseResult res = SqlToYql("SELECT @@foo@@ @ @@bar@@"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:15: Error: mismatched input '@' expecting {<EOF>, ';'}\n"); - } - - Y_UNIT_TEST(InvalidStringFromTable) { - NYql::TAstParseResult res = SqlToYql("select \"FOO\"\"BAR from plato.foo"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: token recognition error at: '\"BAR from plato.foo'\n"); - } - - Y_UNIT_TEST(InvalidDoubleAtStringFromTable) { - NYql::TAstParseResult res = SqlToYql("select @@@@@@ from plato.foo"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: mismatched input '@' expecting {<EOF>, ';'}\n"); - } - - Y_UNIT_TEST(SelectInvalidSyntax) { - NYql::TAstParseResult res = SqlToYql("select 1 form Wat"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:14: Error: extraneous input 'Wat' expecting {<EOF>, ';'}\n"); - } - - Y_UNIT_TEST(SelectNoCluster) { - NYql::TAstParseResult res = SqlToYql("select foo from bar"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: No cluster name given and no default cluster is selected\n"); - } - - Y_UNIT_TEST(SelectDuplicateColumns) { - NYql::TAstParseResult res = SqlToYql("select a, a from plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:11: Error: Unable to use duplicate column names. Collision in name: a\n"); - } - - Y_UNIT_TEST(SelectDuplicateLabels) { - NYql::TAstParseResult res = SqlToYql("select a as foo, b as foo from plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:18: Error: Unable to use duplicate column names. Collision in name: foo\n"); - } - - Y_UNIT_TEST(SelectCaseWithoutThen) { - NYql::TAstParseResult res = SqlToYql("select case when true 1;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - "<main>:1:22: Error: missing THEN at \'1\'\n" - "<main>:1:23: Error: extraneous input \';\' expecting {ELSE, END, WHEN}\n" - ); - } - - Y_UNIT_TEST(SelectComplexCaseWithoutThen) { - NYql::TAstParseResult res = SqlToYql( - "SELECT *\n" - "FROM plato.Input AS a\n" - "WHERE CASE WHEN a.key = \"foo\" a.subkey ELSE a.value END\n" - ); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:30: Error: missing THEN at 'a'\n"); - } - - Y_UNIT_TEST(SelectCaseWithoutEnd) { - NYql::TAstParseResult res = SqlToYql("select case a when b then c end from plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: ELSE is required\n"); - } - - Y_UNIT_TEST(SelectWithBadAggregationNoInput) { - NYql::TAstParseResult res = SqlToYql("select a, Min(b), c"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:8: Error: Column reference 'a'\n" - "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:15: Error: Column reference 'b'\n" - "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:19: Error: Column reference 'c'\n" - ); - } - - Y_UNIT_TEST(SelectWithBadAggregation) { - ExpectFailWithError("select count(*), 1 + key from plato.Input", - "<main>:1:22: Error: Column `key` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(SelectWithBadAggregatedTerms) { - ExpectFailWithError("select key, 2 * subkey from plato.Input group by key", - "<main>:1:17: Error: Column `subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(SelectDistinctWithBadAggregation) { - ExpectFailWithError("select distinct count(*), 1 + key from plato.Input", - "<main>:1:31: Error: Column `key` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - ExpectFailWithError("select distinct key, 2 * subkey from plato.Input group by key", - "<main>:1:26: Error: Column `subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(SelectWithBadAggregationInHaving) { - ExpectFailWithError("select key from plato.Input group by key\n" - "having \"f\" || value == \"foo\"", - "<main>:2:15: Error: Column `value` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(JoinWithNonAggregatedColumnInProjection) { - ExpectFailWithError("select a.key, 1 + b.subkey\n" - "from plato.Input1 as a join plato.Input2 as b using(key)\n" - "group by a.key;", - "<main>:1:19: Error: Column `b.subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - - ExpectFailWithError("select a.key, 1 + b.subkey.x\n" - "from plato.Input1 as a join plato.Input2 as b using(key)\n" - "group by a.key;", - "<main>:1:19: Error: Column must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(SelectWithBadAggregatedTermsWithSources) { - ExpectFailWithError("select key, 1 + a.subkey\n" - "from plato.Input1 as a\n" - "group by a.key;", - "<main>:1:17: Error: Column `a.subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - ExpectFailWithError("select key, 1 + a.subkey.x\n" - "from plato.Input1 as a\n" - "group by a.key;", - "<main>:1:17: Error: Column must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(WarnForAggregationBySelectAlias) { - NYql::TAstParseResult res = SqlToYql("select c + 1 as c from plato.Input\n" - "group by c"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - "<main>:2:11: Warning: GROUP BY will aggregate by column `c` instead of aggregating by SELECT expression with same alias, code: 4532\n" - "<main>:1:10: Warning: You should probably use alias in GROUP BY instead of using it here. Please consult documentation for more details, code: 4532\n"); - - res = SqlToYql("select c + 1 as c from plato.Input\n" - "group by Math::Floor(c + 2) as c;"); - - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - "<main>:2:22: Warning: GROUP BY will aggregate by column `c` instead of aggregating by SELECT expression with same alias, code: 4532\n" - "<main>:1:10: Warning: You should probably use alias in GROUP BY instead of using it here. Please consult documentation for more details, code: 4532\n"); - } - - Y_UNIT_TEST(NoWarnForAggregationBySelectAliasWhenAggrFunctionsAreUsedInAlias) { - NYql::TAstParseResult res = SqlToYql("select\n" - " cast(avg(val) as int) as value,\n" - " value as key\n" - "from\n" - " plato.Input\n" - "group by value"); - - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.Issues.Size() == 0); - - res = SqlToYql("select\n" - " cast(avg(val) over w as int) as value,\n" - " value as key\n" - "from\n" - " plato.Input\n" - "group by value\n" - "window w as ()"); - - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.Issues.Size() == 0); - } - - Y_UNIT_TEST(NoWarnForAggregationBySelectAliasWhenQualifiedNameIsUsed) { - NYql::TAstParseResult res = SqlToYql("select\n" - " Unwrap(a.key) as key\n" - "from plato.Input as a\n" - "join plato.Input2 as b using(k)\n" - "group by a.key;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.Issues.Size() == 0); - - res = SqlToYql("select Unwrap(a.key) as key\n" - "from plato.Input as a\n" - "group by a.key;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.Issues.Size() == 0); - } - - Y_UNIT_TEST(NoWarnForAggregationBySelectAliasWhenTrivialRenamingIsUsed) { - NYql::TAstParseResult res = SqlToYql("select a.key as key\n" - "from plato.Input as a\n" - "group by key;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.Issues.Size() == 0); - - res = SqlToYql("select key as key\n" - "from plato.Input\n" - "group by key;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.Issues.Size() == 0); - } - - Y_UNIT_TEST(ErrorByAggregatingByExpressionWithSameExpressionInSelect) { - ExpectFailWithError("select k * 2 from plato.Input group by k * 2", - "<main>:1:8: Error: Column `k` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(ErrorForAggregationBySelectAlias) { - ExpectFailWithError("select key, Math::Floor(1.1 + a.subkey) as foo\n" - "from plato.Input as a\n" - "group by a.key, foo;", - "<main>:3:17: Warning: GROUP BY will aggregate by column `foo` instead of aggregating by SELECT expression with same alias, code: 4532\n" - "<main>:1:19: Warning: You should probably use alias in GROUP BY instead of using it here. Please consult documentation for more details, code: 4532\n" - "<main>:1:31: Error: Column `a.subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - - ExpectFailWithError("select c + 1 as c from plato.Input\n" - "group by Math::Floor(c + 2);", - "<main>:2:22: Warning: GROUP BY will aggregate by column `c` instead of aggregating by SELECT expression with same alias, code: 4532\n" - "<main>:1:10: Warning: You should probably use alias in GROUP BY instead of using it here. Please consult documentation for more details, code: 4532\n" - "<main>:1:8: Error: Column `c` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(ExplainQueryPlan) { - NYql::TAstParseResult res = SqlToYql("EXPLAIN Q U E R Y PLAN SELECT 1;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "<main>:1:8: Error: mismatched input 'Q' expecting {"); - } - - Y_UNIT_TEST(SelectWithDuplicateGroupingColumns) { - NYql::TAstParseResult res = SqlToYql("select c from plato.Input group by c, c"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Duplicate grouping column: c\n"); - } - - Y_UNIT_TEST(SelectWithBadAggregationInGrouping) { - NYql::TAstParseResult res = SqlToYql("select a, Min(b), c group by c"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:30: Error: Column reference 'c'\n"); - } - - Y_UNIT_TEST(SelectWithOpOnBadAggregation) { - ExpectFailWithError("select 1 + a + Min(b) from plato.Input", - "<main>:1:12: Error: Column `a` must either be a key column in GROUP BY or it should be used in aggregation function\n"); - } - - Y_UNIT_TEST(SelectOrderByConstantNum) { - NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by 1"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:36: Error: Unable to ORDER BY constant expression\n"); - } - - Y_UNIT_TEST(SelectOrderByConstantExpr) { - NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by 1 * 42"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:38: Error: Unable to ORDER BY constant expression\n"); - } - - Y_UNIT_TEST(SelectOrderByConstantString) { - NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by \"nest\""); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:36: Error: Unable to ORDER BY constant expression\n"); - } - - Y_UNIT_TEST(SelectOrderByAggregated) { - NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by min(a)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:36: Error: Unable to ORDER BY aggregated values\n"); - } - - Y_UNIT_TEST(ErrorInOrderByExpresison) { - NYql::TAstParseResult res = SqlToYql("select key, value from plato.Input order by (key as zey)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:45: Error: You should use in ORDER BY column name, qualified field, callable function or expression\n"); - } - - Y_UNIT_TEST(ErrorsInOrderByWhenColumnIsMissingInProjection) { - ExpectFailWithError("select subkey from (select 1 as subkey) order by key", "<main>:1:50: Error: Column key is not in source column set\n"); - ExpectFailWithError("select subkey from plato.Input as a order by x.key", "<main>:1:46: Error: Unknown correlation name: x\n"); - ExpectFailWithError("select distinct a, b from plato.Input order by c", "<main>:1:48: Error: Column c is not in source column set. Did you mean a?\n"); - ExpectFailWithError("select count(*) as a from plato.Input order by c", "<main>:1:48: Error: Column c is not in source column set. Did you mean a?\n"); - ExpectFailWithError("select count(*) as a, b, from plato.Input group by b order by c", "<main>:1:63: Error: Column c is not in source column set. Did you mean a?\n"); - UNIT_ASSERT(SqlToYql("select a, b from plato.Input order by c").IsOk()); - } - - Y_UNIT_TEST(SelectAggregatedWhere) { - NYql::TAstParseResult res = SqlToYql("select * from plato.Input where count(key)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:33: Error: Can not use aggregated values in filtering\n"); - } - - Y_UNIT_TEST(DoubleFrom) { - NYql::TAstParseResult res = SqlToYql("from plato.Input select * from plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:27: Error: Only one FROM clause is allowed\n"); - } - - Y_UNIT_TEST(SelectJoinMissingCorrName) { - NYql::TAstParseResult res = SqlToYql("select * from plato.Input1 as a join plato.Input2 as b on a.key == key"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:65: Error: JOIN: column requires correlation name\n"); - } - - Y_UNIT_TEST(SelectJoinMissingCorrName1) { - NYql::TAstParseResult res = SqlToYql( - "use plato;\n" - "$foo = select * from Input1;\n" - "select * from Input2 join $foo USING(key);\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:27: Error: JOIN: missing correlation name for source\n"); - } - - Y_UNIT_TEST(SelectJoinMissingCorrName2) { - NYql::TAstParseResult res = SqlToYql( - "use plato;\n" - "$foo = select * from Input1;\n" - "select * from Input2 cross join $foo;\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:33: Error: JOIN: missing correlation name for source\n"); - } - - Y_UNIT_TEST(SelectJoinEmptyCorrNames) { - NYql::TAstParseResult res = SqlToYql( - "$left = (SELECT * FROM plato.Input1 LIMIT 2);\n" - "$right = (SELECT * FROM plato.Input2 LIMIT 2);\n" - "SELECT * FROM $left FULL JOIN $right USING (key);\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:45: Error: At least one correlation name is required in join\n"); - } - - Y_UNIT_TEST(SelectJoinSameCorrNames) { - NYql::TAstParseResult res = SqlToYql("SELECT Input.key FROM plato.Input JOIN plato.Input1 ON Input.key == Input.subkey\n"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:66: Error: JOIN: different correlation names are required for joined tables\n"); - } - - Y_UNIT_TEST(SelectJoinConstPredicateArg) { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input1 as A JOIN plato.Input2 as B ON A.key == B.key AND A.subkey == \"wtf\"\n"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:87: Error: JOIN: each equality predicate argument must depend on exactly one JOIN input\n"); - } - - Y_UNIT_TEST(SelectJoinNonEqualityPredicate) { - NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input1 as A JOIN plato.Input2 as B ON A.key == B.key AND A.subkey > B.subkey\n"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:87: Error: JOIN ON expression must be a conjunction of equality predicates\n"); - } - - Y_UNIT_TEST(SelectEquiJoinCorrNameOutOfScope) { - NYql::TAstParseResult res = SqlToYql( - "PRAGMA equijoin;\n" - "SELECT * FROM plato.A JOIN plato.B ON A.key == C.key JOIN plato.C ON A.subkey == C.subkey;\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:45: Error: JOIN: can not use source: C in equality predicate, it is out of current join scope\n"); - } - - Y_UNIT_TEST(SelectEquiJoinNoRightSource) { - NYql::TAstParseResult res = SqlToYql( - "PRAGMA equijoin;\n" - "SELECT * FROM plato.A JOIN plato.B ON A.key == B.key JOIN plato.C ON A.subkey == B.subkey;\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:79: Error: JOIN ON equality predicate must have one of its arguments from the rightmost source\n"); - } - - Y_UNIT_TEST(SelectEquiJoinOuterWithoutType) { - NYql::TAstParseResult res = SqlToYql( - "SELECT * FROM plato.A Outer JOIN plato.B ON A.key == B.key;\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:23: Error: Invalid join type: OUTER JOIN. OUTER keyword is optional and can only be used after LEFT, RIGHT or FULL\n"); - } - - Y_UNIT_TEST(SelectEquiJoinOuterWithWrongType) { - NYql::TAstParseResult res = SqlToYql( - "SELECT * FROM plato.A LEFT semi OUTER JOIN plato.B ON A.key == B.key;\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:33: Error: Invalid join type: LEFT SEMI OUTER JOIN. OUTER keyword is optional and can only be used after LEFT, RIGHT or FULL\n"); - } - - Y_UNIT_TEST(InsertNoCluster) { - NYql::TAstParseResult res = SqlToYql("insert into Output (foo) values (1)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: No cluster name given and no default cluster is selected\n"); - } - - Y_UNIT_TEST(InsertValuesNoLabels) { - NYql::TAstParseResult res = SqlToYql("insert into plato.Output values (1)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: INSERT INTO ... VALUES requires specification of table columns\n"); - } - - Y_UNIT_TEST(UpsertValuesNoLabelsKikimr) { - NYql::TAstParseResult res = SqlToYql("upsert into plato.Output values (1)", 10, TString(NYql::KikimrProviderName)); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: UPSERT INTO ... VALUES requires specification of table columns\n"); - } - - Y_UNIT_TEST(ReplaceValuesNoLabelsKikimr) { - NYql::TAstParseResult res = SqlToYql("replace into plato.Output values (1)", 10, TString(NYql::KikimrProviderName)); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:20: Error: REPLACE INTO ... VALUES requires specification of table columns\n"); - } - - Y_UNIT_TEST(InsertValuesInvalidLabels) { - NYql::TAstParseResult res = SqlToYql("insert into plato.Output (foo) values (1, 2)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:27: Error: VALUES have 2 columns, INSERT INTO expects: 1\n"); - } - - Y_UNIT_TEST(BuiltinFileOpNoArgs) { - NYql::TAstParseResult res = SqlToYql("select FilePath()"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: FilePath() requires exactly 1 arguments, given: 0\n"); - } - - Y_UNIT_TEST(ProcessWithHaving) { - NYql::TAstParseResult res = SqlToYql("process plato.Input using some::udf(value) having value == 1"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:15: Error: PROCESS does not allow HAVING yet! You may request it on yql@ maillist.\n"); - } - - Y_UNIT_TEST(ReduceNoBy) { - NYql::TAstParseResult res = SqlToYql("reduce plato.Input using some::udf(value)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: mismatched input 'using' expecting {',', ON, PRESORT}\n"); - } - - Y_UNIT_TEST(ReduceDistinct) { - NYql::TAstParseResult res = SqlToYql("reduce plato.Input on key using some::udf(distinct value)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:43: Error: DISTINCT can not be used in PROCESS/REDUCE\n"); - } - - Y_UNIT_TEST(CreateTableWithView) { - NYql::TAstParseResult res = SqlToYql("CREATE TABLE plato.foo:bar (key INT);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:22: Error: mismatched input ':' expecting '('\n"); - } - - Y_UNIT_TEST(AsteriskWithSomethingAfter) { - NYql::TAstParseResult res = SqlToYql("select *, LENGTH(value) from plato.Input;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Unable to use plain '*' with other projection items. Please use qualified asterisk instead: '<table>.*' (<table> can be either table name or table alias).\n"); - } - - Y_UNIT_TEST(AsteriskWithSomethingBefore) { - NYql::TAstParseResult res = SqlToYql("select LENGTH(value), * from plato.Input;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:23: Error: Unable to use plain '*' with other projection items. Please use qualified asterisk instead: '<table>.*' (<table> can be either table name or table alias).\n"); - } - - Y_UNIT_TEST(DuplicatedQualifiedAsterisk) { - NYql::TAstParseResult res = SqlToYql("select in.*, key, in.* from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: Unable to use twice same quialified asterisk. Invalid source: in\n"); - } - - Y_UNIT_TEST(BrokenLabel) { - NYql::TAstParseResult res = SqlToYql("select in.*, key as `funny.label` from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:14: Error: Unable to use '.' in column name. Invalid column name: funny.label\n"); - } - - Y_UNIT_TEST(KeyConflictDetect0) { - NYql::TAstParseResult res = SqlToYql("select key, in.key as key from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:13: Error: Unable to use duplicate column names. Collision in name: key\n"); - } - - Y_UNIT_TEST(KeyConflictDetect1) { - NYql::TAstParseResult res = SqlToYql("select length(key) as key, key from plato.Input;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:28: Error: Unable to use duplicate column names. Collision in name: key\n"); - } - - Y_UNIT_TEST(KeyConflictDetect2) { - NYql::TAstParseResult res = SqlToYql("select key, in.key from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); - } - - Y_UNIT_TEST(AutogenerationAliasWithCollisionConflict1) { - UNIT_ASSERT(SqlToYql("select LENGTH(Value), key as column0 from plato.Input;").IsOk()); - } - - Y_UNIT_TEST(AutogenerationAliasWithCollisionConflict2) { - UNIT_ASSERT(SqlToYql("select key as column1, LENGTH(Value) from plato.Input;").IsOk()); - } - - Y_UNIT_TEST(MissedSourceTableForQualifiedAsteriskOnSimpleSelect) { - NYql::TAstParseResult res = SqlToYql("use plato; select Intop.*, Input.key from plato.Input;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: Unknown correlation name: Intop\n"); - } - - Y_UNIT_TEST(MissedSourceTableForQualifiedAsteriskOnJoin) { - NYql::TAstParseResult res = SqlToYql("use plato; select tmissed.*, t2.*, t1.key from plato.Input as t1 join plato.Input as t2 on t1.key==t2.key;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: Unknown correlation name for asterisk: tmissed\n"); - } - - Y_UNIT_TEST(UnableToReferenceOnNotExistSubcolumn) { - NYql::TAstParseResult res = SqlToYql("select b.subkey from (select key from plato.Input as a) as b;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Column subkey is not in source column set\n"); - } - - Y_UNIT_TEST(ConflictOnSameNameWithQualify0) { - NYql::TAstParseResult res = SqlToYql("select in.key, in.key as key from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); - } - - Y_UNIT_TEST(ConflictOnSameNameWithQualify1) { - NYql::TAstParseResult res = SqlToYql("select in.key, length(key) as key from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); - } - - Y_UNIT_TEST(ConflictOnSameNameWithQualify2) { - NYql::TAstParseResult res = SqlToYql("select key, in.key from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); - } - - Y_UNIT_TEST(ConflictOnSameNameWithQualify3) { - NYql::TAstParseResult res = SqlToYql("select in.key, subkey as key from plato.Input as in;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); - } - - Y_UNIT_TEST(SelectFlattenBySameColumns) { - NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key, key as kk)"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:46: Error: Duplicate column name found: key in FlattenBy section\n"); - } - - Y_UNIT_TEST(SelectFlattenBySameAliases) { - NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key as kk, subkey as kk);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Error: Duplicate alias found: kk in FlattenBy section\n"); - } - - Y_UNIT_TEST(SelectFlattenByExprSameAliases) { - NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key as kk, ListSkip(subkey,1) as kk);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Error: Collision between alias and column name: kk in FlattenBy section\n"); - } - - Y_UNIT_TEST(SelectFlattenByConflictNameAndAlias0) { - NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key, subkey as key);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:46: Error: Collision between alias and column name: key in FlattenBy section\n"); - } - - Y_UNIT_TEST(SelectFlattenByConflictNameAndAlias1) { - NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key as kk, subkey as key);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Error: Collision between alias and column name: key in FlattenBy section\n"); - } - - Y_UNIT_TEST(SelectFlattenByExprConflictNameAndAlias1) { - NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key as kk, ListSkip(subkey,1) as key);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Error: Duplicate column name found: key in FlattenBy section\n"); - } - - Y_UNIT_TEST(SelectFlattenByUnnamedExpr) { - NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key, ListSkip(key, 1))"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:46: Error: Unnamed expression after FLATTEN BY is not allowed\n"); - } - - Y_UNIT_TEST(UseInOnStrings) { - NYql::TAstParseResult res = SqlToYql("select * from plato.Input where \"foo\" in \"foovalue\";"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:42: Error: Unable to use IN predicate with string argument, it won't search substring - " - "expecting tuple, list, dict or single column table source\n"); - } - - Y_UNIT_TEST(UseSubqueryInScalarContextInsideIn) { - NYql::TAstParseResult res = SqlToYql("$q = (select key from plato.Input); select * from plato.Input where subkey in ($q);"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:79: Warning: Using subrequest in scalar context after IN, " - "perhaps you should remove parenthesis here, code: 4501\n"); - } - - Y_UNIT_TEST(InHintsWithKeywordClash) { - NYql::TAstParseResult res = SqlToYql("SELECT COMPACT FROM plato.Input WHERE COMPACT IN COMPACT `COMPACT`(1,2,3)"); - UNIT_ASSERT(!res.Root); - // should try to parse last compact as call expression - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:58: Error: Unknown builtin: COMPACT\n"); - } - - Y_UNIT_TEST(ErrorColumnPosition) { - NYql::TAstParseResult res = SqlToYql( - "USE plato;\n" - "SELECT \n" - "value FROM (\n" - "select key from Input\n" - ");\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:1: Error: Column value is not in source column set\n"); - } - - Y_UNIT_TEST(PrimaryViewAbortMapReduce) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input VIEW PRIMARY KEY"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: primary view is not supported for yt tables\n"); - } - - Y_UNIT_TEST(InsertAbortMapReduce) { - NYql::TAstParseResult res = SqlToYql("INSERT OR ABORT INTO plato.Output SELECT key FROM plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: INSERT OR ABORT INTO is not supported for yt tables\n"); - } - - Y_UNIT_TEST(ReplaceIntoMapReduce) { - NYql::TAstParseResult res = SqlToYql("REPLACE INTO plato.Output SELECT key FROM plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: Meaning of REPLACE INTO has been changed, now you should use INSERT INTO <table> WITH TRUNCATE ... for yt\n"); - } - - Y_UNIT_TEST(UpsertIntoMapReduce) { - NYql::TAstParseResult res = SqlToYql("UPSERT INTO plato.Output SELECT key FROM plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: UPSERT INTO is not supported for yt tables\n"); - } - - Y_UNIT_TEST(UpdateMapReduce) { - NYql::TAstParseResult res = SqlToYql("UPDATE plato.Output SET value = value + 1 WHERE key < 1"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: UPDATE is unsupported for yt\n"); - } - - Y_UNIT_TEST(DeleteMapReduce) { - NYql::TAstParseResult res = SqlToYql("DELETE FROM plato.Output WHERE key < 1"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: DELETE is unsupported for yt\n"); - } - - Y_UNIT_TEST(ReplaceIntoWithTruncate) { - NYql::TAstParseResult res = SqlToYql("REPLACE INTO plato.Output WITH TRUNCATE SELECT key FROM plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:32: Error: Unable REPLACE INTO with truncate mode\n"); - } - - Y_UNIT_TEST(UpsertIntoWithTruncate) { - NYql::TAstParseResult res = SqlToYql("UPSERT INTO plato.Output WITH TRUNCATE SELECT key FROM plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:31: Error: Unable UPSERT INTO with truncate mode\n"); - } - - Y_UNIT_TEST(InsertIntoWithTruncateKikimr) { - NYql::TAstParseResult res = SqlToYql("INSERT INTO plato.Output WITH TRUNCATE SELECT key FROM plato.Input", 10, TString(NYql::KikimrProviderName)); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: INSERT INTO WITH TRUNCATE is not supported for kikimr tables\n"); - } - - Y_UNIT_TEST(InsertIntoWithWrongArgumentCount) { - NYql::TAstParseResult res = SqlToYql("insert into plato.Output with truncate (key, value, subkey) values (5, '1', '2', '3');"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:53: Error: VALUES have 4 columns, INSERT INTO ... WITH TRUNCATE expects: 3\n"); - } - - Y_UNIT_TEST(UpsertWithWrongArgumentCount) { - NYql::TAstParseResult res = SqlToYql("upsert into plato.Output (key, value, subkey) values (2, '3');", 10, TString(NYql::KikimrProviderName)); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:39: Error: VALUES have 2 columns, UPSERT INTO expects: 3\n"); - } - - Y_UNIT_TEST(GroupingSetByExprWithoutAlias) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY GROUPING SETS (cast(key as uint32), subkey);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:53: Error: Unnamed expressions are not supported in GROUPING SETS. Please use '<expr> AS <name>'.\n"); - } - - Y_UNIT_TEST(GroupingSetByExprWithoutAlias2) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY subkey || subkey, GROUPING SETS (\n" - "cast(key as uint32), subkey);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:1: Error: Unnamed expressions are not supported in GROUPING SETS. Please use '<expr> AS <name>'.\n"); - } - - Y_UNIT_TEST(CubeByExprWithoutAlias) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY CUBE (key, subkey / key);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:56: Error: Unnamed expressions are not supported in CUBE. Please use '<expr> AS <name>'.\n"); - } - - Y_UNIT_TEST(RollupByExprWithoutAlias) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY ROLLUP (subkey / key);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:53: Error: Unnamed expressions are not supported in ROLLUP. Please use '<expr> AS <name>'.\n"); - } - - Y_UNIT_TEST(GroupByHugeCubeDeniedNoPragma) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY CUBE (key, subkey, value, key + subkey as sum, key - subkey as sub, key + val as keyval);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:119: Error: GROUP BY CUBE is allowed only for 5 columns, but you use 6\n"); - } - - Y_UNIT_TEST(GroupByInvalidPragma) { - NYql::TAstParseResult res = SqlToYql("PRAGMA GroupByCubeLimit = '-4';"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:27: Error: Expected unsigned integer literal as a single argument for: GroupByCubeLimit\n"); - } - - Y_UNIT_TEST(GroupByHugeCubeDeniedPragme) { - NYql::TAstParseResult res = SqlToYql("PRAGMA GroupByCubeLimit = '4'; SELECT key FROM plato.Input GROUP BY CUBE (key, subkey, value, key + subkey as sum, key - subkey as sub);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:132: Error: GROUP BY CUBE is allowed only for 4 columns, but you use 5\n"); - } - - Y_UNIT_TEST(GroupByFewBigCubes) { - NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY CUBE(key, subkey, key + subkey as sum), CUBE(value, value + key + subkey as total);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Unable to GROUP BY more than 64 groups, you try use 80 groups\n"); - } - - Y_UNIT_TEST(GroupByFewBigCubesWithPragmaLimit) { - NYql::TAstParseResult res = SqlToYql("PRAGMA GroupByLimit = '16'; SELECT key FROM plato.Input GROUP BY GROUPING SETS(key, subkey, key + subkey as sum), ROLLUP(value, value + key + subkey as total);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:29: Error: Unable to GROUP BY more than 16 groups, you try use 18 groups\n"); - } - - Y_UNIT_TEST(NoGroupingColumn0) { - NYql::TAstParseResult res = SqlToYql( - "select count(1), key_first, val_first, grouping(key_first, val_first, nomind) as group\n" - "from plato.Input group by grouping sets (cast(key as uint32) /100 as key_first, Substring(value, 1, 1) as val_first);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:71: Error: Column 'nomind' is not a grouping column\n"); - } - - Y_UNIT_TEST(NoGroupingColumn1) { - NYql::TAstParseResult res = SqlToYql("select count(1), grouping(key, value) as group_duo from plato.Input group by cube (key, subkey);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:32: Error: Column 'value' is not a grouping column\n"); - } - - Y_UNIT_TEST(EmptyAccess0) { - NYql::TAstParseResult res = SqlToYql("insert into plato.Output (list0, list1) values (AsList(0, 1, 2), AsList(``));"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:73: Error: Column reference \"\" is not allowed in current scope\n"); - } - - Y_UNIT_TEST(EmptyAccess1) { - NYql::TAstParseResult res = SqlToYql("insert into plato.Output (list0, list1) values (AsList(0, 1, 2), ``);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:66: Error: Column reference \"\" is not allowed in current scope\n"); - } - - Y_UNIT_TEST(UseUnknownColumnInInsert) { - NYql::TAstParseResult res = SqlToYql("insert into plato.Output (list0, list1) values (AsList(0, 1, 2), AsList(`test`));"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:73: Error: Column reference \"test\" is not allowed in current scope\n"); - } - - Y_UNIT_TEST(GroupByEmptyColumn) { - NYql::TAstParseResult res = SqlToYql("select count(1) from plato.Input group by ``;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:43: Error: Column name can not be empty\n"); - } - - Y_UNIT_TEST(ConvertNumberOutOfBase) { - NYql::TAstParseResult res = SqlToYql("select 0o80l;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to parse number from string: 0o80l, char: '8' is out of base: 8\n"); - } - - Y_UNIT_TEST(ConvertNumberOutOfRangeForInt64ButFitsInUint64) { - NYql::TAstParseResult res = SqlToYql("select 0xc000000000000000l;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to parse 13835058055282163712 as integer literal of Int64 type: value out of range for Int64\n"); - } - - Y_UNIT_TEST(ConvertNumberOutOfRangeUint64) { - NYql::TAstParseResult res = SqlToYql("select 0xc0000000000000000l;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to parse number from string: 0xc0000000000000000l, number limit overflow\n"); - - res = SqlToYql("select 1234234543563435151456;\n"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to parse number from string: 1234234543563435151456, number limit overflow\n"); - } - - Y_UNIT_TEST(ConvertNumberNegativeOutOfRange) { - NYql::TAstParseResult res = SqlToYql("select -9223372036854775808;\n" - "select -9223372036854775809;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:8: Error: Failed to parse negative integer: -9223372036854775809, number limit overflow\n"); - } - - Y_UNIT_TEST(InvaildUsageReal0) { - NYql::TAstParseResult res = SqlToYql("select .0;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "<main>:1:7: Error: extraneous input '.' expecting {"); - } - - Y_UNIT_TEST(InvaildUsageReal1) { - NYql::TAstParseResult res = SqlToYql("select .0f;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "<main>:1:7: Error: extraneous input '.' expecting {"); - } - - Y_UNIT_TEST(InvaildUsageWinFunctionWithoutWindow) { - NYql::TAstParseResult res = SqlToYql("select lead(key, 2) from plato.Input;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to use window function Lead without window specification\n"); - } - - Y_UNIT_TEST(DropTableWithIfExists) { - NYql::TAstParseResult res = SqlToYql("DROP TABLE IF EXISTS plato.foo;"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("drop_if_exists")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(TooManyErrors) { - const char* q = R"( - USE plato; - select A, B, C, D, E, F, G, H, I, J, K, L, M, N from (select b from `abc`); -)"; - - NYql::TAstParseResult res = SqlToYql(q, 10); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - R"(<main>:3:16: Error: Column A is not in source column set. Did you mean b? -<main>:3:19: Error: Column B is not in source column set. Did you mean b? -<main>:3:22: Error: Column C is not in source column set. Did you mean b? -<main>:3:25: Error: Column D is not in source column set. Did you mean b? -<main>:3:28: Error: Column E is not in source column set. Did you mean b? -<main>:3:31: Error: Column F is not in source column set. Did you mean b? -<main>:3:34: Error: Column G is not in source column set. Did you mean b? -<main>:3:37: Error: Column H is not in source column set. Did you mean b? -<main>:3:40: Error: Column I is not in source column set. Did you mean b? -<main>: Error: Too many issues, code: 1 -)"); - }; - - Y_UNIT_TEST(ShouldCloneBindingForNamedParameter) { - NYql::TAstParseResult res = SqlToYql(R"($f = () -> { - $value_type = TypeOf(1); - $pair_type = StructType( - TypeOf("2") AS key, - $value_type AS value - ); - - RETURN TupleType( - ListType($value_type), - $pair_type); -}; - -select FormatType($f()); -)"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(BlockedInvalidFrameBounds) { - auto check = [](const TString& frame, const TString& err) { - const TString prefix = "SELECT SUM(x) OVER w FROM plato.Input WINDOW w AS (PARTITION BY key ORDER BY subkey\n"; - NYql::TAstParseResult res = SqlToYql(prefix + frame + ")"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), err); - }; - - check("ROWS UNBOUNDED FOLLOWING", "<main>:2:5: Error: Frame cannot start from UNBOUNDED FOLLOWING\n"); - check("ROWS BETWEEN 5 PRECEDING AND UNBOUNDED PRECEDING", "<main>:2:29: Error: Frame cannot end with UNBOUNDED PRECEDING\n"); - check("ROWS BETWEEN CURRENT ROW AND 5 PRECEDING", "<main>:2:13: Error: Frame cannot start from CURRENT ROW and end with PRECEDING\n"); - check("ROWS BETWEEN 5 FOLLOWING AND CURRENT ROW", "<main>:2:14: Error: Frame cannot start from FOLLOWING and end with CURRENT ROW\n"); - check("ROWS BETWEEN 5 FOLLOWING AND 5 PRECEDING", "<main>:2:14: Error: Frame cannot start from FOLLOWING and end with PRECEDING\n"); - } - - Y_UNIT_TEST(BlockedRangeValueWithoutSingleOrderBy) { - UNIT_ASSERT(SqlToYql("SELECT COUNT(*) OVER (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM plato.Input").IsOk()); - UNIT_ASSERT(SqlToYql("SELECT COUNT(*) OVER (RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) FROM plato.Input").IsOk()); - - auto res = SqlToYql("SELECT COUNT(*) OVER (RANGE 5 PRECEDING) FROM plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:29: Error: RANGE with <offset> PRECEDING/FOLLOWING requires exactly one expression in ORDER BY partition clause\n"); - - res = SqlToYql("SELECT COUNT(*) OVER (ORDER BY key, value RANGE 5 PRECEDING) FROM plato.Input"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:49: Error: RANGE with <offset> PRECEDING/FOLLOWING requires exactly one expression in ORDER BY partition clause\n"); - } - - Y_UNIT_TEST(NoColumnsInFrameBounds) { - NYql::TAstParseResult res = SqlToYql( - "SELECT SUM(x) OVER w FROM plato.Input WINDOW w AS (ROWS BETWEEN\n" - " 1 + key PRECEDING AND 2 + key FOLLOWING);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:6: Error: Column reference \"key\" is not allowed in current scope\n"); - } - - Y_UNIT_TEST(WarnOnEmptyFrameBounds) { - NYql::TAstParseResult res = SqlToYql( - "SELECT SUM(x) OVER w FROM plato.Input WINDOW w AS (PARTITION BY key ORDER BY subkey\n" - "ROWS BETWEEN 10 FOLLOWING AND 5 FOLLOWING)"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:14: Warning: Used frame specification implies empty window frame, code: 4520\n"); - } - - Y_UNIT_TEST(WarnOnRankWithUnorderedWindow) { - NYql::TAstParseResult res = SqlToYql("SELECT RANK() OVER w FROM plato.Input WINDOW w AS ()"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Warning: Rank() is used with unordered window - all rows will be considered equal to each other, code: 4521\n"); - } - - Y_UNIT_TEST(WarnOnRankExprWithUnorderedWindow) { - NYql::TAstParseResult res = SqlToYql("SELECT RANK(key) OVER w FROM plato.Input WINDOW w AS ()"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Warning: Rank(<expression>) is used with unordered window - the result is likely to be undefined, code: 4521\n"); - } - - Y_UNIT_TEST(AnyAsTableName) { - NYql::TAstParseResult res = SqlToYql("use plato; select * from any;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:28: Error: no viable alternative at input 'any;'\n"); - } - - Y_UNIT_TEST(IncorrectOrderOfLambdaOptionalArgs) { - NYql::TAstParseResult res = SqlToYql("$f = ($x?, $y)->($x + $y); select $f(1);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Non-optional argument can not follow optional one\n"); - } - - Y_UNIT_TEST(IncorrectOrderOfActionOptionalArgs) { - NYql::TAstParseResult res = SqlToYql("define action $f($x?, $y) as select $x,$y; end define; do $f(1);"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:23: Error: Non-optional argument can not follow optional one\n"); - } - - Y_UNIT_TEST(NotAllowedQuestionOnNamedNode) { - NYql::TAstParseResult res = SqlToYql("$f = 1; select $f?;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:18: Error: Unexpected token '?' at the end of expression\n"); - } - - Y_UNIT_TEST(AnyAndCrossJoin) { - NYql::TAstParseResult res = SqlToYql("use plato; select * from any Input1 cross join Input2"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:26: Error: ANY should not be used with Cross JOIN\n"); - - res = SqlToYql("use plato; select * from Input1 cross join any Input2"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:44: Error: ANY should not be used with Cross JOIN\n"); - } - - Y_UNIT_TEST(AnyWithCartesianProduct) { - NYql::TAstParseResult res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; select * from any Input1, Input2"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:56: Error: ANY should not be used with Cross JOIN\n"); - - res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; select * from Input1, any Input2"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:64: Error: ANY should not be used with Cross JOIN\n"); - } - - Y_UNIT_TEST(ErrorPlainEndAsInlineActionTerminator) { - NYql::TAstParseResult res = SqlToYql( - "do begin\n" - " select 1\n" - "; end\n"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:0: Error: missing DO at '<EOF>'\n"); - } - - Y_UNIT_TEST(ErrorMultiWayJoinWithUsing) { - NYql::TAstParseResult res = SqlToYql( - "USE plato;\n" - "PRAGMA DisableSimpleColumns;\n" - "SELECT *\n" - "FROM Input1 AS a\n" - "JOIN Input2 AS b USING(key)\n" - "JOIN Input3 AS c ON a.key = c.key;\n" - ); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - "<main>:5:24: Error: Multi-way JOINs should be connected with ON clause instead of USING clause\n" - ); - } - - Y_UNIT_TEST(RequireLabelInFlattenByWithDot) { - NYql::TAstParseResult res = SqlToYql("select * from plato.Input flatten by x.y"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - "<main>:1:40: Error: Unnamed expression after FLATTEN BY is not allowed\n" - ); - } - - Y_UNIT_TEST(WarnUnnamedColumns) { - NYql::TAstParseResult res = SqlToYql( - "PRAGMA WarnUnnamedColumns;\n" - "\n" - "SELECT key, subkey, key || subkey FROM plato.Input ORDER BY subkey;\n"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:28: Warning: Autogenerated column name column2 will be used for expression, code: 4516\n"); - } - - Y_UNIT_TEST(WarnSourceColumnMismatch) { - NYql::TAstParseResult res = SqlToYql( - "insert into plato.Output (key, subkey, new_value, one_more_value) select key as Key, subkey, value, \"x\" from plato.Input;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:51: Warning: Column names in SELECT don't match column specification in parenthesis. \"key\" doesn't match \"Key\". \"new_value\" doesn't match \"value\", code: 4517\n"); - } - - Y_UNIT_TEST(YtCaseInsensitive) { - NYql::TAstParseResult res = SqlToYql("select * from PlatO.foo;"); - UNIT_ASSERT(res.Root); - - res = SqlToYql("use PlatO; select * from foo;"); - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(KikimrCaseSensitive) { - NYql::TAstParseResult res = SqlToYql("select * from PlatO.foo;", 10, "kikimr"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:15: Error: Unknown cluster: PlatO\n"); - - res = SqlToYql("use PlatO; select * from foo;", 10, "kikimr"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:5: Error: Unknown cluster: PlatO\n"); - } - - Y_UNIT_TEST(DiscoveryModeForbidden) { - NYql::TAstParseResult res = SqlToYqlWithMode("insert into plato.Output select * from plato.range(\"\", Input1, Input4)", NSQLTranslation::ESqlMode::DISCOVERY); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: range is not allowed in Discovery mode, code: 4600\n"); - - res = SqlToYqlWithMode("insert into plato.Output select * from plato.like(\"\", \"Input%\")", NSQLTranslation::ESqlMode::DISCOVERY); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: like is not allowed in Discovery mode, code: 4600\n"); - - res = SqlToYqlWithMode("insert into plato.Output select * from plato.regexp(\"\", \"Input.\")", NSQLTranslation::ESqlMode::DISCOVERY); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: regexp is not allowed in Discovery mode, code: 4600\n"); - - res = SqlToYqlWithMode("insert into plato.Output select * from plato.filter(\"\", ($name) -> { return find($name, \"Input\") is not null; })", NSQLTranslation::ESqlMode::DISCOVERY); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: filter is not allowed in Discovery mode, code: 4600\n"); - - res = SqlToYqlWithMode("select Path from plato.folder(\"\") where Type == \"table\"", NSQLTranslation::ESqlMode::DISCOVERY); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:18: Error: folder is not allowed in Discovery mode, code: 4600\n"); - } - - Y_UNIT_TEST(YsonFuncWithoutArgs) { - UNIT_ASSERT(SqlToYql("SELECT Yson::SerializeText(Yson::From());").IsOk()); - } - - Y_UNIT_TEST(CanNotUseOrderByInNonLastSelectInUnionAllChain) { - auto req = "pragma AnsiOrderByLimitInUnionAll;\n" - "use plato;\n" - "\n" - "select * from Input order by key\n" - "union all\n" - "select * from Input order by key limit 1;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:21: Error: ORDER BY within UNION ALL is only allowed after last subquery\n"); - } - - Y_UNIT_TEST(CanNotUseLimitInNonLastSelectInUnionAllChain) { - auto req = "pragma AnsiOrderByLimitInUnionAll;\n" - "use plato;\n" - "\n" - "select * from Input limit 1\n" - "union all\n" - "select * from Input order by key limit 1;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:21: Error: LIMIT within UNION ALL is only allowed after last subquery\n"); - } - - Y_UNIT_TEST(CanNotUseDiscardInNonFirstSelectInUnionAllChain) { - auto req = "pragma AnsiOrderByLimitInUnionAll;\n" - "use plato;\n" - "\n" - "select * from Input\n" - "union all\n" - "discard select * from Input;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:6:1: Error: DISCARD within UNION ALL is only allowed before first subquery\n"); - } - - Y_UNIT_TEST(CanNotUseIntoResultInNonLastSelectInUnionAllChain) { - auto req = "use plato;\n" - "pragma AnsiOrderByLimitInUnionAll;\n" - "\n" - "select * from Input\n" - "union all\n" - "discard select * from Input;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:6:1: Error: DISCARD within UNION ALL is only allowed before first subquery\n"); - } - - Y_UNIT_TEST(YsonStrictInvalidPragma) { - auto res = SqlToYql("pragma yson.Strict = \"wrong\";"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:22: Error: Expected 'true', 'false' or no parameter for: Strict\n"); - } - - Y_UNIT_TEST(WarnTableNameInSomeContexts) { - UNIT_ASSERT(SqlToYql("use plato; select TableName() from Input;").IsOk()); - UNIT_ASSERT(SqlToYql("use plato; select TableName(\"aaaa\");").IsOk()); - UNIT_ASSERT(SqlToYql("select TableName(\"aaaa\", \"yt\");").IsOk()); - - auto res = SqlToYql("select TableName() from plato.Input;"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: TableName requires either service name as second argument or current cluster name\n"); - - res = SqlToYql("use plato;\n" - "select TableName() from Input1 as a join Input2 as b using(key);"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:8: Warning: TableName() may produce empty result when used in ambiguous context (with JOIN), code: 4525\n"); - - res = SqlToYql("use plato;\n" - "select SOME(TableName()), key from Input group by key;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:13: Warning: TableName() will produce empty result when used with aggregation.\n" - "Please consult documentation for possible workaround, code: 4525\n"); - } - - Y_UNIT_TEST(WarnOnDistincWithHavingWithoutAggregations) { - auto res = SqlToYql("select distinct key from plato.Input having key != '0';"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:49: Warning: The usage of HAVING without aggregations with SELECT DISTINCT is non-standard and will stop working soon. Please use WHERE instead., code: 4526\n"); - } - - Y_UNIT_TEST(FlattenByExprWithNestedNull) { - auto res = SqlToYql("USE plato;\n" - "\n" - "SELECT * FROM (SELECT 1 AS region_id)\n" - "FLATTEN BY (\n" - " CAST($unknown(region_id) AS List<String>) AS region\n" - ")"); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:10: Error: Unknown name: $unknown\n"); - } - - Y_UNIT_TEST(EmptySymbolNameIsForbidden) { - auto req = " $`` = 1; select $``;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:5: Error: Empty symbol name is not allowed\n"); - } - - Y_UNIT_TEST(WarnOnBinaryOpWithNullArg) { - auto req = "select * from plato.Input where cast(key as Int32) != NULL"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Warning: Binary operation != will return NULL here, code: 4529\n"); - - req = "select 1 or null"; - res = SqlToYql(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), ""); - } - - Y_UNIT_TEST(ErrorIfTableSampleArgUsesColumns) { - auto req = "SELECT key FROM plato.Input TABLESAMPLE BERNOULLI(MIN_OF(100.0, CAST(subkey as Int32)));"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:70: Error: Column reference \"subkey\" is not allowed in current scope\n"); - } - - Y_UNIT_TEST(DerivedColumnListForSelectIsNotSupportedYet) { - auto req = "SELECT a,b,c FROM plato.Input as t(x,y,z);"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:35: Error: Derived column list is only supported for VALUES\n"); - } - - Y_UNIT_TEST(ErrorIfValuesHasDifferentCountOfColumns) { - auto req = "VALUES (1,2,3), (4,5);"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: All VALUES items should have same size: expecting 3, got 2\n"); - } - - Y_UNIT_TEST(ErrorIfDerivedColumnSizeExceedValuesColumnCount) { - auto req = "SELECT * FROM(VALUES (1,2), (3,4)) as t(x,y,z);"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: Derived column list size exceeds column count in VALUES\n"); - } - - Y_UNIT_TEST(WarnoOnAutogeneratedNamesForValues) { - auto req = "PRAGMA WarnUnnamedColumns;\n" - "SELECT * FROM (VALUES (1,2,3,4), (5,6,7,8)) as t(x,y);"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:16: Warning: Autogenerated column names column2...column3 will be used here, code: 4516\n"); - } - - Y_UNIT_TEST(ErrUnionAllWithOrderByWithoutExplicitLegacyMode) { - auto req = "use plato;\n" - "\n" - "select * from Input order by key\n" - "union all\n" - "select * from Input order by key;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:21: Error: ORDER BY within UNION ALL is only allowed after last subquery\n"); - } - - Y_UNIT_TEST(ErrUnionAllWithLimitWithoutExplicitLegacyMode) { - auto req = "use plato;\n" - "\n" - "select * from Input limit 10\n" - "union all\n" - "select * from Input limit 1;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:21: Error: LIMIT within UNION ALL is only allowed after last subquery\n"); - } - - Y_UNIT_TEST(ErrUnionAllWithIntoResultWithoutExplicitLegacyMode) { - auto req = "use plato;\n" - "\n" - "select * from Input into result aaa\n" - "union all\n" - "select * from Input;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:21: Error: INTO RESULT within UNION ALL is only allowed after last subquery\n"); - } - - Y_UNIT_TEST(ErrUnionAllWithDiscardWithoutExplicitLegacyMode) { - auto req = "use plato;\n" - "\n" - "select * from Input\n" - "union all\n" - "discard select * from Input;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:1: Error: DISCARD within UNION ALL is only allowed before first subquery\n"); - } - - Y_UNIT_TEST(ErrUnionAllKeepsIgnoredOrderByWarning) { - auto req = "use plato;\n" - "\n" - "SELECT * FROM (\n" - " SELECT * FROM Input\n" - " UNION ALL\n" - " SELECT t.* FROM Input AS t ORDER BY t.key\n" - ");"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:3: Warning: ORDER BY without LIMIT in subquery will be ignored, code: 4504\n" - "<main>:6:39: Error: Unknown correlation name: t\n"); - } - - Y_UNIT_TEST(ErrOrderByIgnoredButCheckedForMissingColumns) { - auto req = "$src = SELECT key FROM (SELECT 1 as key, 2 as subkey) ORDER BY x; SELECT * FROM $src;"; - ExpectFailWithError(req, "<main>:1:8: Warning: ORDER BY without LIMIT in subquery will be ignored, code: 4504\n" - "<main>:1:64: Error: Column x is not in source column set\n"); - - req = "$src = SELECT key FROM plato.Input ORDER BY x; SELECT * FROM $src;"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Warning: ORDER BY without LIMIT in subquery will be ignored, code: 4504\n"); - } - - Y_UNIT_TEST(InvalidTtlInterval) { - auto req = R"( - USE plato; - CREATE TABLE tableName (Key Uint32, CreatedAt Timestamp, PRIMARY KEY (Key)) - WITH (TTL = 1 On CreatedAt); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:25: Error: Literal of Interval type is expected for TTL\n" - "<main>:4:25: Error: Invalid TTL settings\n"); - } - - Y_UNIT_TEST(InvalidTtlUnit) { - auto req = R"( - USE plato; - CREATE TABLE tableName (Key Uint32, CreatedAt Uint32, PRIMARY KEY (Key)) - WITH (TTL = Interval("P1D") On CreatedAt AS PICOSECONDS); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "mismatched input 'PICOSECONDS' expecting {MICROSECONDS, MILLISECONDS, NANOSECONDS, SECONDS}"); - } - - Y_UNIT_TEST(InvalidChangefeedSink) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (SINK_TYPE = "S3", MODE = "KEYS_ONLY", FORMAT = "json") - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:55: Error: Unknown changefeed sink type: S3\n"); - } - - Y_UNIT_TEST(InvalidChangefeedSettings) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (SINK_TYPE = "local", FOO = "bar") - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:64: Error: Unknown changefeed setting: FOO\n"); - } - - Y_UNIT_TEST(InvalidChangefeedInitialScan) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", INITIAL_SCAN = "foo") - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:95: Error: Literal of Bool type is expected for INITIAL_SCAN\n"); - } - - Y_UNIT_TEST(InvalidChangefeedVirtualTimestamps) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", VIRTUAL_TIMESTAMPS = "foo") - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:101: Error: Literal of Bool type is expected for VIRTUAL_TIMESTAMPS\n"); - } - - Y_UNIT_TEST(InvalidChangefeedResolvedTimestamps) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", BARRIERS_INTERVAL = "foo") - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:100: Error: Literal of Interval type is expected for BARRIERS_INTERVAL\n"); - } - - Y_UNIT_TEST(InvalidChangefeedRetentionPeriod) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", RETENTION_PERIOD = "foo") - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:99: Error: Literal of Interval type is expected for RETENTION_PERIOD\n"); - } - - Y_UNIT_TEST(InvalidChangefeedTopicPartitions) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", TOPIC_MIN_ACTIVE_PARTITIONS = "foo") - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:110: Error: Literal of integer type is expected for TOPIC_MIN_ACTIVE_PARTITIONS\n"); - } - - Y_UNIT_TEST(InvalidChangefeedAwsRegion) { - auto req = R"( - USE plato; - CREATE TABLE tableName ( - Key Uint32, PRIMARY KEY (Key), - CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", AWS_REGION = true) - ); - )"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:93: Error: Literal of String type is expected for AWS_REGION\n"); - } - - Y_UNIT_TEST(ErrJoinWithGroupingSetsWithoutCorrelationName) { - auto req = "USE plato;\n" - "\n" - "SELECT k1, k2, subkey\n" - "FROM T1 AS a JOIN T2 AS b USING (key)\n" - "GROUP BY GROUPING SETS(\n" - " (a.key as k1, b.subkey as k2),\n" - " (k1),\n" - " (subkey)\n" - ");"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:8:4: Error: Columns in grouping sets should have correlation name, error in key: subkey\n"); - } - - Y_UNIT_TEST(ErrJoinWithGroupByWithoutCorrelationName) { - auto req = "USE plato;\n" - "\n" - "SELECT k1, k2,\n" - " value\n" - "FROM T1 AS a JOIN T2 AS b USING (key)\n" - "GROUP BY a.key as k1, b.subkey as k2,\n" - " value;"; - ExpectFailWithError(req, - "<main>:7:5: Error: Columns in GROUP BY should have correlation name, error in key: value\n"); - } - - Y_UNIT_TEST(ErrWithMissingFrom) { - auto req = "select 1 as key where 1 > 1;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:25: Error: Filtering is not allowed without FROM\n"); - - req = "select 1 + count(*);"; - res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Aggregation is not allowed without FROM\n"); - - req = "select 1 as key, subkey + value;"; - res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:18: Error: Column reference 'subkey'\n" - "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:27: Error: Column reference 'value'\n"); - - req = "select count(1) group by key;"; - res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:26: Error: Column reference 'key'\n"); - } - - Y_UNIT_TEST(ErrWithMissingFromForWindow) { - auto req = "$c = () -> (1 + count(1) over w);\n" - "select $c();"; - ExpectFailWithError(req, - "<main>:1:9: Error: Window and aggregation functions are not allowed in this context\n" - "<main>:1:17: Error: Failed to use aggregation function Count without window specification or in wrong place\n"); - - req = "$c = () -> (1 + lead(1) over w);\n" - "select $c();"; - ExpectFailWithError(req, - "<main>:1:17: Error: Window functions are not allowed in this context\n" - "<main>:1:17: Error: Failed to use window function Lead without window specification or in wrong place\n"); - - req = "select 1 + count(1) over w window w as ();"; - ExpectFailWithError(req, - "<main>:1:1: Error: Window and aggregation functions are not allowed without FROM\n" - "<main>:1:12: Error: Failed to use aggregation function Count without window specification or in wrong place\n"); - - req = "select 1 + lead(1) over w window w as ();"; - ExpectFailWithError(req, - "<main>:1:12: Error: Window functions are not allowed without FROM\n" - "<main>:1:12: Error: Failed to use window function Lead without window specification or in wrong place\n"); - } - - Y_UNIT_TEST(ErrWithMissingFromForInplaceWindow) { - auto req = "$c = () -> (1 + count(1) over ());\n" - "select $c();"; - ExpectFailWithError(req, - "<main>:1:26: Error: Window and aggregation functions are not allowed in this context\n"); - - req = "$c = () -> (1 + lead(1) over (rows between unbounded preceding and current row));\n" - "select $c();"; - ExpectFailWithError(req, - "<main>:1:25: Error: Window and aggregation functions are not allowed in this context\n"); - - req = "select 1 + count(1) over ();"; - ExpectFailWithError(req, - "<main>:1:1: Error: Window and aggregation functions are not allowed without FROM\n" - "<main>:1:12: Error: Failed to use aggregation function Count without window specification or in wrong place\n"); - - req = "select 1 + lead(1) over (rows between current row and unbounded following);"; - ExpectFailWithError(req, - "<main>:1:12: Error: Window functions are not allowed without FROM\n" - "<main>:1:12: Error: Failed to use window function Lead without window specification or in wrong place\n"); - } - - Y_UNIT_TEST(ErrDistinctInWrongPlace) { - auto req = "select Some::Udf(distinct key) from plato.Input;"; - ExpectFailWithError(req, - "<main>:1:18: Error: DISTINCT can only be used in aggregation functions\n"); - req = "select sum(key)(distinct foo) from plato.Input;"; - ExpectFailWithError(req, - "<main>:1:17: Error: DISTINCT can only be used in aggregation functions\n"); - - req = "select len(distinct foo) from plato.Input;"; - ExpectFailWithError(req, - "<main>:1:8: Error: DISTINCT can only be used in aggregation functions\n"); - - req = "$foo = ($x) -> ($x); select $foo(distinct key) from plato.Input;"; - ExpectFailWithError(req, - "<main>:1:34: Error: DISTINCT can only be used in aggregation functions\n"); - } - - Y_UNIT_TEST(ErrForNotSingleChildInInlineAST) { - ExpectFailWithError("select YQL::\"\"", - "<main>:1:8: Error: Failed to parse YQL: expecting AST root node with single child, but got 0\n"); - ExpectFailWithError("select YQL::@@ \t@@", - "<main>:1:8: Error: Failed to parse YQL: expecting AST root node with single child, but got 0\n"); - auto req = "$lambda = YQL::@@(lambda '(x)(+ x x)) (lambda '(y)(+ y y))@@;\n" - "select ListMap([1, 2, 3], $lambda);"; - ExpectFailWithError(req, - "<main>:1:11: Error: Failed to parse YQL: expecting AST root node with single child, but got 2\n"); - } - - Y_UNIT_TEST(ErrEmptyColumnName) { - ExpectFailWithError("select * without \"\" from plato.Input", - "<main>:1:18: Error: String literal can not be used here\n"); - - ExpectFailWithError("select * without `` from plato.Input;", - "<main>:1:18: Error: Empty column name is not allowed\n"); - - ExpectFailWithErrorForAnsiLexer("select * without \"\" from plato.Input", - "<main>:1:18: Error: Empty column name is not allowed\n"); - - ExpectFailWithErrorForAnsiLexer("select * without `` from plato.Input;", - "<main>:1:18: Error: Empty column name is not allowed\n"); - } - - Y_UNIT_TEST(ErrOnNonZeroArgumentsForTableRows) { - ExpectFailWithError("$udf=\"\";process plato.Input using $udf(TableRows(k))", - "<main>:1:40: Error: TableRows requires exactly 0 arguments\n"); - } - - Y_UNIT_TEST(ErrGroupByWithAggregationFunctionAndDistinctExpr) { - ExpectFailWithError("select * from plato.Input group by count(distinct key|key)", - "<main>:1:36: Error: Unable to GROUP BY aggregated values\n"); - } - - // FIXME: check if we can get old behaviour -#if 0 - Y_UNIT_TEST(ErrWithSchemaWithColumnsWithoutType) { - ExpectFailWithError("select * from plato.Input with COLUMNs", - "<main>:1:32: Error: Expected type after COLUMNS\n" - "<main>:1:32: Error: Failed to parse table hints\n"); - - ExpectFailWithError("select * from plato.Input with scheMa", - "<main>:1:32: Error: Expected type after SCHEMA\n" - "<main>:1:32: Error: Failed to parse table hints\n"); - } -#endif - - Y_UNIT_TEST(ErrCollectPreaggregatedInListLiteralWithoutFrom) { - ExpectFailWithError("SELECT([VARIANCE(DISTINCT[])])", - "<main>:1:1: Error: Column references are not allowed without FROM\n" - "<main>:1:9: Error: Column reference '_yql_preagg_Variance0'\n"); - } - - Y_UNIT_TEST(ErrGroupBySmartParenAsTuple) { - ExpectFailWithError("SELECT * FROM plato.Input GROUP BY (k, v,)", - "<main>:1:41: Error: Unexpected trailing comma in grouping elements list\n"); - } - - Y_UNIT_TEST(HandleNestedSmartParensInGroupBy) { - ExpectFailWithError("SELECT * FROM plato.Input GROUP BY (+() as k)", - "<main>:1:37: Error: Unable to GROUP BY constant expression\n"); - } - - Y_UNIT_TEST(ErrRenameWithAddColumn) { - ExpectFailWithError("USE plato; ALTER TABLE table RENAME TO moved, ADD COLUMN addc uint64", - "<main>:1:40: Error: RENAME TO can not be used together with another table action\n"); - } - - Y_UNIT_TEST(ErrAddColumnAndRename) { - // FIXME: fix positions in ALTER TABLE - ExpectFailWithError("USE plato; ALTER TABLE table ADD COLUMN addc uint64, RENAME TO moved", - "<main>:1:46: Error: RENAME TO can not be used together with another table action\n"); - } - - Y_UNIT_TEST(InvalidUuidValue) { - ExpectFailWithError("SELECT Uuid('123e4567ae89ba12d3aa456a426614174ab0')", - "<main>:1:8: Error: Invalid value \"123e4567ae89ba12d3aa456a426614174ab0\" for type Uuid\n"); - ExpectFailWithError("SELECT Uuid('123e4567ae89b-12d3-a456-426614174000')", - "<main>:1:8: Error: Invalid value \"123e4567ae89b-12d3-a456-426614174000\" for type Uuid\n"); - } - - Y_UNIT_TEST(WindowFunctionWithoutOver) { - ExpectFailWithError("SELECT LAST_VALUE(foo) FROM plato.Input", - "<main>:1:8: Error: Can't use window function LastValue without window specification (OVER keyword is missing)\n"); - ExpectFailWithError("SELECT LAST_VALUE(foo) FROM plato.Input GROUP BY key", - "<main>:1:8: Error: Can't use window function LastValue without window specification (OVER keyword is missing)\n"); - } - - Y_UNIT_TEST(CreateAlterUserWithoutCluster) { - ExpectFailWithError("\n CREATE USER user ENCRYPTED PASSWORD 'foobar';", "<main>:2:2: Error: USE statement is missing - no default cluster is selected\n"); - ExpectFailWithError("ALTER USER CURRENT_USER RENAME TO $foo;", "<main>:1:1: Error: USE statement is missing - no default cluster is selected\n"); - } - - Y_UNIT_TEST(ModifyPermissionsWithoutCluster) { - ExpectFailWithError("\n GRANT CONNECT ON `/Root` TO user;", "<main>:2:2: Error: USE statement is missing - no default cluster is selected\n"); - ExpectFailWithError("\n REVOKE MANAGE ON `/Root` FROM user;", "<main>:2:2: Error: USE statement is missing - no default cluster is selected\n"); - } - - Y_UNIT_TEST(ReservedRoleNames) { - ExpectFailWithError("USE plato; CREATE USER current_User;", "<main>:1:24: Error: System role CURRENT_USER can not be used here\n"); - ExpectFailWithError("USE plato; ALTER USER current_User RENAME TO Current_role", "<main>:1:46: Error: System role CURRENT_ROLE can not be used here\n"); - UNIT_ASSERT(SqlToYql("USE plato; DROP GROUP IF EXISTS a, b, c, current_User;").IsOk()); - } - - Y_UNIT_TEST(DisableClassicDivisionWithError) { - ExpectFailWithError("pragma ClassicDivision = 'false'; select $foo / 30;", "<main>:1:42: Error: Unknown name: $foo\n"); - } - - Y_UNIT_TEST(AggregationOfAgrregatedDistinctExpr) { - ExpectFailWithError("select sum(sum(distinct x + 1)) from plato.Input", "<main>:1:12: Error: Aggregation of aggregated values is forbidden\n"); - } - - Y_UNIT_TEST(WarnForUnusedSqlHint) { - NYql::TAstParseResult res = SqlToYql("select * from plato.Input1 as a join /*+ merge() */ plato.Input2 as b using(key);\n" - "select --+ foo(bar)\n" - " 1;"); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:23: Warning: Hint foo will not be used, code: 4534\n"); - } - - Y_UNIT_TEST(WarnForDeprecatedSchema) { - NSQLTranslation::TTranslationSettings settings; - settings.ClusterMapping["s3bucket"] = NYql::S3ProviderName; - NYql::TAstParseResult res = SqlToYqlWithSettings("select * from s3bucket.`foo` with schema (col1 Int32, String as col2, Int64 as col3);", settings); - UNIT_ASSERT(res.Root); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "Warning: Deprecated syntax for positional schema: please use 'column type' instead of 'type AS column', code: 4535\n"); - } - - Y_UNIT_TEST(ErrorOnColumnNameInMaxByLimit) { - ExpectFailWithError( - "SELECT AGGREGATE_BY(AsTuple(value, key), AggregationFactory(\"MAX_BY\", subkey)) FROM plato.Input;", - "<main>:1:42: Error: Source does not allow column references\n" - "<main>:1:71: Error: Column reference 'subkey'\n"); - } - - Y_UNIT_TEST(ErrorInLibraryWithTopLevelNamedSubquery) { - TString withUnusedSubq = "$unused = select max(key) from plato.Input;\n" - "\n" - "define subquery $foo() as\n" - " $count = select count(*) from plato.Input;\n" - " select * from plato.Input limit $count / 2;\n" - "end define;\n" - "export $foo;\n"; - UNIT_ASSERT(SqlToYqlWithMode(withUnusedSubq, NSQLTranslation::ESqlMode::LIBRARY).IsOk()); - - TString withTopLevelSubq = "$count = select count(*) from plato.Input;\n" - "\n" - "define subquery $foo() as\n" - " select * from plato.Input limit $count / 2;\n" - "end define;\n" - "export $foo;\n"; - auto res = SqlToYqlWithMode(withTopLevelSubq, NSQLTranslation::ESqlMode::LIBRARY); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: Named subquery can not be used as a top level statement in libraries\n"); - } - - Y_UNIT_TEST(SessionStartAndSessionStateShouldSurviveSessionWindowArgsError){ - TString query = R"( - $init = ($_row) -> (min(1, 2)); -- error: aggregation func min() can not be used here - $calculate = ($_row, $_state) -> (1); - $update = ($_row, $_state) -> (2); - SELECT - SessionStart() over w as session_start, - SessionState() over w as session_state, - FROM plato.Input as t - WINDOW w AS ( - PARTITION BY user, SessionWindow(ts + 1, $init, $update, $calculate) - ) - )"; - ExpectFailWithError(query, "<main>:2:33: Error: Aggregation function Min requires exactly 1 argument(s), given: 2\n"); - } - - Y_UNIT_TEST(ScalarContextUsage1) { - TString query = R"( - $a = (select 1 as x, 2 as y); - select 1 + $a; - )"; - ExpectFailWithError(query, "<main>:2:39: Error: Source used in expression should contain one concrete column\n" - "<main>:3:24: Error: Source is used here\n"); - } - - Y_UNIT_TEST(ScalarContextUsage2) { - TString query = R"( - use plato; - $a = (select 1 as x, 2 as y); - select * from concat($a); - )"; - ExpectFailWithError(query, "<main>:3:39: Error: Source used in expression should contain one concrete column\n" - "<main>:4:34: Error: Source is used here\n"); - } - - Y_UNIT_TEST(ScalarContextUsage3) { - TString query = R"( - use plato; - $a = (select 1 as x, 2 as y); - select * from range($a); - )"; - ExpectFailWithError(query, "<main>:3:39: Error: Source used in expression should contain one concrete column\n" - "<main>:4:33: Error: Source is used here\n"); - } - - Y_UNIT_TEST(ScalarContextUsage4) { - TString query = R"( - use plato; - $a = (select 1 as x, 2 as y); - insert into $a select 1; - )"; - ExpectFailWithError(query, "<main>:3:39: Error: Source used in expression should contain one concrete column\n" - "<main>:4:25: Error: Source is used here\n"); - } -} - -void CheckUnused(const TString& req, const TString& symbol, unsigned row, unsigned col) { - auto res = SqlToYql(req); - - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), TStringBuilder() << "<main>:" << row << ":" << col << ": Warning: Symbol " << symbol << " is not used, code: 4527\n"); -} - -Y_UNIT_TEST_SUITE(WarnUnused) { - Y_UNIT_TEST(ActionOrSubquery) { - TString req = " $a()\n" - "as select 1;\n" - "end define;\n" - "\n" - "select 1;"; - CheckUnused("define action\n" + req, "$a", 2, 3); - CheckUnused("define subquery\n" + req, "$a", 2, 3); - } - - Y_UNIT_TEST(Import) { - TString req = "import lib1 symbols\n" - " $sqr;\n" - "select 1;"; - CheckUnused(req, "$sqr", 2, 3); - - req = "import lib1 symbols\n" - " $sqr as\n" - " $sq;\n" - "select 1;"; - CheckUnused(req, "$sq", 3, 5); - } - - Y_UNIT_TEST(NamedNodeStatement) { - TString req = " $a, $a = AsTuple(1, 2);\n" - "select $a;"; - CheckUnused(req, "$a", 1, 2); - req = "$a, $b = AsTuple(1, 2);\n" - "select $a;"; - CheckUnused(req, "$b", 1, 6); - CheckUnused(" $a = 1; $a = 2; select $a;", "$a", 1, 2); - } - - Y_UNIT_TEST(Declare) { - CheckUnused("declare $a as String;select 1;", "$a", 1, 9); - } - - Y_UNIT_TEST(ActionParams) { - TString req = "define action $a($x, $y) as\n" - " select $x;\n" - "end define;\n" - "\n" - "do $a(1,2);"; - CheckUnused(req, "$y", 1, 22); - } - - Y_UNIT_TEST(SubqueryParams) { - TString req = "use plato;\n" - "define subquery $q($name, $x) as\n" - " select * from $name;\n" - "end define;\n" - "\n" - "select * from $q(\"Input\", 1);"; - CheckUnused(req, "$x", 2, 27); - } - - Y_UNIT_TEST(For) { - TString req = "define action $a() as\n" - " select 1;\n" - "end define;\n" - "\n" - "for $i in ListFromRange(1, 10)\n" - "do $a();"; - CheckUnused(req, "$i", 5, 5); - } - - Y_UNIT_TEST(LambdaParams) { - TString req = "$lambda = ($x, $y) -> ($x);\n" - "select $lambda(1, 2);"; - CheckUnused(req, "$y", 1, 16); - } - - Y_UNIT_TEST(InsideLambdaBody) { - TString req = "$lambda = () -> {\n" - " $x = 1; return 1;\n" - "};\n" - "select $lambda();"; - CheckUnused(req, "$x", 2, 3); - req = "$lambda = () -> {\n" - " $x = 1; $x = 2; return $x;\n" - "};\n" - "select $lambda();"; - CheckUnused(req, "$x", 2, 3); - } - - Y_UNIT_TEST(InsideAction) { - TString req = "define action $a() as\n" - " $x = 1; select 1;\n" - "end define;\n" - "\n" - "do $a();"; - CheckUnused(req, "$x", 2, 3); - req = "define action $a() as\n" - " $x = 1; $x = 2; select $x;\n" - "end define;\n" - "\n" - "do $a();"; - CheckUnused(req, "$x", 2, 3); - } - - Y_UNIT_TEST(NoWarnOnNestedActions) { - auto req = "pragma warning(\"error\", \"4527\");\n" - "define action $action($b) as\n" - " define action $aaa() as\n" - " select $b;\n" - " end define;\n" - " do $aaa();\n" - "end define;\n" - "\n" - "do $action(1);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(NoWarnForUsageAfterSubquery) { - auto req = "use plato;\n" - "pragma warning(\"error\", \"4527\");\n" - "\n" - "$a = 1;\n" - "\n" - "define subquery $q($table) as\n" - " select * from $table;\n" - "end define;\n" - "\n" - "select * from $q(\"Input\");\n" - "select $a;"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } -} - -Y_UNIT_TEST_SUITE(AnonymousNames) { - Y_UNIT_TEST(ReferenceAnonymousVariableIsForbidden) { - auto req = "$_ = 1; select $_;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:16: Error: Unable to reference anonymous name $_\n"); - - req = "$`_` = 1; select $`_`;"; - res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:18: Error: Unable to reference anonymous name $_\n"); - } - - Y_UNIT_TEST(Declare) { - auto req = "declare $_ as String;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:9: Error: Can not use anonymous name '$_' in DECLARE statement\n"); - } - - Y_UNIT_TEST(ActionSubquery) { - auto req = "define action $_() as select 1; end define;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:15: Error: Can not use anonymous name '$_' as ACTION name\n"); - - req = "define subquery $_() as select 1; end define;"; - res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: Can not use anonymous name '$_' as SUBQUERY name\n"); - } - - Y_UNIT_TEST(Import) { - auto req = "import lib symbols $sqr as $_;"; - auto res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:28: Error: Can not import anonymous name $_\n"); - } - - Y_UNIT_TEST(Export) { - auto req = "export $_;"; - auto res = SqlToYqlWithMode(req, NSQLTranslation::ESqlMode::LIBRARY); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Can not export anonymous name $_\n"); - } - - Y_UNIT_TEST(AnonymousInActionArgs) { - auto req = "pragma warning(\"error\", \"4527\");\n" - "define action $a($_, $y, $_) as\n" - " select $y;\n" - "end define;\n" - "\n" - "do $a(1,2,3);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(AnonymousInSubqueryArgs) { - auto req = "use plato;\n" - "pragma warning(\"error\", \"4527\");\n" - "define subquery $q($_, $y, $_) as\n" - " select * from $y;\n" - "end define;\n" - "\n" - "select * from $q(1,\"Input\",3);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(AnonymousInLambdaArgs) { - auto req = "pragma warning(\"error\", \"4527\");\n" - "$lambda = ($_, $x, $_) -> ($x);\n" - "select $lambda(1,2,3);"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(AnonymousInFor) { - auto req = "pragma warning(\"error\", \"4527\");\n" - "evaluate for $_ in ListFromRange(1, 10) do begin select 1; end do;"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } - - Y_UNIT_TEST(Assignment) { - auto req = "pragma warning(\"error\", \"4527\");\n" - "$_ = 1;\n" - "$_, $x, $_ = AsTuple(1,2,3);\n" - "select $x;"; - UNIT_ASSERT(SqlToYql(req).IsOk()); - } -} - -Y_UNIT_TEST_SUITE(JsonValue) { - Y_UNIT_TEST(JsonValueArgumentCount) { - NYql::TAstParseResult res = SqlToYql("select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json));"); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:49: Error: mismatched input ')' expecting ','\n"); - } - - Y_UNIT_TEST(JsonValueJsonPathMustBeLiteralString) { - NYql::TAstParseResult res = SqlToYql("$jsonPath = \"strict $.key\"; select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), $jsonPath);"); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:79: Error: mismatched input '$' expecting STRING_VALUE\n"); - } - - Y_UNIT_TEST(JsonValueTranslation) { - NYql::TAstParseResult res = SqlToYql("select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\");"); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"strict $.key\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("SafeCast")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("DataType 'Json")); - }; - - TWordCountHive elementStat({"JsonValue"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["JsonValue"]); - } - - Y_UNIT_TEST(JsonValueReturningSection) { - for (const auto& typeName : {"Bool", "Int64", "Double", "String"}) { - NYql::TAstParseResult res = SqlToYql( - TStringBuilder() << "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" RETURNING " << typeName << ");" - ); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"strict $.key\"")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("SafeCast")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("DataType 'Json")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(TStringBuilder() << "DataType '" << typeName)); - }; - - TWordCountHive elementStat({typeName}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat[typeName] > 0); - } - } - - Y_UNIT_TEST(JsonValueInvalidReturningType) { - NYql::TAstParseResult res = SqlToYql("select JSON_VALUE(CAST(@@{'key': 1238}@@ as Json), 'strict $.key' RETURNING invalid);"); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:77: Error: Unknown simple type 'invalid'\n"); - } - - Y_UNIT_TEST(JsonValueAndReturningInExpressions) { - NYql::TAstParseResult res = SqlToYql( - "USE plato\n;" - "$json_value = \"some string\";\n" - "SELECT $json_value;\n" - "SELECT 1 as json_value;\n" - "SELECT $json_value as json_value;\n" - "$returning = \"another string\";\n" - "SELECT $returning;\n" - "SELECT 1 as returning;\n" - "SELECT $returning as returning;\n" - ); - - UNIT_ASSERT(res.Root); - } - - Y_UNIT_TEST(JsonValueValidCaseHandlers) { - const TVector<std::pair<TString, TString>> testCases = { - {"", "'DefaultValue (Null)"}, - {"NULL", "'DefaultValue (Null)"}, - {"ERROR", "'Error (Null)"}, - {"DEFAULT 123", "'DefaultValue (Int32 '\"123\")"}, - }; - - for (const auto& onEmpty : testCases) { - for (const auto& onError : testCases) { - TStringBuilder query; - query << "$json = CAST(@@{\"key\": 1238}@@ as Json);\n" - << "SELECT JSON_VALUE($json, \"strict $.key\""; - if (!onEmpty.first.empty()) { - query << " " << onEmpty.first << " ON EMPTY"; - } - if (!onError.first.empty()) { - query << " " << onError.first << " ON ERROR"; - } - query << ");\n"; - - NYql::TAstParseResult res = SqlToYql(query); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(onEmpty.second + " " + onError.second)); - }; - - TWordCountHive elementStat({"JsonValue"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonValue"] > 0); - } - } - } - - Y_UNIT_TEST(JsonValueTooManyCaseHandlers) { - NYql::TAstParseResult res = SqlToYql( - "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" NULL ON EMPTY NULL ON ERROR NULL ON EMPTY);\n" - ); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF( - Err2Str(res), - "<main>:1:52: Error: Only 1 ON EMPTY and/or 1 ON ERROR clause is expected\n" - ); - } - - Y_UNIT_TEST(JsonValueTooManyOnEmpty) { - NYql::TAstParseResult res = SqlToYql( - "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" NULL ON EMPTY NULL ON EMPTY);\n" - ); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF( - Err2Str(res), - "<main>:1:52: Error: Only 1 ON EMPTY clause is expected\n" - ); - } - - Y_UNIT_TEST(JsonValueTooManyOnError) { - NYql::TAstParseResult res = SqlToYql( - "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" NULL ON ERROR NULL ON ERROR);\n" - ); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF( - Err2Str(res), - "<main>:1:52: Error: Only 1 ON ERROR clause is expected\n" - ); - } - - Y_UNIT_TEST(JsonValueOnEmptyAfterOnError) { - NYql::TAstParseResult res = SqlToYql( - "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" NULL ON ERROR NULL ON EMPTY);\n" - ); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF( - Err2Str(res), - "<main>:1:52: Error: ON EMPTY clause must be before ON ERROR clause\n" - ); - } - - Y_UNIT_TEST(JsonValueNullInput) { - NYql::TAstParseResult res = SqlToYql(R"(SELECT JSON_VALUE(NULL, "strict $.key");)"); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Nothing (OptionalType (DataType 'Json)))")); - }; - - TWordCountHive elementStat({"JsonValue"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonValue"] > 0); - } -} - -Y_UNIT_TEST_SUITE(JsonExists) { - Y_UNIT_TEST(JsonExistsValidHandlers) { - const TVector<std::pair<TString, TString>> testCases = { - {"", "(Just (Bool '\"false\"))"}, - {"TRUE ON ERROR", "(Just (Bool '\"true\"))"}, - {"FALSE ON ERROR", "(Just (Bool '\"false\"))"}, - {"UNKNOWN ON ERROR", "(Nothing (OptionalType (DataType 'Bool)))"}, - // NOTE: in this case we expect arguments of JsonExists callable to end immediately - // after variables. This parenthesis at the end of the expression is left on purpose - {"ERROR ON ERROR", "(Utf8 '\"strict $.key\") (JsonVariables))"}, - }; - - for (const auto& item : testCases) { - NYql::TAstParseResult res = SqlToYql( - TStringBuilder() << R"( - $json = CAST(@@{"key": 1238}@@ as Json); - SELECT JSON_EXISTS($json, "strict $.key" )" << item.first << ");\n" - ); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(item.second)); - }; - - TWordCountHive elementStat({"JsonExists"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonExists"] > 0); - } - } - - Y_UNIT_TEST(JsonExistsInvalidHandler) { - NYql::TAstParseResult res = SqlToYql(R"( - $json = CAST(@@{"key": 1238}@@ as Json); - $default = false; - SELECT JSON_EXISTS($json, "strict $.key" $default ON ERROR); - )"); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:53: Error: mismatched input '$' expecting {')', ERROR, FALSE, TRUE, UNKNOWN}\n"); - } - - Y_UNIT_TEST(JsonExistsNullInput) { - NYql::TAstParseResult res = SqlToYql(R"(SELECT JSON_EXISTS(NULL, "strict $.key");)"); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Nothing (OptionalType (DataType 'Json)))")); - }; - - TWordCountHive elementStat({"JsonExists"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonExists"] > 0); - } -} - -Y_UNIT_TEST_SUITE(JsonQuery) { - Y_UNIT_TEST(JsonQueryValidHandlers) { - using TTestSuite = const TVector<std::pair<TString, TString>>; - TTestSuite wrapCases = { - {"", "'NoWrap"}, - {"WITHOUT WRAPPER", "'NoWrap"}, - {"WITHOUT ARRAY WRAPPER", "'NoWrap"}, - {"WITH WRAPPER", "'Wrap"}, - {"WITH ARRAY WRAPPER", "'Wrap"}, - {"WITH UNCONDITIONAL WRAPPER", "'Wrap"}, - {"WITH UNCONDITIONAL ARRAY WRAPPER", "'Wrap"}, - {"WITH CONDITIONAL WRAPPER", "'ConditionalWrap"}, - {"WITH CONDITIONAL ARRAY WRAPPER", "'ConditionalWrap"}, - }; - TTestSuite handlerCases = { - {"", "'Null"}, - {"ERROR", "'Error"}, - {"NULL", "'Null"}, - {"EMPTY ARRAY", "'EmptyArray"}, - {"EMPTY OBJECT", "'EmptyObject"}, - }; - - for (const auto& wrap : wrapCases) { - for (const auto& onError : handlerCases) { - for (const auto& onEmpty : handlerCases) { - TStringBuilder query; - query << R"($json = CAST(@@{"key": [123]}@@ as Json); - SELECT JSON_QUERY($json, "strict $.key" )" << wrap.first; - if (!onEmpty.first.empty()) { - if (wrap.first.StartsWith("WITH ")) { - continue; - } - query << " " << onEmpty.first << " ON EMPTY"; - } - if (!onError.first.empty()) { - query << " " << onError.first << " ON ERROR"; - } - query << ");\n"; - - NYql::TAstParseResult res = SqlToYql(query); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - const TString args = TStringBuilder() << wrap.second << " " << onEmpty.second << " " << onError.second; - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(args)); - }; - - Cout << wrap.first << " " << onEmpty.first << " " << onError.first << Endl; - - TWordCountHive elementStat({"JsonQuery"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonQuery"] > 0); - } - } - } - } - - Y_UNIT_TEST(JsonQueryOnEmptyWithWrapper) { - NYql::TAstParseResult res = SqlToYql(R"( - $json = CAST(@@{"key": 1238}@@ as Json); - SELECT JSON_QUERY($json, "strict $" WITH ARRAY WRAPPER EMPTY ARRAY ON EMPTY); - )"); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:38: Error: ON EMPTY is prohibited because WRAPPER clause is specified\n"); - } - - Y_UNIT_TEST(JsonQueryNullInput) { - NYql::TAstParseResult res = SqlToYql(R"(SELECT JSON_QUERY(NULL, "strict $.key");)"); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Nothing (OptionalType (DataType 'Json)))")); - }; - - TWordCountHive elementStat({"JsonQuery"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonQuery"] > 0); - } -} - -Y_UNIT_TEST_SUITE(JsonPassing) { - Y_UNIT_TEST(SupportedVariableTypes) { - const TVector<TString> functions = {"JSON_EXISTS", "JSON_VALUE", "JSON_QUERY"}; - - for (const auto& function : functions) { - const auto query = Sprintf(R"( - pragma CompactNamedExprs; - $json = CAST(@@{"key": 1238}@@ as Json); - SELECT %s( - $json, - "strict $.key" - PASSING - "string" as var1, - 1.234 as var2, - CAST(1 as Int64) as var3, - true as var4, - $json as var5 - ))", - function.data() - ); - NYql::TAstParseResult res = SqlToYql(query); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var1" (String '"string")))"), "Cannot find `var1`"); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var2" (Double '"1.234")))"), "Cannot find `var2`"); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var3" (SafeCast (Int32 '"1") (DataType 'Int64))))"), "Cannot find `var3`"); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var4" (Bool '"true")))"), "Cannot find `var4`"); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var5" namedexprnode0))"), "Cannot find `var5`"); - }; - - TWordCountHive elementStat({"JsonVariables"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonVariables"] > 0); - } - } - - Y_UNIT_TEST(ValidVariableNames) { - const TVector<TString> functions = {"JSON_EXISTS", "JSON_VALUE", "JSON_QUERY"}; - - for (const auto& function : functions) { - const auto query = Sprintf(R"( - $json = CAST(@@{"key": 1238}@@ as Json); - SELECT %s( - $json, - "strict $.key" - PASSING - "one" as var1, - "two" as "VaR2", - "three" as `var3`, - "four" as VaR4 - ))", - function.data() - ); - NYql::TAstParseResult res = SqlToYql(query); - - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var1" (String '"one")))"), "Cannot find `var1`"); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"VaR2" (String '"two")))"), "Cannot find `VaR2`"); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var3" (String '"three")))"), "Cannot find `var3`"); - UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"VaR4" (String '"four")))"), "Cannot find `VaR4`"); - }; - - TWordCountHive elementStat({"JsonVariables"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["JsonVariables"] > 0); - } - } -} - -Y_UNIT_TEST_SUITE(MigrationToJsonApi) { - Y_UNIT_TEST(WarningOnDeprecatedJsonUdf) { - NYql::TAstParseResult res = SqlToYql(R"( - $json = CAST(@@{"key": 1234}@@ as Json); - SELECT Json::Parse($json); - )"); - - UNIT_ASSERT(res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:26: Warning: Json UDF is deprecated. Please use JSON API instead, code: 4506\n"); - } -} - -Y_UNIT_TEST_SUITE(AnsiIdentsNegative) { - Y_UNIT_TEST(EnableAnsiLexerFromRequestSpecialComments) { - auto req = "\n" - "\t --!ansi_lexer \n" - "-- Some comment\n" - "-- another comment\n" - "pragma SimpleColumns;\n" - "\n" - "select 1, '''' as empty;"; - - auto res = SqlToYql(req); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - } - - Y_UNIT_TEST(AnsiLexerShouldNotBeEnabledHere) { - auto req = "$str = '\n" - "--!ansi_lexer\n" - "--!syntax_v1\n" - "';\n" - "\n" - "select 1, $str, \"\" as empty;"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - } - - Y_UNIT_TEST(DoubleQuotesInDictsTuplesOrLists) { - auto req = "$d = { 'a': 1, \"b\": 2, 'c': 3,};"; - - auto res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:16: Error: Column reference \"b\" is not allowed in current scope\n"); - - req = "$t = (1, 2, \"a\");"; - - res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:13: Error: Column reference \"a\" is not allowed in current scope\n"); - - req = "$l = ['a', 'b', \"c\"];"; - - res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: Column reference \"c\" is not allowed in current scope\n"); - } - - Y_UNIT_TEST(MultilineComments) { - auto req = "/*/**/ select 1;"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - - req = "/*\n" - "--/*\n" - "*/ select 1;"; - res = SqlToYql(req); - UNIT_ASSERT(res.Root); - res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - - req = "/*\n" - "/*\n" - "--*/\n" - "*/ select 1;"; - res = SqlToYql(req); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:0: Error: mismatched input '*' expecting {';', '(', '$', ALTER, ANALYZE, BACKUP, BATCH, COMMIT, CREATE, DECLARE, DEFINE, DELETE, DISCARD, DO, DROP, EVALUATE, EXPLAIN, EXPORT, FOR, FROM, GRANT, IF, IMPORT, INSERT, PARALLEL, PRAGMA, PROCESS, REDUCE, REPLACE, RESTORE, REVOKE, ROLLBACK, SELECT, SHOW, UPDATE, UPSERT, USE, VALUES}\n"); - res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(res.Root); - } -} - -Y_UNIT_TEST_SUITE(AnsiOptionalAs) { - Y_UNIT_TEST(OptionalAsInProjection) { - UNIT_ASSERT(SqlToYql("PRAGMA AnsiOptionalAs; SELECT a b, c FROM plato.Input;").IsOk()); - ExpectFailWithError("PRAGMA DisableAnsiOptionalAs;\n" - "SELECT a b, c FROM plato.Input;", - "<main>:2:10: Error: Expecting mandatory AS here. Did you miss comma? Please add PRAGMA AnsiOptionalAs; for ANSI compatibility\n"); - } - - Y_UNIT_TEST(OptionalAsWithKeywords) { - UNIT_ASSERT(SqlToYql("PRAGMA AnsiOptionalAs; SELECT a type, b data, c source FROM plato.Input;").IsOk()); - } -} - -Y_UNIT_TEST_SUITE(SessionWindowNegative) { - Y_UNIT_TEST(SessionWindowWithoutSource) { - ExpectFailWithError("SELECT 1 + SessionWindow(ts, 32);", - "<main>:1:12: Error: SessionWindow requires data source\n"); - } - - Y_UNIT_TEST(SessionWindowInProjection) { - ExpectFailWithError("SELECT 1 + SessionWindow(ts, 32) from plato.Input;", - "<main>:1:12: Error: SessionWindow can only be used as a top-level GROUP BY / PARTITION BY expression\n"); - } - - Y_UNIT_TEST(SessionWindowWithNonConstSecondArg) { - ExpectFailWithError( - "SELECT key, session_start FROM plato.Input\n" - "GROUP BY SessionWindow(ts, 32 + subkey) as session_start, key;", - - "<main>:2:10: Error: Source does not allow column references\n" - "<main>:2:33: Error: Column reference 'subkey'\n"); - } - - Y_UNIT_TEST(SessionWindowWithWrongNumberOfArgs) { - ExpectFailWithError("SELECT * FROM plato.Input GROUP BY SessionWindow()", - "<main>:1:36: Error: SessionWindow requires either two or four arguments\n"); - ExpectFailWithError("SELECT * FROM plato.Input GROUP BY SessionWindow(key, subkey, 100)", - "<main>:1:36: Error: SessionWindow requires either two or four arguments\n"); - } - - Y_UNIT_TEST(DuplicateSessionWindow) { - ExpectFailWithError( - "SELECT\n" - " *\n" - "FROM plato.Input\n" - "GROUP BY\n" - " SessionWindow(ts, 10),\n" - " user,\n" - " SessionWindow(ts, 20)\n" - ";", - - "<main>:7:5: Error: Duplicate session window specification:\n" - "<main>:5:5: Error: Previous session window is declared here\n"); - - ExpectFailWithError( - "SELECT\n" - " MIN(key) over w\n" - "FROM plato.Input\n" - "WINDOW w AS (\n" - " PARTITION BY SessionWindow(ts, 10), user,\n" - " SessionWindow(ts, 20)\n" - ");", - - "<main>:6:5: Error: Duplicate session window specification:\n" - "<main>:5:18: Error: Previous session window is declared here\n"); - } - - Y_UNIT_TEST(SessionStartStateWithoutSource) { - ExpectFailWithError("SELECT 1 + SessionStart();", - "<main>:1:12: Error: SessionStart requires data source\n"); - ExpectFailWithError("SELECT 1 + SessionState();", - "<main>:1:12: Error: SessionState requires data source\n"); - } - - Y_UNIT_TEST(SessionStartStateWithoutGroupByOrWindow) { - ExpectFailWithError("SELECT 1 + SessionStart() from plato.Input;", - "<main>:1:12: Error: SessionStart can not be used without aggregation by SessionWindow\n"); - ExpectFailWithError("SELECT 1 + SessionState() from plato.Input;", - "<main>:1:12: Error: SessionState can not be used without aggregation by SessionWindow\n"); - } - - Y_UNIT_TEST(SessionStartStateWithGroupByWithoutSession) { - ExpectFailWithError("SELECT 1 + SessionStart() from plato.Input group by user;", - "<main>:1:12: Error: SessionStart can not be used here: SessionWindow specification is missing in GROUP BY\n"); - ExpectFailWithError("SELECT 1 + SessionState() from plato.Input group by user;", - "<main>:1:12: Error: SessionState can not be used here: SessionWindow specification is missing in GROUP BY\n"); - } - - Y_UNIT_TEST(SessionStartStateWithoutOverWithWindowWithoutSession) { - ExpectFailWithError("SELECT 1 + SessionStart(), MIN(key) over w from plato.Input window w as ()", - "<main>:1:12: Error: SessionStart can not be used without aggregation by SessionWindow. Maybe you forgot to add OVER `window_name`?\n"); - ExpectFailWithError("SELECT 1 + SessionState(), MIN(key) over w from plato.Input window w as ()", - "<main>:1:12: Error: SessionState can not be used without aggregation by SessionWindow. Maybe you forgot to add OVER `window_name`?\n"); - } - - Y_UNIT_TEST(SessionStartStateWithWindowWithoutSession) { - ExpectFailWithError("SELECT 1 + SessionStart() over w, MIN(key) over w from plato.Input window w as ()", - "<main>:1:12: Error: SessionStart can not be used with window w: SessionWindow specification is missing in PARTITION BY\n"); - ExpectFailWithError("SELECT 1 + SessionState() over w, MIN(key) over w from plato.Input window w as ()", - "<main>:1:12: Error: SessionState can not be used with window w: SessionWindow specification is missing in PARTITION BY\n"); - } - - Y_UNIT_TEST(SessionStartStateWithSessionedWindow) { - ExpectFailWithError("SELECT 1 + SessionStart(), MIN(key) over w from plato.Input group by key window w as (partition by SessionWindow(ts, 1)) ", - "<main>:1:12: Error: SessionStart can not be used here: SessionWindow specification is missing in GROUP BY. Maybe you forgot to add OVER `window_name`?\n"); - ExpectFailWithError("SELECT 1 + SessionState(), MIN(key) over w from plato.Input group by key window w as (partition by SessionWindow(ts, 1)) ", - "<main>:1:12: Error: SessionState can not be used here: SessionWindow specification is missing in GROUP BY. Maybe you forgot to add OVER `window_name`?\n"); - } - - Y_UNIT_TEST(AggregationBySessionStateIsNotSupportedYet) { - ExpectFailWithError("SELECT SOME(1 + SessionState()), key from plato.Input group by key, SessionWindow(ts, 1);", - "<main>:1:17: Error: SessionState with GROUP BY is not supported yet\n"); - } - - Y_UNIT_TEST(SessionWindowInRtmr) { - NYql::TAstParseResult res = SqlToYql( - "SELECT * FROM plato.Input GROUP BY SessionWindow(ts, 10);", - 10, TString(NYql::RtmrProviderName)); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:54: Error: Streaming group by query must have a hopping window specification.\n"); - - res = SqlToYql(R"( - SELECT key, SUM(value) AS value FROM plato.Input - GROUP BY key, HOP(subkey, "PT10S", "PT30S", "PT20S"), SessionWindow(ts, 10); - )", 10, TString(NYql::RtmrProviderName)); - - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:13: Error: SessionWindow is unsupported for streaming sources\n"); - } -} - -Y_UNIT_TEST_SUITE(LibraSqlSugar) { - auto makeResult = [](TStringBuf settings) { - return SqlToYql( - TStringBuilder() - << settings - << "\n$udf1 = MyLibra::MakeLibraPreprocessor($settings);" - << "\n$udf2 = CustomLibra::MakeLibraPreprocessor($settings);" - << "\nPROCESS plato.Input USING $udf1(TableRow())" - << "\nUNION ALL" - << "\nPROCESS plato.Input USING $udf2(TableRow());" - ); - }; - - Y_UNIT_TEST(EmptySettings) { - auto res = makeResult(R"( - $settings = AsStruct(); - )"); - UNIT_ASSERT(res.IsOk()); - } - - Y_UNIT_TEST(OnlyEntities) { - auto res = makeResult(R"( - $settings = AsStruct( - AsList("A", "B", "C") AS Entities - ); - )"); - UNIT_ASSERT(res.IsOk()); - } - - Y_UNIT_TEST(EntitiesWithStrategy) { - auto res = makeResult(R"( - $settings = AsStruct( - AsList("A", "B", "C") AS Entities, - "blacklist" AS EntitiesStrategy - ); - )"); - UNIT_ASSERT(res.IsOk()); - } - - Y_UNIT_TEST(AllSettings) { - auto res = makeResult(R"( - $settings = AsStruct( - AsList("A", "B", "C") AS Entities, - "whitelist" AS EntitiesStrategy, - "path" AS BlockstatDict, - false AS ParseWithFat, - "map" AS Mode - ); - )"); - UNIT_ASSERT(res.IsOk()); - } - - Y_UNIT_TEST(BadStrategy) { - auto res = makeResult(R"( - $settings = AsStruct("bad" AS EntitiesStrategy); - )"); - UNIT_ASSERT_STRING_CONTAINS( - Err2Str(res), - "Error: MakeLibraPreprocessor got invalid entities strategy: expected 'whitelist' or 'blacklist'" - ); - } - - Y_UNIT_TEST(BadEntities) { - auto res = makeResult(R"( - $settings = AsStruct(AsList("A", 1) AS Entities); - )"); - UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "Error: MakeLibraPreprocessor entity must be string literal"); - } -} - -Y_UNIT_TEST_SUITE(TrailingQuestionsNegative) { - Y_UNIT_TEST(Basic) { - ExpectFailWithError("SELECT 1?;", "<main>:1:9: Error: Unexpected token '?' at the end of expression\n"); - ExpectFailWithError("SELECT 1? + 1;", "<main>:1:10: Error: mismatched input '+' expecting {<EOF>, ';'}\n"); - ExpectFailWithError("SELECT 1 + 1??? < 2", "<main>:1:13: Error: Unexpected token '?' at the end of expression\n"); - ExpectFailWithError("SELECT 1? > 2? > 3?", - "<main>:1:11: Error: Unexpected token '?' at the end of expression\n" - "<main>:1:16: Error: Unexpected token '?' at the end of expression\n" - "<main>:1:21: Error: Unexpected token '?' at the end of expression\n"); - } - - Y_UNIT_TEST(SmartParen) { - ExpectFailWithError("$x = 1; SELECT (Int32?, $x?)", "<main>:1:27: Error: Unexpected token '?' at the end of expression\n"); - ExpectFailWithError("SELECT (Int32, foo?)", "<main>:1:19: Error: Unexpected token '?' at the end of expression\n"); - } - - Y_UNIT_TEST(LambdaOptArgs) { - ExpectFailWithError("$l = ($x, $y?, $z??, $t?) -> ($x);", "<main>:1:18: Error: Expecting at most one '?' token here (for optional lambda parameters), but got 2\n"); - } -} - -Y_UNIT_TEST_SUITE(FlexibleTypes) { - Y_UNIT_TEST(AssumeOrderByType) { - UNIT_ASSERT(SqlToYql("PRAGMA FlexibleTypes; SELECT 1 AS int32 ASSUME ORDER BY int32").IsOk()); - } - - Y_UNIT_TEST(GroupingSets) { - UNIT_ASSERT(SqlToYql("PRAGMA FlexibleTypes; SELECT COUNT(*) AS cnt, text, uuid FROM plato.Input GROUP BY GROUPING SETS((uuid), (uuid, text));").IsOk()); - } - - Y_UNIT_TEST(WeakField) { - UNIT_ASSERT(SqlToYql("PRAGMA FlexibleTypes; SELECT WeakField(text, string) as text FROM plato.Input").IsOk()); - } - - Y_UNIT_TEST(Aggregation1) { - TString q = - "PRAGMA FlexibleTypes;\n" - "$foo = ($x, $const, $type) -> ($x || $const || FormatType($type));\n" - "SELECT $foo(SOME(x), 'aaa', String) FROM plato.Input GROUP BY y;"; - UNIT_ASSERT(SqlToYql(q).IsOk()); - } - - Y_UNIT_TEST(Aggregation2) { - TString q = - "PRAGMA FlexibleTypes;\n" - "SELECT 1 + String + MAX(key) FROM plato.Input;"; - UNIT_ASSERT(SqlToYql(q).IsOk()); - } -} - -Y_UNIT_TEST_SUITE(ExternalDeclares) { - Y_UNIT_TEST(BasicUsage) { - NSQLTranslation::TTranslationSettings settings; - settings.DeclaredNamedExprs["foo"] = "String"; - auto res = SqlToYqlWithSettings("select $foo;", settings); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "declare") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"__((declare "$foo" (DataType 'String)))__")); - } - }; - - TWordCountHive elementStat = {{TString("declare"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["declare"]); - } - - Y_UNIT_TEST(DeclareOverrides) { - NSQLTranslation::TTranslationSettings settings; - settings.DeclaredNamedExprs["foo"] = "String"; - auto res = SqlToYqlWithSettings("declare $foo as Int32; select $foo;", settings); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "declare") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"__((declare "$foo" (DataType 'Int32)))__")); - } - }; - - TWordCountHive elementStat = {{TString("declare"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["declare"]); - } - - Y_UNIT_TEST(UnusedDeclareDoesNotProduceWarning) { - NSQLTranslation::TTranslationSettings settings; - settings.DeclaredNamedExprs["foo"] = "String"; - auto res = SqlToYqlWithSettings("select 1;", settings); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "declare") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"__((declare "$foo" (DataType 'String)))__")); - } - }; - - TWordCountHive elementStat = {{TString("declare"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["declare"]); - } - - Y_UNIT_TEST(DeclaresWithInvalidTypesFails) { - NSQLTranslation::TTranslationSettings settings; - settings.DeclaredNamedExprs["foo"] = "List<BadType>"; - auto res = SqlToYqlWithSettings("select 1;", settings); - UNIT_ASSERT(!res.Root); - UNIT_ASSERT_NO_DIFF(Err2Str(res), - "<main>:0:5: Error: Unknown type: 'BadType'\n" - "<main>: Error: Failed to parse type for externally declared name 'foo'\n"); - } -} - -Y_UNIT_TEST_SUITE(ExternalDataSource) { - Y_UNIT_TEST(CreateExternalDataSourceWithAuthNone) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="NONE" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"NONE") '('"location" '"my-bucket") '('"source_type" '"ObjectStorage"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceWithAuthServiceAccount) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="SERVICE_ACCOUNT", - SERVICE_ACCOUNT_ID="sa", - SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"SERVICE_ACCOUNT") '('"location" '"my-bucket") '('"service_account_id" '"sa") '('"service_account_secret_name" '"sa_secret_name") '('"source_type" '"ObjectStorage"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceWithBasic) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="BASIC", - LOGIN="admin", - PASSWORD_SECRET_NAME="secret_name" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"BASIC") '('"location" '"protocol://host:port/") '('"login" '"admin") '('"password_secret_name" '"secret_name") '('"source_type" '"PostgreSQL"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceWithMdbBasic) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="MDB_BASIC", - SERVICE_ACCOUNT_ID="sa", - SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name", - LOGIN="admin", - PASSWORD_SECRET_NAME="secret_name" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"MDB_BASIC") '('"location" '"protocol://host:port/") '('"login" '"admin") '('"password_secret_name" '"secret_name") '('"service_account_id" '"sa") '('"service_account_secret_name" '"sa_secret_name") '('"source_type" '"PostgreSQL"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceWithAws) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="AWS", - AWS_ACCESS_KEY_ID_SECRET_NAME="secred_id_name", - AWS_SECRET_ACCESS_KEY_SECRET_NAME="secret_key_name", - AWS_REGION="ru-central-1" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"AWS") '('"aws_access_key_id_secret_name" '"secred_id_name") '('"aws_region" '"ru-central-1") '('"aws_secret_access_key_secret_name" '"secret_key_name") '('"location" '"protocol://host:port/") '('"source_type" '"PostgreSQL"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceWithToken) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="YT", - LOCATION="protocol://host:port/", - AUTH_METHOD="TOKEN", - TOKEN_SECRET_NAME="token_name" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"TOKEN") '('"location" '"protocol://host:port/") '('"source_type" '"YT") '('"token_secret_name" '"token_name"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceWithTablePrefix) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - pragma TablePathPrefix='/aba'; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="NONE" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, "/aba/MyDataSource"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceIfNotExists) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE IF NOT EXISTS MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="NONE" - ); - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"NONE") '('"location" '"my-bucket") '('"source_type" '"ObjectStorage"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObjectIfNotExists")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(AlterExternalDataSource) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - ALTER EXTERNAL DATA SOURCE MyDataSource - SET (SOURCE_TYPE = "ObjectStorage", Login = "Admin"), - SET Location "bucket", - RESET (Auth_Method, Service_Account_Id, Service_Account_Secret_Name); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alterObject))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('features '('('"location" '"bucket") '('"login" '"Admin") '('"source_type" '"ObjectStorage"))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('resetFeatures '('"auth_method" '"service_account_id" '"service_account_secret_name")))#"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalDataSourceOrReplace) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - CREATE OR REPLACE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="NONE" - ); - )"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"NONE") '('"location" '"my-bucket") '('"source_type" '"ObjectStorage"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObjectOrReplace")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateOrReplaceForUnsupportedTableTypesShouldFail) { - ExpectFailWithError(R"sql( - USE plato; - CREATE OR REPLACE TABLE t (a int32 not null, primary key(a, a)); - )sql" , "<main>:3:23: Error: OR REPLACE feature is supported only for EXTERNAL DATA SOURCE and EXTERNAL TABLE\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE OR REPLACE TABLE t ( - Key Uint64, - Value1 String, - PRIMARY KEY (Key) - ) - WITH ( - STORE = COLUMN, - AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = 10 - ); - )sql" , "<main>:3:23: Error: OR REPLACE feature is supported only for EXTERNAL DATA SOURCE and EXTERNAL TABLE\n"); - } - - Y_UNIT_TEST(CreateExternalDataSourceWithBadArguments) { - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource; - )sql" , "<main>:3:56: Error: mismatched input ';' expecting WITH\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - LOCATION="my-bucket", - AUTH_METHOD="NONE" - ); - )sql" , "<main>:5:33: Error: SOURCE_TYPE requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket" - ); - )sql" , "<main>:5:30: Error: AUTH_METHOD requires key\n"); - - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="NONE1" - ); - )sql" , "<main>:6:33: Error: Unknown AUTH_METHOD = NONE1\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="SERVICE_ACCOUNT" - ); - )sql" , "<main>:6:33: Error: SERVICE_ACCOUNT_ID requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="SERVICE_ACCOUNT", - SERVICE_ACCOUNT_ID="s1" - ); - )sql" , "<main>:7:40: Error: SERVICE_ACCOUNT_SECRET_NAME requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="ObjectStorage", - LOCATION="my-bucket", - AUTH_METHOD="SERVICE_ACCOUNT", - SERVICE_ACCOUNT_SECRET_NAME="s1" - ); - )sql" , "<main>:7:49: Error: SERVICE_ACCOUNT_ID requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="BASIC", - LOGIN="admin" - ); - )sql" , "<main>:7:27: Error: PASSWORD_SECRET_NAME requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="BASIC", - PASSWORD_SECRET_NAME="secret_name" - ); - )sql" , "<main>:7:42: Error: LOGIN requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="MDB_BASIC", - SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name", - LOGIN="admin", - PASSWORD_SECRET_NAME="secret_name" - ); - )sql" , "<main>:9:42: Error: SERVICE_ACCOUNT_ID requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="MDB_BASIC", - SERVICE_ACCOUNT_ID="sa", - LOGIN="admin", - PASSWORD_SECRET_NAME="secret_name" - ); - )sql" , "<main>:9:42: Error: SERVICE_ACCOUNT_SECRET_NAME requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="MDB_BASIC", - SERVICE_ACCOUNT_ID="sa", - SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name", - PASSWORD_SECRET_NAME="secret_name" - ); - )sql" , "<main>:9:42: Error: LOGIN requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="MDB_BASIC", - SERVICE_ACCOUNT_ID="sa", - SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name", - LOGIN="admin" - ); - )sql" , "<main>:9:27: Error: PASSWORD_SECRET_NAME requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="AWS", - AWS_SECRET_ACCESS_KEY_SECRET_NAME="secret_key_name", - AWS_REGION="ru-central-1" - ); - )sql" , "<main>:8:32: Error: AWS_ACCESS_KEY_ID_SECRET_NAME requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="AWS", - AWS_ACCESS_KEY_ID_SECRET_NAME="secred_id_name", - AWS_REGION="ru-central-1" - ); - )sql" , "<main>:8:32: Error: AWS_SECRET_ACCESS_KEY_SECRET_NAME requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( - SOURCE_TYPE="PostgreSQL", - LOCATION="protocol://host:port/", - AUTH_METHOD="AWS", - AWS_SECRET_ACCESS_KEY_SECRET_NAME="secret_key_name", - AWS_ACCESS_KEY_ID_SECRET_NAME="secred_id_name" - ); - )sql" , "<main>:8:51: Error: AWS_REGION requires key\n"); - } - - Y_UNIT_TEST(DropExternalDataSourceWithTablePrefix) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - DROP EXTERNAL DATA SOURCE MyDataSource; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropExternalDataSource) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - pragma TablePathPrefix='/aba'; - DROP EXTERNAL DATA SOURCE MyDataSource; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, "/aba/MyDataSource"); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropExternalDataSourceIfExists) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - DROP EXTERNAL DATA SOURCE IF EXISTS MyDataSource; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, "MyDataSource"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObjectIfExists")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } -} - -Y_UNIT_TEST_SUITE(ExternalTable) { - Y_UNIT_TEST(CreateExternalTable) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL TABLE mytable ( - a int - ) WITH ( - DATA_SOURCE="/Root/mydatasource", - LOCATION="/folder1/*" - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('data_source_path (String '"/Root/mydatasource")) '('location (String '"/folder1/*")))) '('tableType 'externalTable)))))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tablescheme")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalTableWithTablePrefix) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - pragma TablePathPrefix='/aba'; - CREATE EXTERNAL TABLE mytable ( - a int - ) WITH ( - DATA_SOURCE="mydatasource", - LOCATION="/folder1/*" - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, "/aba/mydatasource"); - UNIT_ASSERT_STRING_CONTAINS(line, "/aba/mytable"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tablescheme")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalTableObjectStorage) { - auto res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL TABLE mytable ( - a int, - year Int - ) WITH ( - DATA_SOURCE="/Root/mydatasource", - LOCATION="/folder1/*", - FORMAT="json_as_string", - `projection.enabled`="true", - `projection.year.type`="integer", - `projection.year.min`="2010", - `projection.year.max`="2022", - `projection.year.interval`="1", - `projection.month.type`="integer", - `projection.month.min`="1", - `projection.month.max`="12", - `projection.month.interval`="1", - `projection.month.digits`="2", - `storage.location.template`="${year}/${month}", - PARTITONED_BY = "[year, month]" - ); - )sql"); - UNIT_ASSERT_C(res.IsOk(), res.Issues.ToString()); - } - - Y_UNIT_TEST(CreateExternalTableIfNotExists) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE EXTERNAL TABLE IF NOT EXISTS mytable ( - a int - ) WITH ( - DATA_SOURCE="/Root/mydatasource", - LOCATION="/folder1/*" - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('data_source_path (String '"/Root/mydatasource")) '('location (String '"/folder1/*")))) '('tableType 'externalTable)))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, "create_if_not_exists"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalTableOrReplace) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - CREATE OR REPLACE EXTERNAL TABLE mytable ( - a int - ) WITH ( - DATA_SOURCE="/Root/mydatasource", - LOCATION="/folder1/*" - ); - )"); - UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('data_source_path (String '"/Root/mydatasource")) '('location (String '"/folder1/*")))) '('tableType 'externalTable)))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, "create_or_replace"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(AlterExternalTableAddColumn) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - ALTER EXTERNAL TABLE mytable - ADD COLUMN my_column int32, - RESET (LOCATION); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('actions '('('addColumns '('('"my_column" (AsOptionalType (DataType 'Int32))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('setTableSettings '('('location)))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('tableType 'externalTable))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alter))#"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(AlterExternalTableDropColumn) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - ALTER EXTERNAL TABLE mytable - DROP COLUMN my_column, - SET (Location = "abc", Other_Prop = "42"), - SET x 'y'; - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('actions '('('dropColumns '('"my_column")#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('setTableSettings '('('location (String '"abc")) '('Other_Prop (String '"42")) '('x (String '"y")))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('tableType 'externalTable))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alter))#"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateExternalTableWithBadArguments) { - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL TABLE mytable; - )sql" , "<main>:3:45: Error: mismatched input ';' expecting '('\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL TABLE mytable ( - a int - ); - )sql" , "<main>:4:23: Error: DATA_SOURCE requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL TABLE mytable ( - a int - ) WITH ( - DATA_SOURCE="/Root/mydatasource" - ); - )sql" , "<main>:6:33: Error: LOCATION requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL TABLE mytable ( - a int - ) WITH ( - LOCATION="/folder1/*" - ); - )sql" , "<main>:6:30: Error: DATA_SOURCE requires key\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE EXTERNAL TABLE mytable ( - a int, - PRIMARY KEY(a) - ) WITH ( - DATA_SOURCE="/Root/mydatasource", - LOCATION="/folder1/*" - ); - )sql" , "<main>:8:30: Error: PRIMARY KEY is not supported for external table\n"); - } - - Y_UNIT_TEST(DropExternalTable) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - DROP EXTERNAL TABLE MyExternalTable; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("tablescheme")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropExternalTableWithTablePrefix) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - pragma TablePathPrefix='/aba'; - DROP EXTERNAL TABLE MyExternalTable; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, "/aba/MyExternalTable"); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'tablescheme")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropExternalTableIfExists) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - DROP EXTERNAL TABLE IF EXISTS MyExternalTable; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("tablescheme")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("drop_if_exists")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } -} - -Y_UNIT_TEST_SUITE(TopicsDDL) { - void TestQuery(const TString& query, bool expectOk = true) { - TStringBuilder finalQuery; - - finalQuery << "use plato;" << Endl << query; - auto res = SqlToYql(finalQuery, 10, "kikimr"); - if (expectOk) { - UNIT_ASSERT_C(res.IsOk(), res.Issues.ToString()); - } else { - UNIT_ASSERT(!res.IsOk()); - } - } - - Y_UNIT_TEST(CreateTopicSimple) { - TestQuery(R"( - CREATE TOPIC topic1; - )"); - TestQuery(R"( - CREATE TOPIC `cluster1.topic1`; - )"); - TestQuery(R"( - CREATE TOPIC topic1 WITH (metering_mode = "str_value", partition_count_limit = 123, retention_period = Interval('PT1H')); - )"); - } - - Y_UNIT_TEST(CreateTopicConsumer) { - TestQuery(R"( - CREATE TOPIC topic1 (CONSUMER cons1); - )"); - TestQuery(R"( - CREATE TOPIC topic1 (CONSUMER cons1, CONSUMER cons2 WITH (important = false)); - )"); - TestQuery(R"( - CREATE TOPIC topic1 (CONSUMER cons1, CONSUMER cons2 WITH (important = false)) WITH (supported_codecs = "1,2,3"); - )"); - } - - Y_UNIT_TEST(AlterTopicSimple) { - TestQuery(R"( - ALTER TOPIC topic1 SET (retention_period = Interval('PT1H')); - )"); - TestQuery(R"( - ALTER TOPIC topic1 SET (retention_storage_mb = 3, partition_count_limit = 50); - )"); - TestQuery(R"( - ALTER TOPIC topic1 RESET (supported_codecs, retention_period); - )"); - TestQuery(R"( - ALTER TOPIC topic1 RESET (partition_write_speed_bytes_per_second), - SET (partition_write_burst_bytes = 11111, min_active_partitions = 1); - )"); - } - Y_UNIT_TEST(AlterTopicConsumer) { - TestQuery(R"( - ALTER TOPIC topic1 ADD CONSUMER consumer1, - ADD CONSUMER consumer2 WITH (important = false, supported_codecs = "RAW"), - ALTER CONSUMER consumer3 SET (important = false, read_from = 1), - ALTER CONSUMER consumer3 RESET (supported_codecs), - DROP CONSUMER consumer4, - SET (partition_count_limit = 11, retention_period = Interval('PT1H')), - RESET(metering_mode) - )"); - } - Y_UNIT_TEST(DropTopic) { - TestQuery(R"( - DROP TOPIC topic1; - )"); - } - - Y_UNIT_TEST(TopicBadRequests) { - TestQuery(R"( - CREATE TOPIC topic1(); - )", false); - TestQuery(R"( - CREATE TOPIC topic1 SET setting1 = value1; - )", false); - TestQuery(R"( - ALTER TOPIC topic1 SET setting1 value1; - )", false); - TestQuery(R"( - ALTER TOPIC topic1 RESET setting1; - )", false); - - TestQuery(R"( - ALTER TOPIC topic1 DROP CONSUMER consumer4 WITH (k1 = v1); - )", false); - - TestQuery(R"( - CREATE TOPIC topic1 WITH (retention_period = 123); - )", false); - TestQuery(R"( - CREATE TOPIC topic1 (CONSUMER cons1, CONSUMER cons1 WITH (important = false)); - )", false); - TestQuery(R"( - CREATE TOPIC topic1 (CONSUMER cons1 WITH (bad_option = false)); - )", false); - TestQuery(R"( - ALTER TOPIC topic1 ADD CONSUMER cons1, ALTER CONSUMER cons1 RESET (important); - )", false); - TestQuery(R"( - ALTER TOPIC topic1 ADD CONSUMER consumer1, - ALTER CONSUMER consumer3 SET (supported_codecs = "RAW", read_from = 1), - ALTER CONSUMER consumer3 RESET (supported_codecs); - )", false); - TestQuery(R"( - ALTER TOPIC topic1 ADD CONSUMER consumer1, - ALTER CONSUMER consumer3 SET (supported_codecs = "RAW", read_from = 1), - ALTER CONSUMER consumer3 SET (read_from = 2); - )", false); - } - - Y_UNIT_TEST(TopicWithPrefix) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - PRAGMA TablePathPrefix = '/database/path/to/tables'; - ALTER TOPIC `my_table/my_feed` ADD CONSUMER `my_consumer`; - )"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("/database/path/to/tables/my_table/my_feed"), 0}, {"topic", 0}}; - VerifyProgram(res, elementStat); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["topic"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["/database/path/to/tables/my_table/my_feed"]); - } -} - -Y_UNIT_TEST_SUITE(BlockEnginePragma) { - Y_UNIT_TEST(Basic) { - const TVector<TString> values = {"auto", "force", "disable"}; - for (const auto& value : values) { - const auto query = TStringBuilder() << "pragma Blockengine='" << value << "'; select 1;"; - NYql::TAstParseResult res = SqlToYql(query); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - Y_UNUSED(word); - UNIT_ASSERT_STRING_CONTAINS(line, TStringBuilder() << R"(Configure! world (DataSource '"config") '"BlockEngine" '")" << value << "\""); - }; - - TWordCountHive elementStat({"BlockEngine"}); - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT(elementStat["BlockEngine"] == ((value == "disable") ? 0 : 1)); - } - } - - Y_UNIT_TEST(UnknownSetting) { - ExpectFailWithError("use plato; pragma BlockEngine='foo';", - "<main>:1:31: Error: Expected `disable|auto|force' argument for: BlockEngine\n"); - } -} - -Y_UNIT_TEST_SUITE(TViewSyntaxTest) { - Y_UNIT_TEST(CreateViewSimple) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - CREATE VIEW TheView WITH (security_invoker = TRUE) AS SELECT 1; - )" - ); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - } - - Y_UNIT_TEST(CreateViewIfNotExists) { - constexpr const char* name = "TheView"; - NYql::TAstParseResult res = SqlToYql(std::format(R"( - USE plato; - CREATE VIEW IF NOT EXISTS {} AS SELECT 1; - )", name - )); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_STRING_CONTAINS(line, name); - UNIT_ASSERT_STRING_CONTAINS(line, "createObjectIfNotExists"); - } - }; - - TWordCountHive elementStat = { {"Write!"} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); - } - - Y_UNIT_TEST(CreateViewFromTable) { - constexpr const char* path = "/PathPrefix/TheView"; - constexpr const char* query = R"( - SELECT * FROM SomeTable - )"; - - NYql::TAstParseResult res = SqlToYql(std::format(R"( - USE plato; - CREATE VIEW `{}` WITH (security_invoker = TRUE) AS {}; - )", - path, - query - ) - ); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_STRING_CONTAINS(line, path); - UNIT_ASSERT_STRING_CONTAINS(line, "createObject"); - } - }; - TWordCountHive elementStat = { {"Write!"} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); - } - - Y_UNIT_TEST(CheckReconstructedQuery) { - constexpr const char* path = "/PathPrefix/TheView"; - constexpr const char* query = R"( - SELECT * FROM FirstTable JOIN SecondTable ON FirstTable.key == SecondTable.key - )"; - - NYql::TAstParseResult res = SqlToYql(std::format(R"( - USE plato; - CREATE VIEW `{}` WITH (security_invoker = TRUE) AS {}; - )", - path, - query - ) - ); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TString reconstructedQuery = ToString(Tokenize(query)); - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - if (word == "query_text") { - UNIT_ASSERT_STRING_CONTAINS(line, reconstructedQuery); - } - }; - TWordCountHive elementStat = { {"Write!"} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); - } - - Y_UNIT_TEST(DropView) { - constexpr const char* path = "/PathPrefix/TheView"; - NYql::TAstParseResult res = SqlToYql(std::format(R"( - USE plato; - DROP VIEW `{}`; - )", - path - ) - ); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_STRING_CONTAINS(line, path); - UNIT_ASSERT_STRING_CONTAINS(line, "dropObject"); - } - }; - TWordCountHive elementStat = { {"Write!"} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); - } - - Y_UNIT_TEST(DropViewIfExists) { - constexpr const char* name = "TheView"; - NYql::TAstParseResult res = SqlToYql(std::format(R"( - USE plato; - DROP VIEW IF EXISTS {}; - )", name - )); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_STRING_CONTAINS(line, name); - UNIT_ASSERT_STRING_CONTAINS(line, "dropObjectIfExists"); - } - }; - - TWordCountHive elementStat = { {"Write!"} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); - } - - Y_UNIT_TEST(CreateViewWithTablePrefix) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - PRAGMA TablePathPrefix='/PathPrefix'; - CREATE VIEW TheView WITH (security_invoker = TRUE) AS SELECT 1; - )" - ); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write!") { - UNIT_ASSERT_STRING_CONTAINS(line, "/PathPrefix/TheView"); - UNIT_ASSERT_STRING_CONTAINS(line, "createObject"); - } - }; - - TWordCountHive elementStat = { {"Write!"} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); - } - - Y_UNIT_TEST(DropViewWithTablePrefix) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - PRAGMA TablePathPrefix='/PathPrefix'; - DROP VIEW TheView; - )" - ); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, "/PathPrefix/TheView"); - UNIT_ASSERT_STRING_CONTAINS(line, "dropObject"); - } - }; - - TWordCountHive elementStat = { {"Write!"} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); - } - - Y_UNIT_TEST(YtAlternativeSchemaSyntax) { - NYql::TAstParseResult res = SqlToYql(R"( - SELECT * FROM plato.Input WITH schema(y Int32, x String not null); - )"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "userschema") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, - line.find(R"__('('('"userschema" (StructType '('"y" (AsOptionalType (DataType 'Int32))) '('"x" (DataType 'String))))))__")); - } - }; - - TWordCountHive elementStat = {{TString("userschema"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["userschema"]); - } - - Y_UNIT_TEST(UseViewAndFullColumnId) { - NYql::TAstParseResult res = SqlToYql("USE plato; SELECT Input.x FROM Input VIEW uitzicht;"); - UNIT_ASSERT(res.Root); - - TWordCountHive elementStat = {{TString("SqlAccess"), 0}, {"SqlProjectItem", 0}, {"Read!", 0}}; - VerifyProgram(res, elementStat); - UNIT_ASSERT_VALUES_EQUAL(0, elementStat["SqlAccess"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Read!"]); - } -} - -Y_UNIT_TEST_SUITE(CompactNamedExprs) { - Y_UNIT_TEST(SourceCallablesInWrongContext) { - TString query = R"( - pragma CompactNamedExprs; - $foo = %s(); - select $foo from plato.Input; - )"; - - THashMap<TString, TString> errs = { - {"TableRow", "<main>:3:20: Error: TableRow requires data source\n"}, - {"JoinTableRow", "<main>:3:20: Error: JoinTableRow requires data source\n"}, - {"TableRecordIndex", "<main>:3:20: Error: Unable to use function: TableRecord without source\n"}, - {"TablePath", "<main>:3:20: Error: Unable to use function: TablePath without source\n"}, - {"SystemMetadata", "<main>:3:20: Error: Unable to use function: SystemMetadata without source\n"}, - }; - - for (TString callable : { "TableRow", "JoinTableRow", "TableRecordIndex", "TablePath", "SystemMetadata"}) { - auto req = Sprintf(query.c_str(), callable.c_str()); - ExpectFailWithError(req, errs[callable]); - } - } - - Y_UNIT_TEST(ValidateUnusedExprs) { - TString query = R"( - pragma warning("disable", "4527"); - pragma CompactNamedExprs; - pragma ValidateUnusedExprs; - - $foo = count(1); - select 1; - )"; - ExpectFailWithError(query, "<main>:6:20: Error: Aggregation is not allowed in this context\n"); - query = R"( - pragma warning("disable", "4527"); - pragma CompactNamedExprs; - pragma ValidateUnusedExprs; - - define subquery $x() as - select count(1, 2); - end define; - select 1; - )"; - ExpectFailWithError(query, "<main>:7:24: Error: Aggregation function Count requires exactly 1 argument(s), given: 2\n"); - } - - Y_UNIT_TEST(DisableValidateUnusedExprs) { - TString query = R"( - pragma warning("disable", "4527"); - pragma CompactNamedExprs; - pragma DisableValidateUnusedExprs; - - $foo = count(1); - select 1; - )"; - SqlToYql(query).IsOk(); - query = R"( - pragma warning("disable", "4527"); - pragma CompactNamedExprs; - pragma DisableValidateUnusedExprs; - - define subquery $x() as - select count(1, 2); - end define; - select 1; - )"; - SqlToYql(query).IsOk(); - } -} - -Y_UNIT_TEST_SUITE(ResourcePool) { - Y_UNIT_TEST(CreateResourcePool) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE RESOURCE POOL MyResourcePool WITH ( - CONCURRENT_QUERY_LIMIT=20, - QUERY_CANCEL_AFTER_SECONDS=86400, - QUEUE_TYPE="FIFO" - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"concurrent_query_limit" (Int32 '"20")) '('"query_cancel_after_seconds" (Int32 '"86400")) '('"queue_type" '"FIFO"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateResourcePoolWithBadArguments) { - ExpectFailWithError(R"sql( - USE plato; - CREATE RESOURCE POOL MyResourcePool; - )sql" , "<main>:3:51: Error: mismatched input ';' expecting WITH\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE RESOURCE POOL MyResourcePool WITH ( - DUPLICATE_SETTING="first_value", - DUPLICATE_SETTING="second_value" - ); - )sql" , "<main>:5:21: Error: DUPLICATE_SETTING duplicate keys\n"); - } - - Y_UNIT_TEST(AlterResourcePool) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - ALTER RESOURCE POOL MyResourcePool - SET (CONCURRENT_QUERY_LIMIT = 30, Weight = 5, QUEUE_TYPE = "UNORDERED"), - RESET (Query_Cancel_After_Seconds, Query_Count_Limit); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alterObject))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('features '('('"concurrent_query_limit" (Int32 '"30")) '('"queue_type" '"UNORDERED") '('"weight" (Int32 '"5")))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('resetFeatures '('"query_cancel_after_seconds" '"query_count_limit")))#"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropResourcePool) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - DROP RESOURCE POOL MyResourcePool; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } -} - -Y_UNIT_TEST_SUITE(BackupCollection) { - Y_UNIT_TEST(CreateBackupCollection) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE BACKUP COLLECTION TestCollection WITH ( - STORAGE="local", - TAG="test" -- for testing purposes, not a real thing - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"storage" (String '"local")))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"tag" (String '"test"))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('entries '()))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'create")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateBackupCollectionWithDatabase) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE BACKUP COLLECTION TestCollection DATABASE WITH ( - STORAGE="local", - TAG="test" -- for testing purposes, not a real thing - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"storage" (String '"local")))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"tag" (String '"test"))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('entries '('('('type 'database)))))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'create")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateBackupCollectionWithTables) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE BACKUP COLLECTION TestCollection ( - TABLE someTable, - TABLE `prefix/anotherTable` - ) WITH ( - STORAGE="local", - TAG="test" -- for testing purposes, not a real thing - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"storage" (String '"local")))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"tag" (String '"test"))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('entries '('('('type 'table) '('path '"someTable")) '('('type 'table) '('path '"prefix/anotherTable")))))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'create")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateBackupCollectionWithBadArguments) { - ExpectFailWithError(R"sql( - USE plato; - CREATE BACKUP COLLECTION TestCollection; - )sql" , "<main>:3:55: Error: mismatched input ';' expecting {'(', DATABASE, WITH}\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE BACKUP COLLECTION TABLE TestCollection; - )sql" , "<main>:3:47: Error: mismatched input 'TestCollection' expecting {'(', DATABASE, WITH}\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE BACKUP COLLECTION DATABASE `test` TestCollection; - )sql" , "<main>:3:50: Error: mismatched input '`test`' expecting {'(', DATABASE, WITH}\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE BACKUP COLLECTION TestCollection WITH ( - DUPLICATE_SETTING="first_value", - DUPLICATE_SETTING="second_value" - ); - )sql" , "<main>:5:21: Error: DUPLICATE_SETTING duplicate keys\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE BACKUP COLLECTION TestCollection WITH ( - INT_SETTING=1 - ); - )sql" , "<main>:4:21: Error: INT_SETTING value should be a string literal\n"); - } - - Y_UNIT_TEST(AlterBackupCollection) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - ALTER BACKUP COLLECTION TestCollection - SET (STORAGE="remote"), -- also just for test - SET (TAG1 = "123"), - RESET (TAG2, TAG3); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alter))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"storage" (String '"remote")))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"tag1" (String '"123"))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('resetSettings '('"tag2" '"tag3")))#"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(AlterBackupCollectionEntries) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - ALTER BACKUP COLLECTION TestCollection - DROP TABLE `test`, - ADD DATABASE; - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alter))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('alterEntries)#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('type 'table) '('path '"test") '('action 'drop)))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('type 'database) '('action 'add)))#"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropBackupCollection) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - DROP BACKUP COLLECTION TestCollection; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("drop")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } -} - -Y_UNIT_TEST_SUITE(ResourcePoolClassifier) { - Y_UNIT_TEST(CreateResourcePoolClassifier) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( - RANK=20, - RESOURCE_POOL='wgUserQueries', - MEMBER_NAME='yandex_query@abc' - ); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"member_name" '"yandex_query@abc") '('"rank" (Int32 '"20")) '('"resource_pool" '"wgUserQueries"))#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(CreateResourcePoolClassifierWithBadArguments) { - ExpectFailWithError(R"sql( - USE plato; - CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier; - )sql" , "<main>:3:72: Error: mismatched input ';' expecting WITH\n"); - - ExpectFailWithError(R"sql( - USE plato; - CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( - DUPLICATE_SETTING="first_value", - DUPLICATE_SETTING="second_value" - ); - )sql" , "<main>:5:21: Error: DUPLICATE_SETTING duplicate keys\n"); - } - - Y_UNIT_TEST(AlterResourcePoolClassifier) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - ALTER RESOURCE POOL CLASSIFIER MyResourcePoolClassifier - SET (RANK = 30, Weight = 5, MEMBER_NAME = "test@user"), - RESET (Resource_Pool); - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alterObject))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('features '('('"member_name" '"test@user") '('"rank" (Int32 '"30")) '('"weight" (Int32 '"5")))))#"); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('('resetFeatures '('"resource_pool")))#"); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(DropResourcePoolClassifier) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - DROP RESOURCE POOL CLASSIFIER MyResourcePoolClassifier; - )sql"); - UNIT_ASSERT(res.Root); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0}}; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(BacktickMatching) { - auto req = "select\n" - " 1 as `Schema has \\`RealCost\\``\n" - " -- foo`bar"; - auto res = SqlToYql(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - - req = "select 1 as `a``b`, 2 as ````, 3 as `\\x60a\\x60`, 4 as ```b```, 5 as `\\`c\\``"; - res = SqlToYql(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - res = SqlToYqlWithAnsiLexer(req); - UNIT_ASSERT(res.Root); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - } -} - -Y_UNIT_TEST_SUITE(Backup) { - Y_UNIT_TEST(Simple) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - BACKUP TestCollection; - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'Incremental")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'backup")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(Incremental) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - BACKUP TestCollection INCREMENTAL; - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'backupIncremental")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } -} - -Y_UNIT_TEST_SUITE(Restore) { - Y_UNIT_TEST(Simple) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - RESTORE TestCollection; - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'restore")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } - - Y_UNIT_TEST(AtPoint) { - NYql::TAstParseResult res = SqlToYql(R"sql( - USE plato; - RESTORE TestCollection AT '2024-06-16_20-14-02'; - )sql"); - UNIT_ASSERT_C(res.Root, res.Issues.ToString()); - - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); - UNIT_ASSERT_STRING_CONTAINS(line, R"#('at '"2024-06-16_20-14-02")#"); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'restore")); - } - }; - - TWordCountHive elementStat = { {TString("Write"), 0} }; - VerifyProgram(res, elementStat, verifyLine); - - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - } -} - -Y_UNIT_TEST_SUITE(ColumnFamily) { - Y_UNIT_TEST(CompressionLevelCorrectUsage) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - CREATE TABLE tableName ( - Key Uint32 FAMILY default, - Value String FAMILY family1, - PRIMARY KEY (Key), - FAMILY default ( - DATA = "test", - COMPRESSION = "lz4", - COMPRESSION_LEVEL = 5 - ), - FAMILY family1 ( - DATA = "test", - COMPRESSION = "lz4", - COMPRESSION_LEVEL = 3 - ) - ); - )"); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { - if (word == "Write") { - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("compression_level")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("5")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("3")); - } - }; - - TWordCountHive elementStat = { { TString("Write"), 0 }, { TString("compression_level"), 0 } }; - VerifyProgram(res, elementStat, verifyLine); - UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); - UNIT_ASSERT_VALUES_EQUAL(2, elementStat["compression_level"]); - } - - Y_UNIT_TEST(FieldDataIsNotString) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - CREATE TABLE tableName ( - Key Uint32 FAMILY default, - PRIMARY KEY (Key), - FAMILY default ( - DATA = 1, - COMPRESSION = "lz4", - COMPRESSION_LEVEL = 5 - ) - ); - )"); - UNIT_ASSERT(!res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 1); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "DATA value should be a string literal"); - } - - Y_UNIT_TEST(FieldCompressionIsNotString) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - CREATE TABLE tableName ( - Key Uint32 FAMILY default, - PRIMARY KEY (Key), - FAMILY default ( - DATA = "test", - COMPRESSION = 2, - COMPRESSION_LEVEL = 5 - ), - ); - )"); - UNIT_ASSERT(!res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 1); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "COMPRESSION value should be a string literal"); - } - - Y_UNIT_TEST(FieldCompressionLevelIsNotInteger) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - CREATE TABLE tableName ( - Key Uint32 FAMILY default, - PRIMARY KEY (Key), - FAMILY default ( - DATA = "test", - COMPRESSION = "lz4", - COMPRESSION_LEVEL = "5" - ) - ); - )"); - UNIT_ASSERT(!res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 1); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "COMPRESSION_LEVEL value should be an integer"); - } - - Y_UNIT_TEST(AlterCompressionCorrectUsage) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION "lz4"; - )"); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - } - - Y_UNIT_TEST(AlterCompressionFieldIsNotString) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION lz4; - )"); - UNIT_ASSERT(!res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 1); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "mismatched input 'lz4' expecting {STRING_VALUE, DIGITS, INTEGER_VALUE}"); - } - - Y_UNIT_TEST(AlterCompressionLevelCorrectUsage) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION_LEVEL 5; - )"); - UNIT_ASSERT(res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 0); - } - - Y_UNIT_TEST(AlterCompressionLevelFieldIsNotInteger) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION_LEVEL "5"; - )"); - UNIT_ASSERT(!res.IsOk()); - UNIT_ASSERT(res.Issues.Size() == 1); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "COMPRESSION_LEVEL value should be an integer"); - } -} +#define ANTLR_VER 4 +#include "sql_ut_common.h" Y_UNIT_TEST_SUITE(QuerySplit) { Y_UNIT_TEST(Simple) { @@ -8180,86 +97,4 @@ Y_UNIT_TEST_SUITE(QuerySplit) { } } -Y_UNIT_TEST_SUITE(Transfer) { - Y_UNIT_TEST(Lambda) { - NYql::TAstParseResult res = SqlToYql(R"( use plato; - -- Русский коммент, empty statement - ; - - -- befor comment - $a = "А"; - - SELECT * FROM Input; - - $b = ($x) -> { return $a || $x; }; - - CREATE TRANSFER `TransferName` - FROM `TopicName` TO `TableName` - USING ($x) -> { - -- internal comment - return $b($x); - } - WITH ( - CONNECTION_STRING = "grpc://localhost:2135/?database=/Root" - ); - )"); - - UNIT_ASSERT_C(res.IsOk(), res.Issues.ToString()); - UNIT_ASSERT_VALUES_EQUAL_C(res.Issues.Size(), 0, res.Issues.ToString()); - - const auto programm = GetPrettyPrint(res); - - Cerr << ">>>>> Root " << programm << Endl; - auto expected = R"('transformLambda 'use plato; --- befor comment - $a = "А"; -$b = ($x) -> { return $a || $x; }; -$__ydb_transfer_lambda = ($x) -> { - -- internal comment - return $b($x); - }; -))"; - - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, programm.find(expected)); - - } -} - -Y_UNIT_TEST_SUITE(MatchRecognizeMeasuresAggregation) { - Y_UNIT_TEST(InsideSelect) { - ExpectFailWithError(R"sql( - SELECT FIRST(0), LAST(1); - )sql", - "<main>:2:20: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - "<main>:2:30: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - ); - } - - Y_UNIT_TEST(OutsideSelect) { - ExpectFailWithError(R"sql( - $lambda = ($x) -> (FIRST($x) + LAST($x)); - SELECT $lambda(x) FROM plato.Input; - )sql", - "<main>:2:32: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - "<main>:2:44: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - ); - } - - Y_UNIT_TEST(AsAggregateFunction) { - ExpectFailWithError(R"sql( - SELECT FIRST(x), LAST(x) FROM plato.Input; - )sql", - "<main>:2:20: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - "<main>:2:30: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - ); - } - Y_UNIT_TEST(AsWindowFunction) { - ExpectFailWithError(R"sql( - SELECT FIRST(x) OVER(), LAST(x) OVER() FROM plato.Input; - )sql", - "<main>:2:20: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - "<main>:2:37: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" - ); - } -} diff --git a/yql/essentials/sql/v1/sql_ut_antlr4.h b/yql/essentials/sql/v1/sql_ut_antlr4.h index 4f4203a789..03d804707f 100644 --- a/yql/essentials/sql/v1/sql_ut_antlr4.h +++ b/yql/essentials/sql/v1/sql_ut_antlr4.h @@ -10,6 +10,7 @@ #include <library/cpp/testing/unittest/registar.h> +#include <library/cpp/regex/pcre/pcre.h> #include <util/string/split.h> #include <deque> #include <unordered_set> @@ -84,6 +85,13 @@ inline void ExpectFailWithError(const TString& query, const TString& error) { UNIT_ASSERT_NO_DIFF(Err2Str(res), error); } +inline void ExpectFailWithFuzzyError(const TString& query, const TString& errorRegex) { + NYql::TAstParseResult res = SqlToYql(query); + + UNIT_ASSERT(!res.Root); + UNIT_ASSERT(NPcre::TPcre<char>(errorRegex.c_str()).Matches(Err2Str(res))); +} + inline NYql::TAstParseResult SqlToYqlWithAnsiLexer(const TString& query, size_t maxErrors = 10, const TString& provider = {}, EDebugOutput debug = EDebugOutput::None) { bool ansiLexer = true; return SqlToYqlWithMode(query, NSQLTranslation::ESqlMode::QUERY, maxErrors, provider, debug, ansiLexer); diff --git a/yql/essentials/sql/v1/sql_ut_common.h b/yql/essentials/sql/v1/sql_ut_common.h new file mode 100644 index 0000000000..407cc31121 --- /dev/null +++ b/yql/essentials/sql/v1/sql_ut_common.h @@ -0,0 +1,8351 @@ +namespace { + TString ToString(const TParsedTokenList& tokens) { + TStringBuilder reconstructedQuery; + for (const auto& token : tokens) { + if (token.Name == "WS" || token.Name == "EOF") { + continue; + } + if (!reconstructedQuery.empty()) { + reconstructedQuery << ' '; + } + reconstructedQuery << token.Content; + } + return reconstructedQuery; + } +} + +Y_UNIT_TEST_SUITE(AnsiMode) { + Y_UNIT_TEST(PragmaAnsi) { + UNIT_ASSERT(SqlToYql("PRAGMA ANSI 2016;").IsOk()); + } +} + +Y_UNIT_TEST_SUITE(SqlParsingOnly) { + ///This function is used in BACKWARD COMPATIBILITY tests below that LIMIT the sets of token that CAN NOT be used + ///as identifiers in different contexts in a SQL request + ///\return list of tokens that failed this check + TVector<TString> ValidateTokens(const THashSet<TString>& forbidden, const std::function<TString (const TString& )>& makeRequest) { + THashMap<TString, bool> allTokens; + for (const auto& t: NSQLFormat::GetKeywords()) { + allTokens[t] = !forbidden.contains((t)); + } + for (const auto& f: forbidden) { + UNIT_ASSERT(allTokens.contains(f)); //check that forbidden list contains tokens only(argument check) + } + TVector<TString> failed; + for (const auto& [token, allowed]: allTokens) { + if (SqlToYql(makeRequest(token)).IsOk() != allowed) + failed.push_back(token); + } + return failed; + } + + Y_UNIT_TEST(TokensAsColumnName) { //id_expr + auto failed = ValidateTokens({ + "ALL", "ANY", "AS", "ASSUME", "ASYMMETRIC", "AUTOMAP", "BETWEEN", "BITCAST", + "CALLABLE", "CASE", "CAST", "CUBE", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", + "DICT", "DISTINCT", "ENUM", "ERASE", "EXCEPT", "EXISTS", "FLOW", "FROM", "FULL", + "HAVING", "HOP", "INTERSECT", "JSON_EXISTS", "JSON_QUERY", "JSON_VALUE", "LIMIT", "LIST", + "NOT", "OPTIONAL", "PROCESS", "REDUCE", "REPEATABLE", "RESOURCE", "RETURN", "RETURNING", "ROLLUP", + "SELECT", "SET", "STREAM", "STRUCT", "SYMMETRIC", "TAGGED", "TUPLE", "UNBOUNDED", + "UNION", "VARIANT", "WHEN", "WHERE", "WINDOW", "WITHOUT" + }, + [](const TString& token){ + TStringBuilder req; + req << "SELECT " << token << " FROM Plato.Input"; + return req; + } + ); + UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); + } + + Y_UNIT_TEST(TokensAsWithoutColumnName) { //id_without + auto failed = ValidateTokens({ + "ALL", "AS", "ASSUME", "ASYMMETRIC", "AUTOMAP", "BETWEEN", "BITCAST", + "CALLABLE", "CASE", "CAST", "CUBE", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", + "DICT", "DISTINCT", "EMPTY_ACTION", "ENUM", "EXCEPT", "EXISTS", "FALSE", "FLOW", "FROM", "FULL", + "HAVING", "HOP", "INTERSECT", "JSON_EXISTS", "JSON_QUERY", "JSON_VALUE", "LIMIT", "LIST", + "NOT", "NULL", "OPTIONAL", "PROCESS", "REDUCE", "REPEATABLE", "RESOURCE", "RETURN", "RETURNING", "ROLLUP", + "SELECT", "SET", "STRUCT", "SYMMETRIC", "TAGGED", "TRUE", "TUPLE", "UNBOUNDED", + "UNION", "VARIANT", "WHEN", "WHERE", "WINDOW", "WITHOUT" + }, + [](const TString& token){ + TStringBuilder req; + req << "SELECT * WITHOUT " << token << " FROM Plato.Input"; + return req; + } + ); + UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); + } + + Y_UNIT_TEST(TokensAsColumnNameInAddColumn) { //id_schema + auto failed = ValidateTokens({ + "ANY", "AUTOMAP", "CALLABLE", "COLUMN", "DICT", "ENUM", "ERASE", "FALSE", "FLOW", + "LIST", "OPTIONAL", "REPEATABLE", "RESOURCE", + "SET", "STREAM", "STRUCT", "TAGGED", "TRUE", "TUPLE", "VARIANT" + }, + [](const TString& token){ + TStringBuilder req; + req << "ALTER TABLE Plato.Input ADD COLUMN " << token << " Bool"; + return req; + } + ); + UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); + } + + Y_UNIT_TEST(TokensAsColumnAlias) { + auto failed = ValidateTokens({ + "AUTOMAP", "FALSE", + "REPEATABLE", "TRUE" + }, + [](const TString& token){ + TStringBuilder req; + req << "SELECT Col as " << token << " FROM Plato.Input"; + return req; + } + ); + UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); + } + + Y_UNIT_TEST(TokensAsTableName) { //id_table_or_type + auto failed = ValidateTokens({ + "ANY", "AUTOMAP", "COLUMN", "ERASE", "FALSE", + "REPEATABLE", "STREAM", "TRUE" + }, + [](const TString& token){ + TStringBuilder req; + req << "SELECT * FROM Plato." << token; + return req; + } + ); + UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); + } + + Y_UNIT_TEST(TokensAsTableAlias) { //id_table + auto failed = ValidateTokens({ + "AUTOMAP", "CALLABLE", "DICT", "ENUM","FALSE", "FLOW", + "LIST", "OPTIONAL", "REPEATABLE", "RESOURCE", + "SET", "STRUCT", "TAGGED", "TRUE", "TUPLE", "VARIANT" + }, + [](const TString& token){ + TStringBuilder req; + req << "SELECT * FROM Plato.Input AS " << token; + return req; + } + ); + UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); + } + + Y_UNIT_TEST(TokensAsHints) { //id_hint + auto failed = ValidateTokens({ + "AUTOMAP", "CALLABLE", "COLUMNS", "DICT", "ENUM", "FALSE", "FLOW", + "LIST", "OPTIONAL", "REPEATABLE", "RESOURCE", + "SCHEMA", "SET", "STRUCT", "TAGGED", "TRUE", "TUPLE", "VARIANT" + }, + [](const TString& token){ + TStringBuilder req; + req << "SELECT * FROM Plato.Input WITH " << token; + return req; + } + ); + UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); + } + + Y_UNIT_TEST(TokensAsWindow) { //id_window + auto failed = ValidateTokens({ + "AUTOMAP", "CALLABLE", "DICT", "ENUM", "FALSE", "FLOW", "GROUPS", "LIST", "OPTIONAL", + "RANGE", "REPEATABLE", "RESOURCE", "ROWS", "SET", "STRUCT", "TAGGED" ,"TRUE", "TUPLE", "VARIANT" + }, + [](const TString& token){ + TStringBuilder req; + req << "SELECT * FROM Plato.Input WINDOW " << token << " AS ()"; + return req; + } + ); + UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); + } + + Y_UNIT_TEST(TokensAsIdExprIn) { //id_expr_in + auto failed = ValidateTokens({ + "ALL", "ANY", "AS", "ASSUME", "ASYMMETRIC", "AUTOMAP", "BETWEEN", "BITCAST", + "CALLABLE", "CASE", "CAST", "COMPACT", "CUBE", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", + "DICT", "DISTINCT", "ENUM", "ERASE", "EXCEPT", "EXISTS", "FLOW", "FROM", "FULL", + "HAVING", "HOP", "INTERSECT", "JSON_EXISTS", "JSON_QUERY", "JSON_VALUE", "LIMIT", "LIST", + "NOT", "OPTIONAL", "PROCESS", "REDUCE", "REPEATABLE", "RESOURCE", "RETURN", "RETURNING", "ROLLUP", + "SELECT", "SET", "STREAM", "STRUCT", "SYMMETRIC", "TAGGED", "TUPLE", "UNBOUNDED", + "UNION", "VARIANT", "WHEN", "WHERE", "WINDOW", "WITHOUT" + }, + [](const TString& token){ + TStringBuilder req; + req << "SELECT * FROM Plato.Input WHERE q IN " << token; + return req; + } + ); + UNIT_ASSERT_VALUES_EQUAL(failed, TVector<TString>{}); + } + + Y_UNIT_TEST(TableHints) { + UNIT_ASSERT(SqlToYql("SELECT * FROM plato.Input WITH INFER_SCHEMA").IsOk()); + UNIT_ASSERT(SqlToYql("SELECT * FROM plato.Input WITH (INFER_SCHEMA)").IsOk()); + } + + Y_UNIT_TEST(InNoHints) { + TString query = "SELECT * FROM plato.Input WHERE key IN (1,2,3)"; + + VerifySqlInHints(query, { "'('('warnNoAnsi))" }, {}); + VerifySqlInHints(query, { "'()" }, false); + VerifySqlInHints(query, { "'('('ansi))" }, true); + } + + Y_UNIT_TEST(InHintCompact) { + // should parse COMPACT as hint + TString query = "SELECT * FROM plato.Input WHERE key IN COMPACT(1, 2, 3)"; + + VerifySqlInHints(query, { "'('isCompact)" }); + } + + Y_UNIT_TEST(InHintSubquery) { + // should parse tableSource as hint + TString query = "$subq = (SELECT key FROM plato.Input); SELECT * FROM plato.Input WHERE key IN $subq"; + + VerifySqlInHints(query, { "'('tableSource)" }); + } + + Y_UNIT_TEST(InHintCompactSubquery) { + TString query = "$subq = (SELECT key FROM plato.Input); SELECT * FROM plato.Input WHERE key IN COMPACT $subq"; + + VerifySqlInHints(query, { "'('isCompact)", "'('tableSource)" }); + } + + Y_UNIT_TEST(CompactKeywordNotReservedForNames) { + UNIT_ASSERT(SqlToYql("SELECT COMPACT FROM plato.Input WHERE COMPACT IN COMPACT(1, 2, 3)").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT * FROM COMPACT").IsOk()); + } + + Y_UNIT_TEST(FamilyKeywordNotReservedForNames) { + // FIXME: check if we can get old behaviour + //UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE FAMILY (FAMILY Uint32, PRIMARY KEY (FAMILY));").IsOk()); + //UNIT_ASSERT(SqlToYql("USE plato; SELECT FAMILY FROM FAMILY").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT FAMILY FROM Input").IsOk()); + } + + Y_UNIT_TEST(ResetKeywordNotReservedForNames) { + UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE RESET (RESET Uint32, PRIMARY KEY (RESET));").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT RESET FROM RESET").IsOk()); + } + + Y_UNIT_TEST(SyncKeywordNotReservedForNames) { + UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE SYNC (SYNC Uint32, PRIMARY KEY (SYNC));").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT SYNC FROM SYNC").IsOk()); + } + + Y_UNIT_TEST(AsyncKeywordNotReservedForNames) { + UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE ASYNC (ASYNC Uint32, PRIMARY KEY (ASYNC));").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT ASYNC FROM ASYNC").IsOk()); + } + + Y_UNIT_TEST(DisableKeywordNotReservedForNames) { + UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE DISABLE (DISABLE Uint32, PRIMARY KEY (DISABLE));").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT DISABLE FROM DISABLE").IsOk()); + } + + Y_UNIT_TEST(ChangefeedKeywordNotReservedForNames) { + UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE CHANGEFEED (CHANGEFEED Uint32, PRIMARY KEY (CHANGEFEED));").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT CHANGEFEED FROM CHANGEFEED").IsOk()); + } + + Y_UNIT_TEST(ReplicationKeywordNotReservedForNames) { + UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE REPLICATION (REPLICATION Uint32, PRIMARY KEY (REPLICATION));").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT REPLICATION FROM REPLICATION").IsOk()); + } + + Y_UNIT_TEST(TransferKeywordNotReservedForNames) { + UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE TRANSFER (TRANSFER Uint32, PRIMARY KEY (TRANSFER));").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT TRANSFER FROM TRANSFER").IsOk()); + } + + Y_UNIT_TEST(SecondsKeywordNotReservedForNames) { + UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE SECONDS (SECONDS Uint32, PRIMARY KEY (SECONDS));").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT SECONDS FROM SECONDS").IsOk()); + } + + Y_UNIT_TEST(MillisecondsKeywordNotReservedForNames) { + UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE MILLISECONDS (MILLISECONDS Uint32, PRIMARY KEY (MILLISECONDS));").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT MILLISECONDS FROM MILLISECONDS").IsOk()); + } + + Y_UNIT_TEST(MicrosecondsKeywordNotReservedForNames) { + UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE MICROSECONDS (MICROSECONDS Uint32, PRIMARY KEY (MICROSECONDS));").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT MICROSECONDS FROM MICROSECONDS").IsOk()); + } + + Y_UNIT_TEST(NanosecondsKeywordNotReservedForNames) { + UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE NANOSECONDS (NANOSECONDS Uint32, PRIMARY KEY (NANOSECONDS));").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT NANOSECONDS FROM NANOSECONDS").IsOk()); + } + + Y_UNIT_TEST(Jubilee) { + NYql::TAstParseResult res = SqlToYql("USE plato; INSERT INTO Arcadia (r2000000) VALUES (\"2M GET!!!\");"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(QualifiedAsteriskBefore) { + NYql::TAstParseResult res = SqlToYql( + "PRAGMA DisableSimpleColumns;" + "select interested_table.*, LENGTH(value) AS megahelpful_len from plato.Input as interested_table;" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + static bool seenStar = false; + if (word == "FlattenMembers") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("interested_table.")); + } else if (word == "SqlProjectItem") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("megahelpful_len"))); + UNIT_ASSERT_VALUES_EQUAL(seenStar, true); + } else if (word == "SqlProjectStarItem") { + seenStar = true; + } + }; + TWordCountHive elementStat = {{TString("FlattenMembers"), 0}, {TString("SqlProjectItem"), 0}, {TString("SqlProjectStarItem"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["FlattenMembers"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectStarItem"]); + } + + Y_UNIT_TEST(QualifiedAsteriskAfter) { + NYql::TAstParseResult res = SqlToYql( + "PRAGMA DisableSimpleColumns;" + "select LENGTH(value) AS megahelpful_len, interested_table.* from plato.Input as interested_table;" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + static bool seenStar = false; + if (word == "FlattenMembers") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("interested_table.")); + } else if (word == "SqlProjectItem") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("megahelpful_len"))); + UNIT_ASSERT_VALUES_EQUAL(seenStar, false); + } else if (word == "SqlProjectStarItem") { + seenStar = true; + } + }; + TWordCountHive elementStat = {{TString("FlattenMembers"), 0}, {TString("SqlProjectItem"), 0}, {TString("SqlProjectStarItem"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["FlattenMembers"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectStarItem"]); + } + + Y_UNIT_TEST(QualifiedMembers) { + NYql::TAstParseResult res = SqlToYql("select interested_table.key, interested_table.value from plato.Input as interested_table;"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + const bool fieldKey = TString::npos != line.find(Quote("key")); + const bool fieldValue = TString::npos != line.find(Quote("value")); + const bool refOnTable = TString::npos != line.find("interested_table."); + if (word == "SqlProjectItem") { + UNIT_ASSERT(fieldKey || fieldValue); + UNIT_ASSERT(!refOnTable); + } else if (word == "Write!") { + UNIT_ASSERT(fieldKey && fieldValue && !refOnTable); + } + }; + TWordCountHive elementStat = {{TString("SqlProjectStarItem"), 0}, {TString("SqlProjectItem"), 0}, {TString("Write!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(0, elementStat["SqlProjectStarItem"]); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlProjectItem"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + } + + Y_UNIT_TEST(ExplainQueryPlan) { + UNIT_ASSERT(SqlToYql("EXPLAIN SELECT 1;").IsOk()); + UNIT_ASSERT(SqlToYql("EXPLAIN QUERY PLAN SELECT 1;").IsOk()); + } + + Y_UNIT_TEST(JoinParseCorrect) { + NYql::TAstParseResult res = SqlToYql( + "PRAGMA DisableSimpleColumns;" + " SELECT table_bb.*, table_aa.key as megakey" + " FROM plato.Input AS table_aa" + " JOIN plato.Input AS table_bb" + " ON table_aa.value == table_bb.value;" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "SelectMembers") { + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("table_aa.")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table_bb.")); + } else if (word == "SqlProjectItem") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("megakey"))); + } else if (word == "SqlColumn") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("table_aa"))); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("key"))); + } + }; + TWordCountHive elementStat = {{TString("SqlProjectItem"), 0}, {TString("SqlProjectStarItem"), 0}, {TString("SelectMembers"), 0}, {TString("SqlColumn"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectStarItem"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SelectMembers"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlColumn"]); + } + + Y_UNIT_TEST(Join3Table) { + NYql::TAstParseResult res = SqlToYql( + " PRAGMA DisableSimpleColumns;" + " SELECT table_bb.*, table_aa.key as gigakey, table_cc.* " + " FROM plato.Input AS table_aa" + " JOIN plato.Input AS table_bb ON table_aa.key == table_bb.key" + " JOIN plato.Input AS table_cc ON table_aa.subkey == table_cc.subkey;" + ); + Err2Str(res); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "SelectMembers") { + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("table_aa.")); + UNIT_ASSERT(line.find("table_bb.") != TString::npos || line.find("table_cc.") != TString::npos); + } else if (word == "SqlProjectItem") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("gigakey"))); + } else if (word == "SqlColumn") { + const auto posTableAA = line.find(Quote("table_aa")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, posTableAA); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("key"))); + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("table_aa", posTableAA + 3)); + } + }; + TWordCountHive elementStat = {{TString("SqlProjectItem"), 0}, {TString("SqlProjectStarItem"), 0}, {TString("SelectMembers"), 0}, {TString("SqlColumn"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlProjectStarItem"]); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SelectMembers"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlColumn"]); + } + + Y_UNIT_TEST(DisabledJoinCartesianProduct) { + NYql::TAstParseResult res = SqlToYql("pragma DisableAnsiImplicitCrossJoin; use plato; select * from A,B,C"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:67: Error: Cartesian product of tables is disabled. Please use explicit CROSS JOIN or enable it via PRAGMA AnsiImplicitCrossJoin\n"); + } + + Y_UNIT_TEST(JoinCartesianProduct) { + NYql::TAstParseResult res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; select * from A,B,C"); + UNIT_ASSERT(res.Root); + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "EquiJoin") { + auto pos = line.find("Cross"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, pos); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Cross", pos + 1)); + } + }; + TWordCountHive elementStat = {{TString("EquiJoin"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["EquiJoin"]); + } + + Y_UNIT_TEST(CreateAlterUserWithLoginNoLogin) { + auto reqCreateUser = SqlToYql(R"( + USE plato; + CREATE USER user1; + )"); + + UNIT_ASSERT(reqCreateUser.IsOk()); + + auto reqAlterUser = SqlToYql(R"( + USE plato; + ALTER USER user1; + )"); + + UNIT_ASSERT(!reqAlterUser.IsOk()); +#if ANTLR_VER == 3 + UNIT_ASSERT_STRING_CONTAINS(reqAlterUser.Issues.ToString(), "Error: Unexpected token ';' : cannot match to any predicted input..."); +#else + UNIT_ASSERT_STRING_CONTAINS(reqAlterUser.Issues.ToString(), "Error: mismatched input ';' expecting {ENCRYPTED, HASH, LOGIN, NOLOGIN, PASSWORD, RENAME, WITH}"); +#endif + + auto reqPasswordAndLogin = SqlToYql(R"( + USE plato; + CREATE USER user1 LOgin; + )"); + + UNIT_ASSERT(reqPasswordAndLogin.IsOk()); + + auto reqPasswordAndNoLogin = SqlToYql(R"( + USE plato; + CREATE USER user1 PASSWORD '123' NOLOGIN; + )"); + + UNIT_ASSERT(reqPasswordAndNoLogin.IsOk()); + + auto reqLogin = SqlToYql(R"( + USE plato; + CREATE USER user1 LOGIN; + )"); + + UNIT_ASSERT(reqLogin.IsOk()); + + auto reqNoLogin = SqlToYql(R"( + USE plato; + CREATE USER user1 NOLOGIN; + )"); + + UNIT_ASSERT(reqNoLogin.IsOk()); + + auto reqLoginNoLogin = SqlToYql(R"( + USE plato; + CREATE USER user1 LOGIN NOLOGIN; + )"); + + UNIT_ASSERT(!reqLoginNoLogin.IsOk()); + UNIT_ASSERT_STRING_CONTAINS(reqLoginNoLogin.Issues.ToString(), "Error: Conflicting or redundant options"); + + auto reqAlterLoginNoLogin = SqlToYql(R"( + USE plato; + CREATE USER user1 LOGIN; + ALTER USER user1 NOLOGIN; + )"); + + UNIT_ASSERT(reqAlterLoginNoLogin.IsOk()); + + auto reqAlterLoginNoLoginWithPassword = SqlToYql(R"( + USE plato; + CREATE USER user1 LOGIN; + ALTER USER user1 PASSWORD '321' NOLOGIN; + )"); + + UNIT_ASSERT(reqAlterLoginNoLoginWithPassword.IsOk()); + } + + Y_UNIT_TEST(CreateUserWithHash) { + auto reqCreateUser = SqlToYql(R"( + USE plato; + CREATE USER user1 HASH '{ + "hash": "p4ffeMugohqyBwyckYCK1TjJfz3LIHbKiGL+t+oEhzw=", + "salt": "U+tzBtgo06EBQCjlARA6Jg==", + "type": "argon2id" + }'; + )"); + + UNIT_ASSERT(reqCreateUser.IsOk()); + + auto reqCreateUserWithNoLogin = SqlToYql(R"( + USE plato; + CREATE USER user1 HASH '{ + "hash": "p4ffeMugohqyBwyckYCK1TjJfz3LIHbKiGL+t+oEhzw=", + "salt": "U+tzBtgo06EBQCjlARA6Jg==", + "type": "argon2id" + }' + NOLOGIN; + )"); + + UNIT_ASSERT(reqCreateUserWithNoLogin.IsOk()); + + auto reqCreateUserWithPassword = SqlToYql(R"( + USE plato; + CREATE USER user1 HASH '{ + "hash": "p4ffeMugohqyBwyckYCK1TjJfz3LIHbKiGL+t+oEhzw=", + "salt": "U+tzBtgo06EBQCjlARA6Jg==", + "type": "argon2id" + }' + PASSWORD '123'; + )"); + + UNIT_ASSERT(!reqCreateUserWithPassword.IsOk()); + UNIT_ASSERT_STRING_CONTAINS(reqCreateUserWithPassword.Issues.ToString(), "Error: Conflicting or redundant options"); + + auto reqAlterUser = SqlToYql(R"( + USE plato; + CREATE USER user1; + ALTER USER user1 HASH '{ + "hash": "p4ffeMugohqyBwyckYCK1TjJfz3LIHbKiGL+t+oEhzw=", + "salt": "U+tzBtgo06EBQCjlARA6Jg==", + "type": "argon2id" + }'; + )"); + + UNIT_ASSERT(reqAlterUser.IsOk()); + } + + Y_UNIT_TEST(JoinWithoutConcreteColumns) { + NYql::TAstParseResult res = SqlToYql( + " use plato;" + " SELECT a.v, b.value" + " FROM `Input1` VIEW `ksv` AS a" + " JOIN `Input2` AS b" + " ON a.k == b.key;" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "SqlProjectItem") { + UNIT_ASSERT(line.find(Quote("a.v")) != TString::npos || line.find(Quote("b.value")) != TString::npos); + } else if (word == "SqlColumn") { + const auto posTableA = line.find(Quote("a")); + const auto posTableB = line.find(Quote("b")); + if (posTableA != TString::npos) { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("v"))); + } else { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, posTableB); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("value"))); + } + } + }; + TWordCountHive elementStat = {{TString("SqlProjectStarItem"), 0}, {TString("SqlProjectItem"), 0}, {TString("SqlColumn"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(0, elementStat["SqlProjectStarItem"]); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlProjectItem"]); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlColumn"]); + } + + Y_UNIT_TEST(JoinWithSameValues) { + NYql::TAstParseResult res = SqlToYql("SELECT a.value, b.value FROM plato.Input AS a JOIN plato.Input as b ON a.key == b.key;"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "SqlProjectItem") { + const bool isValueFromA = TString::npos != line.find(Quote("a.value")); + const bool isValueFromB = TString::npos != line.find(Quote("b.value")); + UNIT_ASSERT(isValueFromA || isValueFromB); + } if (word == "Write!") { + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("a.a.")); + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("b.b.")); + } + }; + TWordCountHive elementStat = {{TString("SqlProjectStarItem"), 0}, {TString("SqlProjectItem"), 0}, {"Write!", 0}}; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(0, elementStat["SqlProjectStarItem"]); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["SqlProjectItem"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + } + + Y_UNIT_TEST(SameColumnsForDifferentTables) { + NYql::TAstParseResult res = SqlToYql("SELECT a.key, b.key FROM plato.Input as a JOIN plato.Input as b on a.key==b.key;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(SameColumnsForDifferentTablesFullJoin) { + NYql::TAstParseResult res = SqlToYql("SELECT a.key, b.key, a.value, b.value FROM plato.Input AS a FULL JOIN plato.Input AS b USING(key);"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(JoinStreamLookupStrategyHint) { + { + NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ StreamLookup() */ plato.Input AS b USING(key);"); + UNIT_ASSERT(res.Root); + } + //case insensitive + { + NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ streamlookup() */ plato.Input AS b USING(key);"); + UNIT_ASSERT(res.Root); + } + } + + Y_UNIT_TEST(JoinConflictingStrategyHint) { + { + NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ StreamLookup() */ /*+ Merge() */ plato.Input AS b USING(key);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:91: Error: Conflicting join strategy hints\n"); + } + } + + Y_UNIT_TEST(JoinDuplicatingStrategyHint) { + { + NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ StreamLookup() */ /*+ StreamLookup() */ plato.Input AS b USING(key);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:98: Error: Duplicate join strategy hint\n"); + } + } + + Y_UNIT_TEST(WarnCrossJoinStrategyHint) { + NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a CROSS JOIN /*+ merge() */ plato.Input AS b;"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:32: Warning: Non-default join strategy will not be used for CROSS JOIN, code: 4534\n"); + } + + Y_UNIT_TEST(WarnCartesianProductStrategyHint) { + NYql::TAstParseResult res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; SELECT * FROM A, /*+ merge() */ B;"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:74: Warning: Non-default join strategy will not be used for CROSS JOIN, code: 4534\n"); + } + + Y_UNIT_TEST(WarnUnknownJoinStrategyHint) { + NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input AS a JOIN /*+ xmerge() */ plato.Input AS b USING (key);"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_STRINGS_EQUAL(res.Issues.ToString(), "<main>:1:41: Warning: Unsupported join hint: xmerge, code: 4534\n"); + } + + Y_UNIT_TEST(ReverseLabels) { + NYql::TAstParseResult res = SqlToYql("select in.key as subkey, subkey as key from plato.Input as in;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(AutogenerationAliasWithoutCollisionConflict1) { + NYql::TAstParseResult res = SqlToYql("select LENGTH(Value), key as column1 from plato.Input;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(AutogenerationAliasWithoutCollision2Conflict2) { + NYql::TAstParseResult res = SqlToYql("select key as column0, LENGTH(Value) from plato.Input;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(InputAliasForQualifiedAsterisk) { + NYql::TAstParseResult res = SqlToYql("use plato; select zyuzya.*, key from plato.Input as zyuzya;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(SelectSupportsResultColumnsWithTrailingComma) { + NYql::TAstParseResult res = SqlToYql("select a, b, c, from plato.Input;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(SelectOrderByLabeledColumn) { + NYql::TAstParseResult res = SqlToYql("pragma DisableOrderedColumns; select key as goal from plato.Input order by goal"); + UNIT_ASSERT(res.Root); + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "DataSource") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("plato")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Input")); + + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("goal")); + } else if (word == "Sort") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("goal")); + + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("key")); + } + }; + TWordCountHive elementStat = {{TString("DataSource"), 0}, {TString("Sort"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["DataSource"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Sort"]); + } + + Y_UNIT_TEST(SelectOrderBySimpleExpr) { + NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by a + a"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(SelectAssumeOrderByTableRowAccess) { + NYql::TAstParseResult res = SqlToYql("$key = 'foo';select * from plato.Input assume order by TableRow().$key"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(SelectOrderByDuplicateLabels) { + NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by a, a"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(SelectOrderByExpression) { + NYql::TAstParseResult res = SqlToYql("select * from plato.Input as i order by cast(key as uint32) + cast(subkey as uint32)"); + UNIT_ASSERT(res.Root); + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Sort") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"+MayWarn\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("key")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("subkey")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Bool 'true)")); + + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("i.key")); + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("i.subkey")); + } + }; + TWordCountHive elementStat = {{TString("Sort"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Sort"]); + } + + Y_UNIT_TEST(SelectOrderByExpressionDesc) { + NYql::TAstParseResult res = SqlToYql("pragma disablesimplecolumns; select i.*, key, subkey from plato.Input as i order by cast(i.key as uint32) - cast(i.subkey as uint32) desc"); + UNIT_ASSERT(res.Root); + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Sort") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"-MayWarn\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"key\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"subkey\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Bool 'false)")); + } else if (word == "Write!") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'columns")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"key\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"subkey\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("prefix")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"i.\"")); + } + }; + TWordCountHive elementStat = {{TString("Sort"), 0}, {TString("Write!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Sort"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + } + + Y_UNIT_TEST(SelectOrderByExpressionAsc) { + NYql::TAstParseResult res = SqlToYql("select i.key, i.subkey from plato.Input as i order by cast(key as uint32) % cast(i.subkey as uint32) asc"); + UNIT_ASSERT(res.Root); + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Sort") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"%MayWarn\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"key\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"subkey\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Bool 'true)")); + } else if (word == "Write!") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'columns")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"key\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"subkey\"")); + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("i.")); + } + }; + TWordCountHive elementStat = {{TString("Sort"), 0}, {TString("Write!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Sort"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + } + + Y_UNIT_TEST(ReferenceToKeyInSubselect) { + NYql::TAstParseResult res = SqlToYql("select b.key from (select a.key from plato.Input as a) as b;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(OrderByCastValue) { + NYql::TAstParseResult res = SqlToYql("select i.key, i.subkey from plato.Input as i order by cast(key as uint32) desc;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(GroupByCastValue) { + NYql::TAstParseResult res = SqlToYql("select count(1) from plato.Input as i group by cast(key as uint8);"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(KeywordInSelectColumns) { + NYql::TAstParseResult res = SqlToYql("select in, s.check from (select 1 as in, \"test\" as check) as s;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(SelectAllGroupBy) { + NYql::TAstParseResult res = SqlToYql("select * from plato.Input group by subkey;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(CreateObjectWithFeatures) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT secretId (TYPE SECRET) WITH (Key1=Value1, K2=V2);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('\"K2\" '\"V2\") '('\"Key1\" '\"Value1\")")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); + } + + Y_UNIT_TEST(CreateObjectIfNotExists) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT IF NOT EXISTS secretId (TYPE SECRET) WITH (Key1=Value1, K2=V2);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObjectIfNotExists")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); + } + + Y_UNIT_TEST(CreateObjectWithFeaturesStrings) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT secretId (TYPE SECRET) WITH (Key1=\"Value1\", K2='V2', K3=V3, K4='', K5=`aaa`, K6='a\\'aa');"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('\"K2\" '\"V2\") '('\"K3\" '\"V3\") '('\"K4\" '\"\") '('\"K5\" '\"aaa\") '('\"K6\" '\"a'aa\") '('\"Key1\" '\"Value1\")")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}, {TString("SECRET"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + } + + Y_UNIT_TEST(UpsertObjectWithFeatures) { + NYql::TAstParseResult res = SqlToYql("USE plato; UPSERT OBJECT secretId (TYPE SECRET) WITH (Key1=Value1, K2=V2);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('\"K2\" '\"V2\") '('\"Key1\" '\"Value1\")")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("upsertObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); + } + + Y_UNIT_TEST(CreateObjectWithFeaturesAndFlags) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT secretId (TYPE SECRET) WITH (Key1=Value1, K2=V2, RECURSE);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('\"Key1\" '\"Value1\") '('\"RECURSE\")")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); + } + + Y_UNIT_TEST(Select1Type) { + NYql::TAstParseResult res = SqlToYql("SELECT 1 type;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(SelectTableType) { + NYql::TAstParseResult res = SqlToYql("USE plato; SELECT * from T type;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(CreateObjectNoFeatures) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE OBJECT secretId (TYPE SECRET);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); + } + + Y_UNIT_TEST(AlterObjectWithFeatures) { + NYql::TAstParseResult res = SqlToYql( + "USE plato;\n" + "declare $path as String;\n" + "ALTER OBJECT secretId (TYPE SECRET) SET (Key1=$path, K2=V2);" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'features")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"Key1\" (EvaluateAtom \"$path\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"K2\" '\"V2\"")); + + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alterObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); + } + + Y_UNIT_TEST(AlterObjectNoFeatures) { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER OBJECT secretId (TYPE SECRET);"); + UNIT_ASSERT(!res.Root); + } + + Y_UNIT_TEST(DropObjectNoFeatures) { + NYql::TAstParseResult res = SqlToYql("USE plato; DROP OBJECT secretId (TYPE SECRET);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); + } + + Y_UNIT_TEST(DropObjectWithFeatures) { + NYql::TAstParseResult res = SqlToYql("USE plato; DROP OBJECT secretId (TYPE SECRET) WITH (A, B, C);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'features")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); + } + + Y_UNIT_TEST(DropObjectWithOneOption) { + NYql::TAstParseResult res = SqlToYql("USE plato; DROP OBJECT secretId (TYPE SECRET) WITH OVERRIDE;"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'features")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"OVERRIDE\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); + } + + Y_UNIT_TEST(DropObjectIfExists) { + NYql::TAstParseResult res = SqlToYql("USE plato; DROP OBJECT IF EXISTS secretId (TYPE SECRET);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObjectIfExists")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}, {TString("SECRET"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SECRET"]); + } + + Y_UNIT_TEST(PrimaryKeyParseCorrect) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE tableName (Key Uint32, Subkey Int64, Value String, PRIMARY KEY (Key, Subkey));"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"Key\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"Subkey\"")); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}, {TString("primarykey"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["primarykey"]); + } + + Y_UNIT_TEST(AlterDatabaseAst) { + NYql::TAstParseResult request = SqlToYql("USE plato; ALTER DATABASE `/Root/test` OWNER TO user1;"); + UNIT_ASSERT(request.IsOk()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + Y_UNUSED(word); + + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find( + R"(let world (Write! world sink (Key '('databasePath (String '"/Root/test"))) (Void) '('('mode 'alterDatabase) '('owner '"user1"))))" + )); + }; + + TWordCountHive elementStat({TString("\'mode \'alterDatabase")}); + VerifyProgram(request, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["\'mode \'alterDatabase"]); + } + + Y_UNIT_TEST(CreateTableNonNullableYqlTypeAstCorrect) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32 not null);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (DataType 'Int32) '('columnConstrains '('('not_null))) '())))))))__")); + } + }; + + TWordCountHive elementStat = {{TString("Write!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + } + + Y_UNIT_TEST(CreateTableNullableYqlTypeAstCorrect) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))))))__")); + } + }; + + TWordCountHive elementStat = {{TString("Write!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + } + + Y_UNIT_TEST(CreateTableNonNullablePgTypeAstCorrect) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a pg_int4 not null);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (PgType '_int4) '('columnConstrains '('('not_null))) '())))))))__")); + } + }; + + TWordCountHive elementStat = {{TString("Write!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + } + + Y_UNIT_TEST(CreateTableNullablePgTypeAstCorrect) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a pg_int4);"); + UNIT_ASSERT(res.Root); + + res.Root->PrettyPrintTo(Cout, PRETTY_FLAGS); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (PgType '_int4)) '('columnConstrains '()) '()))))))__")); + } + }; + + TWordCountHive elementStat = {{TString("Write!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + } + + Y_UNIT_TEST(CreateTableNullPkColumnsAreAllowed) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32, primary key(a));"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))) '('primarykey '('"a")))))__")); + } + }; + + TWordCountHive elementStat = {{TString("Write!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + } + + Y_UNIT_TEST(CreateTableNotNullPkColumnsAreIdempotentAstCorrect) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32 not null, primary key(a));"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (DataType 'Int32) '('columnConstrains '('('not_null))) '()))) '('primarykey '('"a"))))))__")); + } + }; + + TWordCountHive elementStat = {{TString("Write!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + } + + Y_UNIT_TEST(CreateTableWithIfNotExists) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE IF NOT EXISTS t (a int32, primary key(a));"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create_if_not_exists) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))) '('primarykey '('"a")))))__")); + } + }; + + TWordCountHive elementStat = {{TString("Write!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + } + + Y_UNIT_TEST(CreateTempTable) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TEMP TABLE t (a int32, primary key(a));"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, + line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))) '('primarykey '('"a")) '('temporary))))__"), line); + } + }; + + TWordCountHive elementStat = {{TString("Write!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + } + + Y_UNIT_TEST(CreateTemporaryTable) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TEMPORARY TABLE t (a int32, primary key(a));"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, + line.find(R"__((Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a" (AsOptionalType (DataType 'Int32)) '('columnConstrains '()) '()))) '('primarykey '('"a")) '('temporary))))__"), line); + } + }; + + TWordCountHive elementStat = {{TString("Write!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + } + + Y_UNIT_TEST(CreateTableWithoutTypes) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a, primary key(a));"); + UNIT_ASSERT(!res.Root); + } + + Y_UNIT_TEST(CreateTableAsSelectWithTypes) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32, primary key(a)) AS SELECT * FROM ts;"); + UNIT_ASSERT(!res.Root); + } + + Y_UNIT_TEST(CreateTableAsSelect) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a, b, primary key(a)) AS SELECT * FROM ts;"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((let world (Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '('('"a") '('"b"))) '('primarykey '('"a"))))))__")); + } + if (word == "Read!") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((Read! world (DataSource '"yt" '"plato") (MrTableConcat (Key '('table (String '"ts")))))__")); + } + }; + + TWordCountHive elementStat = {{TString("Write!"), 0}, {TString("Read!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Read!"]); + } + + Y_UNIT_TEST(CreateTableAsSelectOnlyPrimary) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (primary key(a)) AS SELECT * FROM ts;"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((let world (Write! world sink (Key '('tablescheme (String '"t"))) values '('('mode 'create) '('columns '()) '('primarykey '('"a"))))))__")); + } + if (word == "Read!") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((Read! world (DataSource '"yt" '"plato") (MrTableConcat (Key '('table (String '"ts")))))__")); + } + }; + + TWordCountHive elementStat = {{TString("Write!"), 0}, {TString("Read!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Read!"]); + } + + Y_UNIT_TEST(CreateTableAsValuesFail) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a, primary key(a)) AS VALUES (1), (2);"); + UNIT_ASSERT(!res.Root); + } + + Y_UNIT_TEST(CreateTableDuplicatedPkColumnsFail) { + NYql::TAstParseResult res = SqlToYql("USE plato; CREATE TABLE t (a int32 not null, primary key(a, a));"); + UNIT_ASSERT(!res.Root); + } + + Y_UNIT_TEST(DeleteFromTableByKey) { + NYql::TAstParseResult res = SqlToYql("delete from plato.Input where key = 200;", 10, "kikimr"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete)")); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DeleteFromTable) { + NYql::TAstParseResult res = SqlToYql("delete from plato.Input;", 10, "kikimr"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete)")); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DeleteFromTableBatch) { + NYql::TAstParseResult res = SqlToYql("batch delete from plato.Input;", 10, "kikimr"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete)")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('is_batch 'true)")); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DeleteFromTableOnValues) { + NYql::TAstParseResult res = SqlToYql("delete from plato.Input on (key) values (1);", + 10, "kikimr"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete_on)")); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DeleteFromTableOnSelect) { + NYql::TAstParseResult res = SqlToYql( + "delete from plato.Input on select key from plato.Input where value > 0;", 10, "kikimr"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'delete_on)")); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DeleteFromTableOnBatch) { + NYql::TAstParseResult res = SqlToYql("batch delete from plato.Input on (key) values (1);", + 10, "kikimr"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:6: Error: BATCH DELETE is unsupported with ON\n"); + } + + Y_UNIT_TEST(UpdateByValues) { + NYql::TAstParseResult res = SqlToYql("update plato.Input set key = 777, value = 'cool' where key = 200;", 10, "kikimr"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update)")); + } else if (word == "AsStruct") { + const bool isKey = line.find("key") != TString::npos; + const bool isValue = line.find("value") != TString::npos; + UNIT_ASSERT(isKey || isValue); + if (isKey) { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("777"))); + } else if (isValue) { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("cool"))); + } + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}, {TString("AsStruct"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["AsStruct"]); + } + + Y_UNIT_TEST(UpdateByValuesBatch) { + NYql::TAstParseResult res = SqlToYql("batch update plato.Input set key = 777, value = 'cool' where key = 200;", 10, "kikimr"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update)")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('is_batch 'true)")); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(UpdateByMultiValues) { + NYql::TAstParseResult res = SqlToYql("update plato.Input set (key, value, subkey) = ('2','ddd',':') where key = 200;", 10, "kikimr"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update)")); + } else if (word == "AsStruct") { + const bool isKey = line.find("key") != TString::npos; + const bool isSubkey = line.find("subkey") != TString::npos; + const bool isValue = line.find("value") != TString::npos; + UNIT_ASSERT(isKey || isSubkey || isValue); + if (isKey && !isSubkey) { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("2"))); + } else if (isSubkey) { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote(":"))); + } else if (isValue) { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("ddd"))); + } + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}, {TString("AsStruct"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["AsStruct"]); + } + + Y_UNIT_TEST(UpdateBySelect) { + NYql::TAstParseResult res = SqlToYql("update plato.Input set (key, value, subkey) = (select key, value, subkey from plato.Input where key = 911) where key = 200;", 10, "kikimr"); + UNIT_ASSERT(res.Root); + + int lineIndex = 0; + int writeLineIndex = -1; + bool found = false; + + TVerifyLineFunc verifyLine = [&lineIndex, &writeLineIndex, &found](const TString& word, const TString& line) { + if (word == "Write") { + writeLineIndex = lineIndex; + found = line.find("('mode 'update)") != TString::npos; + } else if (word == "mode") { + found |= lineIndex == writeLineIndex + 1 && line.find("('mode 'update)") != TString::npos; + UNIT_ASSERT(found); + } + + ++lineIndex; + }; + + TWordCountHive elementStat = {{TString("Write"), 0}, {TString("mode"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(UpdateSelfModifyAll) { + NYql::TAstParseResult res = SqlToYql("update plato.Input set subkey = subkey + 's';", 10, "kikimr"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update)")); + } else if (word == "AsStruct") { + const bool isSubkey = line.find("subkey") != TString::npos; + UNIT_ASSERT(isSubkey); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("subkey"))); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(Quote("s"))); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}, {TString("AsStruct"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["AsStruct"]); + } + + Y_UNIT_TEST(UpdateOnValues) { + NYql::TAstParseResult res = SqlToYql("update plato.Input on (key, value) values (5, 'cool')", 10, "kikimr"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update_on)")); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(UpdateOnSelect) { + NYql::TAstParseResult res = SqlToYql( + "update plato.Input on select key, value + 1 as value from plato.Input", 10, "kikimr"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("('mode 'update_on)")); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(UpdateOnBatch) { + NYql::TAstParseResult res = SqlToYql("batch update plato.Input on (key, value) values (5, 'cool')", 10, "kikimr"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:6: Error: BATCH UPDATE is unsupported with ON\n"); + } + + Y_UNIT_TEST(UnionAllTest) { + NYql::TAstParseResult res = SqlToYql("PRAGMA DisableEmitUnionMerge; SELECT key FROM plato.Input UNION ALL select subkey FROM plato.Input;"); + UNIT_ASSERT(res.Root); + + TWordCountHive elementStat = {{TString("UnionAll"), 0}}; + VerifyProgram(res, elementStat, {}); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["UnionAll"]); + } + + Y_UNIT_TEST(UnionAllMergeTest) { + NYql::TAstParseResult res = SqlToYql("PRAGMA EmitUnionMerge; SELECT key FROM plato.Input UNION ALL select subkey FROM plato.Input;"); + UNIT_ASSERT(res.Root); + + TWordCountHive elementStat = {{TString("UnionMerge"), 0}}; + VerifyProgram(res, elementStat, {}); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["UnionMerge"]); + } + + Y_UNIT_TEST(UnionTest) { + NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input UNION select subkey FROM plato.Input;"); + UNIT_ASSERT(res.Root); + + TWordCountHive elementStat = {{TString("Union"), 0}}; + VerifyProgram(res, elementStat, {}); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Union"]); + } + + Y_UNIT_TEST(UnionAggregationTest) { + NYql::TAstParseResult res = SqlToYql(R"( + PRAGMA DisableEmitUnionMerge; + SELECT 1 + UNION ALL + SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 + UNION + SELECT 1 UNION SELECT 1 UNION SELECT 1 UNION SELECT 1 + UNION ALL + SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1; + )"); + UNIT_ASSERT(res.Root); + + TWordCountHive elementStat = {{TString("Union"), 0}, {TString("UnionAll"), 0}}; + VerifyProgram(res, elementStat, {}); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["UnionAll"]); + UNIT_ASSERT_VALUES_EQUAL(3, elementStat["Union"]); + } + + Y_UNIT_TEST(UnionMergeAggregationTest) { + NYql::TAstParseResult res = SqlToYql(R"( + PRAGMA EmitUnionMerge; + SELECT 1 + UNION ALL + SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 + UNION + SELECT 1 UNION SELECT 1 UNION SELECT 1 UNION SELECT 1 + UNION ALL + SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1; + )"); + UNIT_ASSERT(res.Root); + + TWordCountHive elementStat = {{TString("Union"), 0}, {TString("UnionMerge"), 0}}; + VerifyProgram(res, elementStat, {}); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["UnionMerge"]); + UNIT_ASSERT_VALUES_EQUAL(3, elementStat["Union"]); + } + + Y_UNIT_TEST(DeclareDecimalParameter) { + NYql::TAstParseResult res = SqlToYql("declare $value as Decimal(22,9); select $value as cnt;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(SimpleGroupBy) { + NYql::TAstParseResult res = SqlToYql("select count(1),z from plato.Input group by key as z order by z;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(EmptyColumnName0) { + /// Now it's parsed well and error occur on validate step like "4:31:Empty struct member name is not allowed" in "4:31:Function: AddMember" + NYql::TAstParseResult res = SqlToYql("insert into plato.Output (``, list1) values (0, AsList(0, 1, 2));"); + /// Verify that parsed well without crash + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(KikimrRollback) { + NYql::TAstParseResult res = SqlToYql("use plato; select * from Input; rollback;", 10, "kikimr"); + UNIT_ASSERT(res.Root); + + TWordCountHive elementStat = {{TString("rollback"), 0}}; + VerifyProgram(res, elementStat); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["rollback"]); + } + + Y_UNIT_TEST(PragmaFile) { + NYql::TAstParseResult res = SqlToYql(R"(pragma file("HW", "sbr:181041334");)"); + UNIT_ASSERT(res.Root); + + TWordCountHive elementStat = {{TString(R"((let world (Configure! world (DataSource '"config") '"AddFileByUrl" '"HW" '"sbr:181041334")))"), 0}}; + VerifyProgram(res, elementStat); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat.cbegin()->second); + } + + Y_UNIT_TEST(DoNotCrashOnNamedInFilter) { + NYql::TAstParseResult res = SqlToYql("USE plato; $all = ($table_name) -> { return true; }; SELECT * FROM FILTER(Input, $all)"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(PragmasFileAndUdfOrder) { + NYql::TAstParseResult res = SqlToYql(R"( + PRAGMA file("libvideoplayers_udf.so", "https://proxy.sandbox.yandex-team.ru/235185290"); + PRAGMA udf("libvideoplayers_udf.so"); + )"); + UNIT_ASSERT(res.Root); + + const auto programm = GetPrettyPrint(res); + const auto file = programm.find("AddFileByUrl"); + const auto udfs = programm.find("ImportUdfs"); + UNIT_ASSERT(file < udfs); + } + + Y_UNIT_TEST(ProcessUserType) { + NYql::TAstParseResult res = SqlToYql("process plato.Input using Kikimr::PushData(TableRows());", 1, TString(NYql::KikimrProviderName)); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Kikimr.PushData") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("TupleType")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("TypeOf")); + } + }; + + TWordCountHive elementStat = {{TString("Kikimr.PushData"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Kikimr.PushData"]); + } + + Y_UNIT_TEST(ProcessUserTypeAuth) { + NYql::TAstParseResult res = SqlToYql("process plato.Input using YDB::PushData(TableRows(), AsTuple('oauth', SecureParam('api:oauth')));", 1, TString(NYql::KikimrProviderName)); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "YDB.PushData") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("TupleType")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("TypeOf")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("api:oauth")); + } + }; + + TWordCountHive elementStat = {{TString("YDB.PushData"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["YDB.PushData"]); + } + + Y_UNIT_TEST(SelectStreamRtmr) { + NYql::TAstParseResult res = SqlToYql( + "USE plato; INSERT INTO Output SELECT STREAM key FROM Input;", + 10, TString(NYql::RtmrProviderName)); + UNIT_ASSERT(res.Root); + + res = SqlToYql( + "USE plato; INSERT INTO Output SELECT key FROM Input;", + 10, TString(NYql::RtmrProviderName)); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(SelectStreamRtmrJoinWithYt) { + NYql::TAstParseResult res = SqlToYql( + "USE plato; INSERT INTO Output SELECT STREAM key FROM Input LEFT JOIN hahn.ttt as t ON Input.key = t.Name;", + 10, TString(NYql::RtmrProviderName)); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(SelectStreamNonRtmr) { + NYql::TAstParseResult res = SqlToYql( + "USE plato; INSERT INTO Output SELECT STREAM key FROM Input;", + 10); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:31: Error: SELECT STREAM is unsupported for non-streaming sources\n"); + } + + Y_UNIT_TEST(GroupByHopRtmr) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; INSERT INTO Output SELECT key, SUM(value) AS value FROM Input + GROUP BY key, HOP(subkey, "PT10S", "PT30S", "PT20S"); + )", 10, TString(NYql::RtmrProviderName)); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(GroupByHopRtmrSubquery) { + // 'use plato' intentially avoided + NYql::TAstParseResult res = SqlToYql(R"( + SELECT COUNT(*) AS value FROM (SELECT * FROM plato.Input) + GROUP BY HOP(Data, "PT10S", "PT30S", "PT20S") + )", 10, TString(NYql::RtmrProviderName)); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(GroupByHopRtmrSubqueryBinding) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; + $q = SELECT * FROM Input; + INSERT INTO Output SELECT STREAM * FROM ( + SELECT COUNT(*) AS value FROM $q + GROUP BY HOP(Data, "PT10S", "PT30S", "PT20S") + ); + )", 10, TString(NYql::RtmrProviderName)); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(GroupByNoHopRtmr) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; INSERT INTO Output SELECT STREAM key, SUM(value) AS value FROM Input + GROUP BY key; + )", 10, TString(NYql::RtmrProviderName)); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:22: Error: Streaming group by query must have a hopping window specification.\n"); + } + + Y_UNIT_TEST(KikimrInserts) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; + INSERT INTO Output SELECT key, value FROM Input; + INSERT OR ABORT INTO Output SELECT key, value FROM Input; + INSERT OR IGNORE INTO Output SELECT key, value FROM Input; + INSERT OR REVERT INTO Output SELECT key, value FROM Input; + )", 10, TString(NYql::KikimrProviderName)); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(WarnMissingIsBeforeNotNull) { + NYql::TAstParseResult res = SqlToYql("select 1 NOT NULL"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Warning: Missing IS keyword before NOT NULL, code: 4507\n"); + } + + Y_UNIT_TEST(Subqueries) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; + $sq1 = (SELECT * FROM plato.Input); + + $sq2 = SELECT * FROM plato.Input; + + $squ1 = ( + SELECT * FROM plato.Input + UNION ALL + SELECT * FROM plato.Input + ); + + $squ2 = + SELECT * FROM plato.Input + UNION ALL + SELECT * FROM plato.Input; + + $squ3 = ( + (SELECT * FROM plato.Input) + UNION ALL + (SELECT * FROM plato.Input) + ); + + SELECT * FROM $sq1; + SELECT * FROM $sq2; + SELECT * FROM $squ1; + SELECT * FROM $squ2; + SELECT * FROM $squ3; + )"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(SubqueriesJoin) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; + + $left = SELECT * FROM plato.Input1 WHERE value != "BadValue"; + $right = SELECT * FROM plato.Input2; + + SELECT * FROM $left AS l + JOIN $right AS r + ON l.key == r.key; + )"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(AnyInBackticksAsTableName) { + NYql::TAstParseResult res = SqlToYql("use plato; select * from `any`;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(AnyJoinForTableAndSubQuery) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; + + $r = SELECT * FROM plato.Input2; + + SELECT * FROM ANY plato.Input1 AS l + LEFT JOIN ANY $r AS r + USING (key); + )"); + + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "EquiJoin") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('left 'any)")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('right 'any)")); + } + }; + + TWordCountHive elementStat = {{TString("left"), 0}, {TString("right"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["left"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["right"]); + } + + Y_UNIT_TEST(AnyJoinForTableAndTableSource) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; + + $r = AsList( + AsStruct("aaa" as key, "bbb" as subkey, "ccc" as value) + ); + + SELECT * FROM ANY plato.Input1 AS l + LEFT JOIN ANY AS_TABLE($r) AS r + USING (key); + )"); + + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "EquiJoin") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('left 'any)")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('right 'any)")); + } + }; + + TWordCountHive elementStat = {{TString("left"), 0}, {TString("right"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["left"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["right"]); + } + + Y_UNIT_TEST(AnyJoinNested) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; + + FROM ANY Input1 as a + JOIN Input2 as b ON a.key = b.key + LEFT JOIN ANY Input3 as c ON a.key = c.key + RIGHT JOIN ANY Input4 as d ON d.key = b.key + CROSS JOIN Input5 + SELECT *; + )"); + + UNIT_ASSERT(res.Root); + + TWordCountHive elementStat = {{TString("left"), 0}, {TString("right"), 0}}; + VerifyProgram(res, elementStat); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["left"]); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["right"]); + } + + Y_UNIT_TEST(InlineAction) { + NYql::TAstParseResult res = SqlToYql( + "do begin\n" + " select 1\n" + "; end do\n"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), ""); + } + + Y_UNIT_TEST(FlattenByCorrelationName) { + UNIT_ASSERT(SqlToYql("select * from plato.Input as t flatten by t.x").IsOk()); + UNIT_ASSERT(SqlToYql("select * from plato.Input as t flatten by t -- same as flatten by t.t").IsOk()); + } + + Y_UNIT_TEST(DiscoveryMode) { + UNIT_ASSERT(SqlToYqlWithMode("insert into plato.Output select * from plato.Input", NSQLTranslation::ESqlMode::DISCOVERY).IsOk()); + UNIT_ASSERT(SqlToYqlWithMode("select * from plato.concat(Input1, Input2)", NSQLTranslation::ESqlMode::DISCOVERY).IsOk()); + UNIT_ASSERT(SqlToYqlWithMode("select * from plato.each(AsList(\"Input1\", \"Input2\"))", NSQLTranslation::ESqlMode::DISCOVERY).IsOk()); + } + + Y_UNIT_TEST(CubeWithAutoGeneratedLikeColumnName) { + UNIT_ASSERT(SqlToYql("select key,subkey,group from plato.Input group by cube(key,subkey,group)").IsOk()); + } + + Y_UNIT_TEST(CubeWithAutoGeneratedLikeAlias) { + UNIT_ASSERT(SqlToYql("select key,subkey,group from plato.Input group by cube(key,subkey,value as group)").IsOk()); + } + + Y_UNIT_TEST(FilterCanBeUsedAsColumnIdOrBind) { + UNIT_ASSERT(SqlToYql("select filter from plato.Input").IsOk()); + UNIT_ASSERT(SqlToYql("select 1 as filter").IsOk()); + UNIT_ASSERT(SqlToYql("$filter = 1; select $filter").IsOk()); + } + + Y_UNIT_TEST(DuplicateSemicolonsAreAllowedBetweenTopLevelStatements) { + UNIT_ASSERT(SqlToYql(";;select 1; ; select 2;/*comment*/;select 3;;--comment\n;select 4;;").IsOk()); + } + + Y_UNIT_TEST(DuplicateAndMissingTrailingSemicolonsAreAllowedBetweenActionStatements) { + TString req = + "define action $action($b,$c) as\n" + " ;;$d = $b + $c;\n" + " select $b;\n" + " select $c;;\n" + " select $d,\n" + "end define;\n" + "\n" + "do $action(1,2);"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(DuplicateAndMissingTrailingSemicolonsAreAllowedBetweenInlineActionStatements) { + TString req = + "do begin\n" + " ;select 1,\n" + "end do;\n" + "evaluate for $i in AsList(1,2,3) do begin\n" + " select $i;;\n" + " select $i + $i;;\n" + "end do;"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(DuplicateSemicolonsAreAllowedBetweenLambdaStatements) { + TString req = + "$x=1;\n" + "$foo = ($a, $b)->{\n" + " ;;$v = $a + $b;\n" + " $bar = ($c) -> {; return $c << $x};;\n" + " return $bar($v);;\n" + "};\n" + "select $foo(1,2);"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(StringLiteralWithEscapedBackslash) { + NYql::TAstParseResult res1 = SqlToYql(R"foo(SELECT 'a\\';)foo"); + NYql::TAstParseResult res2 = SqlToYql(R"foo(SELECT "a\\";)foo"); + UNIT_ASSERT(res1.Root); + UNIT_ASSERT(res2.Root); + + TWordCountHive elementStat = {{TString("a\\"), 0}}; + + VerifyProgram(res1, elementStat); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["a\\"]); + + VerifyProgram(res2, elementStat); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["a\\"]); + } + + Y_UNIT_TEST(StringMultiLineLiteralWithEscapes) { + UNIT_ASSERT(SqlToYql("SELECT @@@foo@@@@bar@@@").IsOk()); + UNIT_ASSERT(SqlToYql("SELECT @@@@@@@@@").IsOk()); + } + + Y_UNIT_TEST(StringMultiLineLiteralConsequitiveAt) { + UNIT_ASSERT(!SqlToYql("SELECT @").IsOk()); + UNIT_ASSERT(!SqlToYql("SELECT @@").IsOk()); + UNIT_ASSERT(!SqlToYql("SELECT @@@").IsOk()); + UNIT_ASSERT( SqlToYql("SELECT @@@@").IsOk()); + UNIT_ASSERT( SqlToYql("SELECT @@@@@").IsOk()); + + UNIT_ASSERT(!SqlToYql("SELECT @@@@@@").IsOk()); + UNIT_ASSERT(!SqlToYql("SELECT @@@@@@@").IsOk()); + + UNIT_ASSERT( SqlToYql("SELECT @@@@@@@@").IsOk()); + UNIT_ASSERT( SqlToYql("SELECT @@@@@@@@@").IsOk()); + UNIT_ASSERT(!SqlToYql("SELECT @@@@@@@@@@").IsOk()); + } + + Y_UNIT_TEST(ConstnessForListDictSetCreate) { + auto req = "$foo = ($x, $y) -> (\"aaaa\");\n" + "\n" + "select\n" + " $foo(sum(key), ListCreate(String)),\n" + " $foo(sum(key), DictCreate(String, String)),\n" + " $foo(sum(key), SetCreate(String)),\n" + "from (select 1 as key);"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(CanUseEmptyTupleInWindowPartitionBy) { + auto req = "select sum(key) over w\n" + "from plato.Input\n" + "window w as (partition compact by (), (subkey), (), value || value as dvalue);"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(DenyAnsiOrderByLimitLegacyMode) { + auto req = "pragma DisableAnsiOrderByLimitInUnionAll;\n" + "use plato;\n" + "\n" + "select * from Input order by key limit 10\n" + "union all\n" + "select * from Input order by key limit 1;"; + + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: DisableAnsiOrderByLimitInUnionAll pragma is deprecated and no longer supported\n"); + } + + Y_UNIT_TEST(ReduceUsingUdfWithShortcutsWorks) { + auto req = "use plato;\n" + "\n" + "$arg = 'foo';\n" + "$func = XXX::YYY($arg);\n" + "\n" + "REDUCE Input ON key using $func(subkey);\n" + "REDUCE Input ON key using $func(UUU::VVV(TableRow()));\n"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + req = "use plato;\n" + "\n" + "$arg = 'foo';\n" + "$func = XXX::YYY($arg);\n" + "\n" + "REDUCE Input ON key using all $func(subkey);\n" + "REDUCE Input ON key using all $func(UUU::VVV(TableRow()));"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(YsonDisableStrict) { + UNIT_ASSERT(SqlToYql("pragma yson.DisableStrict = \"false\";").IsOk()); + UNIT_ASSERT(SqlToYql("pragma yson.DisableStrict;").IsOk()); + } + + Y_UNIT_TEST(YsonStrict) { + UNIT_ASSERT(SqlToYql("pragma yson.Strict = \"false\";").IsOk()); + UNIT_ASSERT(SqlToYql("pragma yson.Strict;").IsOk()); + } + + Y_UNIT_TEST(JoinByTuple) { + auto req = "use plato;\n" + "\n" + "select * from T1 as a\n" + "join T2 as b\n" + "on AsTuple(a.key, a.subkey) = AsTuple(b.key, b.subkey);"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(JoinByStruct) { + auto req = "use plato;\n" + "\n" + "select * from T1 as a\n" + "join T2 as b\n" + "on AsStruct(a.key as k, a.subkey as sk) = AsStruct(b.key as k, b.subkey as sk);"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(JoinByUdf) { + auto req = "use plato;\n" + "\n" + "select a.align\n" + "from T1 as a\n" + "join T2 as b\n" + "on Yson::SerializeJsonEncodeUtf8(a.align)=b.align;"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(EscapedIdentifierAsLambdaArg) { + auto req = "$f = ($`foo bar`, $x) -> { return $`foo bar` + $x; };\n" + "\n" + "select $f(1, 2);"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + const auto programm = GetPrettyPrint(res); + auto expected = "(lambda '(\"$foo bar\" \"$x\")"; + UNIT_ASSERT(programm.find(expected) != TString::npos); + } + + Y_UNIT_TEST(UdfSyntaxSugarOnlyCallable) { + auto req = "SELECT Udf(DateTime::FromString)('2022-01-01');"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + const auto programm = GetPrettyPrint(res); + auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType)))"; + UNIT_ASSERT(programm.find(expected) != TString::npos); + } + + Y_UNIT_TEST(UdfSyntaxSugarTypeNoRun) { + auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, 'foo' as TypeConfig)('2022-01-01');"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + const auto programm = GetPrettyPrint(res); + auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\")"; + UNIT_ASSERT(programm.find(expected) != TString::npos); + } + + Y_UNIT_TEST(UdfSyntaxSugarRunNoType) { + auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, Void() as RunConfig)('2022-01-01');"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + const auto programm = GetPrettyPrint(res); + auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"\" (Void))"; + UNIT_ASSERT(programm.find(expected) != TString::npos); + } + + Y_UNIT_TEST(UdfSyntaxSugarFullTest) { + auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, 'foo' as TypeConfig, Void() As RunConfig)('2022-01-01');"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + const auto programm = GetPrettyPrint(res); + auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\" (Void))"; + UNIT_ASSERT(programm.find(expected) != TString::npos); + } + + Y_UNIT_TEST(UdfSyntaxSugarOtherRunConfigs) { + auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, 'foo' as TypeConfig, '55' As RunConfig)('2022-01-01');"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + const auto programm = GetPrettyPrint(res); + auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\" (String '\"55\"))"; + UNIT_ASSERT(programm.find(expected) != TString::npos); + } + + Y_UNIT_TEST(UdfSyntaxSugarOtherRunConfigs2) { + auto req = "SELECT Udf(DateTime::FromString, String, Tuple<Int32, Float>, 'foo' as TypeConfig, AsTuple(32, 'no', AsStruct(1e-9 As SomeFloat)) As RunConfig)('2022-01-01');"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + const auto programm = GetPrettyPrint(res); + auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (DataType 'String) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\" '((Int32 '\"32\") (String '\"no\") (AsStruct '('\"SomeFloat\" (Double '\"1e-9\")))))"; + UNIT_ASSERT(programm.find(expected) != TString::npos); + } + + Y_UNIT_TEST(UdfSyntaxSugarOptional) { + auto req = "SELECT Udf(DateTime::FromString, String?, Int32??, Tuple<Int32, Float>, \"foo\" as TypeConfig, Void() As RunConfig)(\"2022-01-01\");"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + const auto programm = GetPrettyPrint(res); + auto expected = "(SqlCall '\"DateTime.FromString\" '((PositionalArgs (String '\"2022-01-01\")) (AsStruct)) (TupleType (TypeOf '((String '\"2022-01-01\"))) (TypeOf (AsStruct)) (TupleType (OptionalType (DataType 'String)) (OptionalType (OptionalType (DataType 'Int32))) (TupleType (DataType 'Int32) (DataType 'Float)))) '\"foo\" (Void))"; + UNIT_ASSERT(programm.find(expected) != TString::npos); + } + + Y_UNIT_TEST(CompactionPolicyParseCorrect) { + NYql::TAstParseResult res = SqlToYql( + R"( USE plato; + CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) + WITH ( COMPACTION_POLICY = "SomeCompactionPreset" );)" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("compactionPolicy")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("SomeCompactionPreset")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(AutoPartitioningBySizeParseCorrect) { + NYql::TAstParseResult res = SqlToYql( + R"( USE plato; + CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) + WITH ( AUTO_PARTITIONING_BY_SIZE = ENABLED );)" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("autoPartitioningBySize")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("ENABLED")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(UniformPartitionsParseCorrect) { + NYql::TAstParseResult res = SqlToYql( + R"( USE plato; + CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) + WITH ( UNIFORM_PARTITIONS = 16 );)" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("uniformPartitions")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("16")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DateTimeTtlParseCorrect) { + NYql::TAstParseResult res = SqlToYql( + R"( USE plato; + CREATE TABLE tableName (Key Uint32, CreatedAt Timestamp, PRIMARY KEY (Key)) + WITH (TTL = Interval("P1D") On CreatedAt);)" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(IntTtlParseCorrect) { + NYql::TAstParseResult res = SqlToYql( + R"( USE plato; + CREATE TABLE tableName (Key Uint32, CreatedAt Uint32, PRIMARY KEY (Key)) + WITH (TTL = Interval("P1D") On CreatedAt AS SECONDS);)" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("columnUnit")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("seconds")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(TtlTieringParseCorrect) { + NYql::TAstParseResult res = SqlToYql( + R"( USE plato; + CREATE TABLE tableName (Key Uint32, CreatedAt Uint32, PRIMARY KEY (Key)) + WITH (TTL = + Interval("P1D") TO EXTERNAL DATA SOURCE Tier1, + Interval("P2D") TO EXTERNAL DATA SOURCE Tier2, + Interval("P30D") DELETE + ON CreatedAt AS SECONDS);)" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("storageName")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier1")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier2")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("172800000")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("2592000000")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("columnUnit")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("seconds")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(TtlTieringWithOtherActionsParseCorrect) { + NYql::TAstParseResult res = SqlToYql( + R"( USE plato; + ALTER TABLE tableName + ADD FAMILY cold (DATA = "rot"), + SET TTL + Interval("P1D") TO EXTERNAL DATA SOURCE Tier1, + Interval("P2D") TO EXTERNAL DATA SOURCE Tier2, + Interval("P30D") DELETE + ON CreatedAt, + ALTER COLUMN payload_v2 SET FAMILY cold, + ALTER FAMILY default SET DATA "ssd" + ;)" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("addColumnFamilies")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("cold")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alterColumnFamilies")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("default")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("storageName")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier1")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier2")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("172800000")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("2592000000")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(TieringParseCorrect) { + NYql::TAstParseResult res = SqlToYql( + R"( USE plato; + CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) + WITH ( TIERING = 'my_tiering' );)" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiering")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("my_tiering")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(StoreExternalBlobsParseCorrect) { + NYql::TAstParseResult res = SqlToYql( + R"( USE plato; + CREATE TABLE tableName (Key Uint32, Value String, PRIMARY KEY (Key)) + WITH ( STORE_EXTERNAL_BLOBS = ENABLED );)" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("storeExternalBlobs")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("ENABLED")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DefaultValueColumn2) { + auto res = SqlToYql(R"( use plato; + $lambda = () -> { + RETURN CAST(RandomUuid(2) as String) + }; + + CREATE TABLE tableName ( + Key Uint32 DEFAULT RandomNumber(1), + Value String DEFAULT $lambda, + PRIMARY KEY (Key) + ); + )"); + + UNIT_ASSERT_C(res.Root, Err2Str(res)); + + const auto program = GetPrettyPrint(res); + + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("RandomNumber")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("RandomUuid")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("columnConstrains")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("columnConstrains")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, program.find("Write")); + +#if 0 + Cerr << program << Endl; +#endif + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DefaultValueColumn3) { + auto res = SqlToYql(R"( use plato; + + CREATE TABLE tableName ( + database_id Utf8, + cloud_id Utf8, + global_id Utf8 DEFAULT database_id || "=====", + PRIMARY KEY (database_id) + ); + )"); + + UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:6:40: Error: Column reference \"database_id\" is not allowed in current scope\n"); + UNIT_ASSERT(!res.Root); + } + + Y_UNIT_TEST(DefaultValueColumn) { + auto res = SqlToYql(R"( use plato; + CREATE TABLE tableName ( + Key Uint32 FAMILY cold DEFAULT 5, + Value String FAMILY default DEFAULT "empty", + PRIMARY KEY (Key), + FAMILY default ( + DATA = "test", + COMPRESSION = "lz4" + ), + FAMILY cold ( + DATA = "test", + COMPRESSION = "off" + ) + ); + )"); + + UNIT_ASSERT_C(res.Root, Err2Str(res)); + +#if 0 + const auto program = GetPrettyPrint(res); + Cerr << program << Endl; +#endif + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("default")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("columnConstrains")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("columnFamilies")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(ChangefeedParseCorrect) { + auto res = SqlToYql(R"( USE plato; + CREATE TABLE tableName ( + Key Uint32, PRIMARY KEY (Key), + CHANGEFEED feedName WITH ( + MODE = 'KEYS_ONLY', + FORMAT = 'json', + INITIAL_SCAN = TRUE, + VIRTUAL_TIMESTAMPS = FALSE, + BARRIERS_INTERVAL = Interval("PT1S"), + RETENTION_PERIOD = Interval("P1D"), + TOPIC_MIN_ACTIVE_PARTITIONS = 10, + AWS_REGION = 'aws:region' + ) + ); + )"); + UNIT_ASSERT_C(res.Root, Err2Str(res)); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("changefeed")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("mode")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("KEYS_ONLY")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("format")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("json")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("initial_scan")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("true")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("virtual_timestamps")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("false")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("barriers_interval")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("retention_period")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("topic_min_active_partitions")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("aws_region")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("aws:region")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CloneForAsTableWorksWithCube) { + UNIT_ASSERT(SqlToYql("SELECT * FROM AS_TABLE([<|k1:1, k2:1|>]) GROUP BY CUBE(k1, k2);").IsOk()); + } + + Y_UNIT_TEST(WindowPartitionByColumnProperlyEscaped) { + NYql::TAstParseResult res = SqlToYql("SELECT SUM(key) OVER w FROM plato.Input WINDOW w AS (PARTITION BY `column with space`);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "CalcOverWindow") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("\"column with space\"")); + } + }; + + TWordCountHive elementStat = { {TString("CalcOverWindow"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["CalcOverWindow"]); + } + + Y_UNIT_TEST(WindowPartitionByExpressionWithoutAliasesAreAllowed) { + NYql::TAstParseResult res = SqlToYql("SELECT SUM(key) OVER w FROM plato.Input as i WINDOW w AS (PARTITION BY ii.subkey);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "AddMember") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("AddMember row 'group_w_0 (SqlAccess 'struct (Member row '\"ii\")")); + } + if (word == "CalcOverWindow") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("CalcOverWindow core '('\"group_w_0\")")); + } + }; + + TWordCountHive elementStat = { {TString("CalcOverWindow"), 0}, {TString("AddMember"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["CalcOverWindow"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["AddMember"]); + } + + Y_UNIT_TEST(PqReadByAfterUse) { + ExpectFailWithError("use plato; pragma PqReadBy='plato2';", + "<main>:1:28: Error: Cluster in PqReadPqBy pragma differs from cluster specified in USE statement: plato2 != plato\n"); + + UNIT_ASSERT(SqlToYql("pragma PqReadBy='plato2';").IsOk()); + UNIT_ASSERT(SqlToYql("pragma PqReadBy='plato2'; use plato;").IsOk()); + UNIT_ASSERT(SqlToYql("$x='plato'; use rtmr:$x; pragma PqReadBy='plato2';").IsOk()); + UNIT_ASSERT(SqlToYql("use plato; pragma PqReadBy='dq';").IsOk()); + } + + Y_UNIT_TEST(MrObject) { + NYql::TAstParseResult res = SqlToYql( + "declare $path as String;\n" + "select * from plato.object($path, `format`, \"comp\" || \"ression\" as compression, 1 as bar) with schema (Int32 as y, String as x)" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "MrObject") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((MrObject (EvaluateAtom "$path") '"format" '('('"compression" (Concat (String '"comp") (String '"ression"))) '('"bar" (Int32 '"1")))))__")); + } else if (word == "userschema") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__('('('"userschema" (StructType '('"y" (DataType 'Int32)) '('"x" (DataType 'String))) '('"y" '"x"))))__")); + } + }; + + TWordCountHive elementStat = {{TString("MrObject"), 0}, {TString("userschema"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["MrObject"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["userschema"]); + } + + Y_UNIT_TEST(TableBindings) { + NSQLTranslation::TTranslationSettings settings = GetSettingsWithS3Binding("foo"); + NYql::TAstParseResult res = SqlToYqlWithSettings( + "select * from bindings.foo", + settings + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "MrObject") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((MrTableConcat (Key '('table (String '"path")))) (Void) '('('"bar" '"1") '('"compression" '"ccompression") '('"format" '"format") '('"partitionedby" '"key" '"subkey") '('"userschema" (SqlTypeFromYson)__")); + } + }; + + TWordCountHive elementStat = {{TString("MrTableConcat"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["MrTableConcat"]); + + settings.DefaultCluster = "plato"; + settings.BindingsMode = NSQLTranslation::EBindingsMode::DISABLED; + res = SqlToYqlWithSettings( + "select * from bindings.foo", + settings + ); + UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:1:15: Error: Please remove 'bindings.' from your query, the support for this syntax has ended, code: 4601\n"); + UNIT_ASSERT(!res.Root); + + settings.BindingsMode = NSQLTranslation::EBindingsMode::DROP; + res = SqlToYqlWithSettings( + "select * from bindings.foo", + settings + ); + + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine2 = [](const TString& word, const TString& line) { + if (word == "MrTableConcat") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((MrTableConcat (Key '('table (String '"foo")))) (Void) '())))__")); + } + }; + + TWordCountHive elementStat2 = {{TString("MrTableConcat"), 0}}; + VerifyProgram(res, elementStat2, verifyLine2); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat2["MrTableConcat"]); + + settings.BindingsMode = NSQLTranslation::EBindingsMode::DROP_WITH_WARNING; + res = SqlToYqlWithSettings( + "select * from bindings.foo", + settings + ); + UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:1:15: Warning: Please remove 'bindings.' from your query, the support for this syntax will be dropped soon, code: 4538\n"); + UNIT_ASSERT(res.Root); + + TWordCountHive elementStat3 = {{TString("MrTableConcat"), 0}}; + VerifyProgram(res, elementStat3, verifyLine2); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat3["MrTableConcat"]); + } + + Y_UNIT_TEST(TableBindingsWithInsert) { + NSQLTranslation::TTranslationSettings settings = GetSettingsWithS3Binding("foo"); + NYql::TAstParseResult res = SqlToYqlWithSettings( + "insert into bindings.foo with truncate (x, y) values (1, 2);", + settings + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((Write! world sink (Key '('table (String '"path"))) values '('('"bar" '"1") '('"compression" '"ccompression") '('"format" '"format") '('"partitionedby" '"key" '"subkey") '('"userschema" (SqlTypeFromYson)__")); + } + }; + + TWordCountHive elementStat = {{TString("Write!"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write!"]); + + settings.DefaultCluster = "plato"; + settings.BindingsMode = NSQLTranslation::EBindingsMode::DISABLED; + res = SqlToYqlWithSettings( + "insert into bindings.foo with truncate (x, y) values (1, 2);", + settings + ); + UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:1:13: Error: Please remove 'bindings.' from your query, the support for this syntax has ended, code: 4601\n"); + UNIT_ASSERT(!res.Root); + + settings.BindingsMode = NSQLTranslation::EBindingsMode::DROP; + res = SqlToYqlWithSettings( + "insert into bindings.foo with truncate (x, y) values (1, 2);", + settings + ); + UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), ""); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine2 = [](const TString& word, const TString& line) { + if (word == "Write!") { + //UNIT_ASSERT_VALUES_EQUAL(line, ""); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__((Write! world sink (Key '('table (String '"foo"))) values '('('mode 'renew)))__")); + } + }; + + TWordCountHive elementStat2 = {{TString("Write!"), 0}}; + VerifyProgram(res, elementStat2, verifyLine2); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat2["Write!"]); + + settings.BindingsMode = NSQLTranslation::EBindingsMode::DROP_WITH_WARNING; + res = SqlToYqlWithSettings( + "insert into bindings.foo with truncate (x, y) values (1, 2);", + settings + ); + UNIT_ASSERT_VALUES_EQUAL(Err2Str(res), "<main>:1:13: Warning: Please remove 'bindings.' from your query, the support for this syntax will be dropped soon, code: 4538\n"); + UNIT_ASSERT(res.Root); + + TWordCountHive elementStat3 = {{TString("Write!"), 0}}; + VerifyProgram(res, elementStat3, verifyLine2); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat3["Write!"]); + } + + Y_UNIT_TEST(TrailingCommaInWithout) { + UNIT_ASSERT(SqlToYql("SELECT * WITHOUT stream, FROM plato.Input").IsOk()); + UNIT_ASSERT(SqlToYql("SELECT a.* WITHOUT a.intersect, FROM plato.Input AS a").IsOk()); + UNIT_ASSERT(SqlToYql("SELECT a.* WITHOUT col1, col2, a.col3, FROM plato.Input AS a").IsOk()); + } + + Y_UNIT_TEST(NoStackOverflowOnBigCaseStatement) { + TStringBuilder req; + req << "select case 1 + 123"; + for (size_t i = 0; i < 20000; ++i) { + req << " when " << i << " then " << i + 1; + } + req << " else 100500 end;"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(CollectPreaggregatedInListLiteral) { + UNIT_ASSERT(SqlToYql("SELECT [COUNT(DISTINCT a+b)] FROM plato.Input").IsOk()); + } + + Y_UNIT_TEST(SmartParenInGroupByClause) { + UNIT_ASSERT(SqlToYql("SELECT * FROM plato.Input GROUP BY (k, v)").IsOk()); + } + + Y_UNIT_TEST(AlterTableRenameToIsCorrect) { + UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table RENAME TO moved").IsOk()); + } + + Y_UNIT_TEST(AlterTableAddDropColumnIsCorrect) { + UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table ADD COLUMN addc uint64, DROP COLUMN dropc, ADD addagain uint64").IsOk()); + } + + Y_UNIT_TEST(AlterTableSetTTLIsCorrect) { + UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table SET (TTL = Interval(\"PT3H\") ON column)").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table SET (TTL = Interval(\"PT3H\") ON column AS SECONDS)").IsOk()); + } + + Y_UNIT_TEST(AlterTableSetTieringIsCorrect) { + UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table SET (TIERING = 'my_tiering')").IsOk()); + } + + Y_UNIT_TEST(AlterTableAddChangefeedIsCorrect) { + UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table ADD CHANGEFEED feed WITH (MODE = 'UPDATES', FORMAT = 'json')").IsOk()); + } + + Y_UNIT_TEST(AlterTableAlterChangefeedIsCorrect) { + UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table ALTER CHANGEFEED feed DISABLE").IsOk()); + ExpectFailWithError("USE plato; ALTER TABLE table ALTER CHANGEFEED feed SET (FORMAT = 'proto');", + "<main>:1:57: Error: FORMAT alter is not supported\n"); + } + + Y_UNIT_TEST(AlterTableDropChangefeedIsCorrect) { + UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table DROP CHANGEFEED feed").IsOk()); + } + + Y_UNIT_TEST(AlterTableSetPartitioningIsCorrect) { + UNIT_ASSERT(SqlToYql("USE plato; ALTER TABLE table SET (AUTO_PARTITIONING_BY_SIZE = DISABLED)").IsOk()); + } + + Y_UNIT_TEST(AlterTableAddIndexWithIsNotSupported) { +#if ANTLR_VER == 3 + ExpectFailWithFuzzyError("USE plato; ALTER TABLE table ADD INDEX idx GLOBAL ON (col) WITH (a=b)", + "<main>:1:40: Error: with: alternative is not implemented yet: \\d+:\\d+: global_index\\n"); +#else + ExpectFailWithError("USE plato; ALTER TABLE table ADD INDEX idx GLOBAL ON (col) WITH (a=b)", + "<main>:1:40: Error: with: alternative is not implemented yet: \n"); +#endif + } + + Y_UNIT_TEST(AlterTableAddIndexLocalIsNotSupported) { +#if ANTLR_VER == 3 + ExpectFailWithFuzzyError("USE plato; ALTER TABLE table ADD INDEX idx LOCAL ON (col)", + "<main>:1:40: Error: local: alternative is not implemented yet: \\d+:\\d+: local_index\\n"); +#else + ExpectFailWithError("USE plato; ALTER TABLE table ADD INDEX idx LOCAL ON (col)", + "<main>:1:40: Error: local: alternative is not implemented yet: \n"); +#endif + } + + Y_UNIT_TEST(CreateTableAddIndexVector) { + const auto result = SqlToYql(R"(USE plato; + CREATE TABLE table ( + pk INT32 NOT NULL, + col String, + INDEX idx GLOBAL USING vector_kmeans_tree + ON (col) COVER (col) + WITH (distance=cosine, vector_type=float, vector_dimension=1024,), + PRIMARY KEY (pk)) + )"); + UNIT_ASSERT_C(result.IsOk(), result.Issues.ToString()); + } + + Y_UNIT_TEST(AlterTableAddIndexVector) { + const auto result = SqlToYql(R"(USE plato; + ALTER TABLE table ADD INDEX idx + GLOBAL USING vector_kmeans_tree + ON (col) COVER (col) + WITH (distance=cosine, vector_type="float", vector_dimension=1024) + )"); + UNIT_ASSERT_C(result.IsOk(), result.Issues.ToString()); + } + + Y_UNIT_TEST(AlterTableAddIndexUnknownSubtype) { + ExpectFailWithError("USE plato; ALTER TABLE table ADD INDEX idx GLOBAL USING unknown ON (col)", + "<main>:1:57: Error: UNKNOWN index subtype is not supported\n"); + } + + Y_UNIT_TEST(AlterTableAddIndexMissedParameter) { + ExpectFailWithError(R"(USE plato; + ALTER TABLE table ADD INDEX idx + GLOBAL USING vector_kmeans_tree + ON (col) + WITH (distance=cosine, vector_type=float) + )", + "<main>:5:52: Error: vector_dimension should be set\n"); + } + + Y_UNIT_TEST(AlterTableAlterIndexSetPartitioningIsCorrect) { + const auto result = SqlToYql("USE plato; ALTER TABLE table ALTER INDEX index SET AUTO_PARTITIONING_MIN_PARTITIONS_COUNT 10"); + UNIT_ASSERT_C(result.IsOk(), result.Issues.ToString()); + } + + Y_UNIT_TEST(AlterTableAlterIndexSetMultiplePartitioningSettings) { + const auto result = SqlToYql("USE plato; ALTER TABLE table ALTER INDEX index SET " + "(AUTO_PARTITIONING_BY_LOAD = ENABLED, AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = 10)" + ); + UNIT_ASSERT_C(result.IsOk(), result.Issues.ToString()); + } + + Y_UNIT_TEST(AlterTableAlterIndexResetPartitioningIsNotSupported) { + ExpectFailWithError("USE plato; ALTER TABLE table ALTER INDEX index RESET (AUTO_PARTITIONING_MIN_PARTITIONS_COUNT)", + "<main>:1:55: Error: AUTO_PARTITIONING_MIN_PARTITIONS_COUNT reset is not supported\n" + ); + } + + Y_UNIT_TEST(AlterTableAlterColumnDropNotNullAstCorrect) { + auto reqSetNull = SqlToYql(R"( + USE plato; + CREATE TABLE tableName ( + id Uint32, + val Uint32 NOT NULL, + PRIMARY KEY (id) + ); + + COMMIT; + ALTER TABLE tableName ALTER COLUMN val DROP NOT NULL; + )"); + + UNIT_ASSERT(reqSetNull.IsOk()); + UNIT_ASSERT(reqSetNull.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + Y_UNUSED(word); + + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find( + R"(let world (Write! world sink (Key '('tablescheme (String '"tableName"))) (Void) '('('mode 'alter) '('actions '('('alterColumns '('('"val" '('changeColumnConstraints '('('drop_not_null)))))))))))" + )); + }; + + TWordCountHive elementStat({TString("\'mode \'alter")}); + VerifyProgram(reqSetNull, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["\'mode \'alter"]); + } + + Y_UNIT_TEST(AlterSequence) { + UNIT_ASSERT(SqlToYql(R"( + USE plato; + ALTER SEQUENCE sequence START WITH 10 INCREMENT 2 RESTART WITH 5; + )").IsOk()); + UNIT_ASSERT(SqlToYql(R"( + USE plato; + ALTER SEQUENCE sequence INCREMENT 2; + )").IsOk()); + UNIT_ASSERT(SqlToYql(R"( + USE plato; + ALTER SEQUENCE sequence INCREMENT 2 START 1000; + )").IsOk()); + UNIT_ASSERT(SqlToYql(R"( + USE plato; + ALTER SEQUENCE sequence RESTART START 1000; + )").IsOk()); + UNIT_ASSERT(SqlToYql(R"( + USE plato; + ALTER SEQUENCE IF EXISTS sequence INCREMENT 1000 START 100 RESTART; + )").IsOk()); + UNIT_ASSERT(SqlToYql(R"( + USE plato; + ALTER SEQUENCE IF EXISTS sequence RESTART 1000 START WITH 100 INCREMENT BY 7; + )").IsOk()); + } + + Y_UNIT_TEST(AlterSequenceIncorrect) { + { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence START WITH 10 INCREMENT 2 RESTART WITH 5 RESTART;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:75: Error: Restart value defined more than once\n"); + } + { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence START WITH 10 INCREMENT 2 START 100 RESTART WITH 5;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:60: Error: Start value defined more than once\n"); + } + { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence INCREMENT BY 7 START WITH 10 INCREMENT 2 RESTART WITH 5 RESTART;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:62: Error: Increment defined more than once\n"); + } + { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 100 START WITH 10 INCREMENT 2 RESTART WITH 5;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:77: Error: Restart value defined more than once\n"); + } + { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1234234543563435151456 START WITH 10 INCREMENT 2;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:49: Error: Failed to parse number from string: 1234234543563435151456, number limit overflow\n"); + } + { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1 START WITH 9223372036854775817 INCREMENT 4;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Start value: 9223372036854775817 cannot be greater than max value: 9223372036854775807\n"); + } + { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 9223372036854775827 START WITH 5 INCREMENT 4;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Restart value: 9223372036854775827 cannot be greater than max value: 9223372036854775807\n"); + } + { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1 START WITH 4 INCREMENT 0;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Increment must not be zero\n"); + } + { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 0 START WITH 4 INCREMENT 1;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Restart value: 0 cannot be less than min value: 1\n"); + } + { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1 START WITH 0 INCREMENT 1;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Start value: 0 cannot be less than min value: 1\n"); + } + { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence RESTART WITH 1 START WITH 1 INCREMENT 9223372036854775837;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Increment: 9223372036854775837 cannot be greater than max value: 9223372036854775807\n"); + } + } + + Y_UNIT_TEST(AlterSequenceCorrect) { + { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE sequence START WITH 10 INCREMENT 2 RESTART WITH 5;"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("sequence")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter")); + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("alter_if_exists")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("start")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("increment")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("restart")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE IF EXISTS sequence INCREMENT 2 RESTART;"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("sequence")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter_if_exists")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("increment")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("restart")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + { + NYql::TAstParseResult res = SqlToYql("USE plato; ALTER SEQUENCE IF EXISTS sequence START 10 INCREMENT BY 2;"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("sequence")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter_if_exists")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("start")); + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("restart")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("increment")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + } + + Y_UNIT_TEST(ShowCreateTable) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; + SHOW CREATE TABLE user; + )"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Read") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("showCreateTable")); + } + }; + + TWordCountHive elementStat = {{TString("Read"), 0}, {TString("showCreateTable"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Read"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["showCreateTable"]); + } + + Y_UNIT_TEST(OptionalAliases) { + UNIT_ASSERT(SqlToYql("USE plato; SELECT foo FROM (SELECT key foo FROM Input);").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT a.x FROM Input1 a JOIN Input2 b ON a.key = b.key;").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; SELECT a.x FROM (VALUES (1,2), (3,4)) a(x,key) JOIN Input b ON a.key = b.key;").IsOk()); + } + + Y_UNIT_TEST(TableNameConstness) { + UNIT_ASSERT(SqlToYql("USE plato; $path = 'foo'; SELECT TableName($path), count(*) FROM Input;").IsOk()); + UNIT_ASSERT(SqlToYql("$path = 'foo'; SELECT TableName($path, 'yt'), count(*) FROM plato.Input;").IsOk()); + ExpectFailWithError("USE plato; SELECT TableName(), count(*) FROM plato.Input;", + "<main>:1:19: Error: Expression has to be an aggregation function or key column, because aggregation is used elsewhere in this subquery\n"); + } + + Y_UNIT_TEST(UseShouldWorkAsColumnName) { + UNIT_ASSERT(SqlToYql("select use from (select 1 as use);").IsOk()); + } + + Y_UNIT_TEST(TrueFalseWorkAfterDollar) { + UNIT_ASSERT(SqlToYql("$ true = false; SELECT $ true or false;").IsOk()); + UNIT_ASSERT(SqlToYql("$False = 0; SELECT $False;").IsOk()); + } + + Y_UNIT_TEST(WithSchemaEquals) { + UNIT_ASSERT(SqlToYql("select * from plato.T with schema Struct<a:Int32, b:String>;").IsOk()); + UNIT_ASSERT(SqlToYql("select * from plato.T with columns = Struct<a:Int32, b:String>;").IsOk()); + } + + Y_UNIT_TEST(WithNonStructSchemaS3) { + NSQLTranslation::TTranslationSettings settings; + settings.ClusterMapping["s3bucket"] = NYql::S3ProviderName; + UNIT_ASSERT(SqlToYqlWithSettings("select * from s3bucket.`foo` with schema (col1 Int32, String as col2, Int64 as col3);", settings).IsOk()); + } + + Y_UNIT_TEST(AllowNestedTuplesInGroupBy) { + NYql::TAstParseResult res = SqlToYql("select count(*) from plato.Input group by 1 + (x, y, z);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + Y_UNUSED(word); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Aggregate core '('\"group0\")")); + }; + + TWordCountHive elementStat({"Aggregate"}); + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT(elementStat["Aggregate"] == 1); + } + + Y_UNIT_TEST(AllowGroupByWithParens) { + NYql::TAstParseResult res = SqlToYql("select count(*) from plato.Input group by (x, y as alias1, z);"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + Y_UNUSED(word); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Aggregate core '('\"x\" '\"alias1\" '\"z\")")); + }; + + TWordCountHive elementStat({"Aggregate"}); + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT(elementStat["Aggregate"] == 1); + } + + Y_UNIT_TEST(CreateAsyncReplicationParseCorrect) { + auto req = R"( + USE plato; + CREATE ASYNC REPLICATION MyReplication + FOR table1 AS table2, table3 AS table4 + WITH ( + CONNECTION_STRING = "grpc://localhost:2135/?database=/MyDatabase", + ENDPOINT = "localhost:2135", + DATABASE = "/MyDatabase" + ); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("MyReplication")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("create")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table1")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table2")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table3")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("table4")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("connection_string")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("grpc://localhost:2135/?database=/MyDatabase")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("endpoint")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("localhost:2135")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("database")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("/MyDatabase")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateAsyncReplicationUnsupportedSettings) { + auto reqTpl = R"( + USE plato; + CREATE ASYNC REPLICATION MyReplication + FOR table1 AS table2, table3 AS table4 + WITH ( + %s = "%s" + ) + )"; + + auto settings = THashMap<TString, TString>{ + {"STATE", "DONE"}, + {"FAILOVER_MODE", "FORCE"}, + }; + + for (const auto& [k, v] : settings) { + auto req = Sprintf(reqTpl, k.c_str(), v.c_str()); + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), Sprintf("<main>:6:%zu: Error: %s is not supported in CREATE\n", 20 + k.size(), k.c_str())); + } + } + + Y_UNIT_TEST(AsyncReplicationInvalidCommitInterval) { + auto req = R"( + USE plato; + CREATE ASYNC REPLICATION MyReplication + FOR table1 AS table2, table3 AS table4 + WITH ( + COMMIT_INTERVAL = "FOO" + ); + )"; + + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:6:35: Error: Literal of Interval type is expected for COMMIT_INTERVAL\n"); + } + + Y_UNIT_TEST(AlterAsyncReplicationParseCorrect) { + auto req = R"( + USE plato; + ALTER ASYNC REPLICATION MyReplication + SET ( + STATE = "DONE", + FAILOVER_MODE = "FORCE" + ); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("MyReplication")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("state")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("DONE")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("failover_mode")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("FORCE")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(AlterAsyncReplicationSettings) { + auto reqTpl = R"( + USE plato; + ALTER ASYNC REPLICATION MyReplication + SET ( + %s = "%s" + ) + )"; + + auto settings = THashMap<TString, TString>{ + {"connection_string", "grpc://localhost:2135/?database=/MyDatabase"}, + {"endpoint", "localhost:2135"}, + {"database", "/MyDatabase"}, + {"token", "foo"}, + {"token_secret_name", "foo_secret_name"}, + {"user", "user"}, + {"password", "bar"}, + {"password_secret_name", "bar_secret_name"}, + }; + + for (const auto& [k, v] : settings) { + auto req = Sprintf(reqTpl, k.c_str(), v.c_str()); + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [&k, &v](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("MyReplication")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alter")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(k)); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(v)); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + } + + Y_UNIT_TEST(AlterAsyncReplicationUnsupportedSettings) { + { + auto req = R"( + USE plato; + ALTER ASYNC REPLICATION MyReplication SET (CONSISTENCY_LEVEL = "GLOBAL"); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:80: Error: CONSISTENCY_LEVEL is not supported in ALTER\n"); + } + { + auto req = R"( + USE plato; + ALTER ASYNC REPLICATION MyReplication SET (COMMIT_INTERVAL = Interval("PT10S")); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:87: Error: COMMIT_INTERVAL is not supported in ALTER\n"); + } + } + + Y_UNIT_TEST(AsyncReplicationInvalidSettings) { + auto req = R"( + USE plato; + ALTER ASYNC REPLICATION MyReplication SET (FOO = "BAR"); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:62: Error: Unknown replication setting: FOO\n"); + } + + Y_UNIT_TEST(DropAsyncReplicationParseCorrect) { + auto req = R"( + USE plato; + DROP ASYNC REPLICATION MyReplication; + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("MyReplication")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("drop")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DropAsyncReplicationCascade) { + auto req = R"( + USE plato; + DROP ASYNC REPLICATION MyReplication CASCADE; + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropCascade")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(PragmaCompactGroupBy) { + auto req = "PRAGMA CompactGroupBy; SELECT key, COUNT(*) FROM plato.Input GROUP BY key;"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Aggregate") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'('compact)")); + } + }; + + TWordCountHive elementStat = { {TString("Aggregate"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Aggregate"]); + } + + Y_UNIT_TEST(PragmaDisableCompactGroupBy) { + auto req = "PRAGMA DisableCompactGroupBy; SELECT key, COUNT(*) FROM plato.Input GROUP /*+ compact() */ BY key;"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Aggregate") { + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'('compact)")); + } + }; + + TWordCountHive elementStat = { {TString("Aggregate"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Aggregate"]); + } + + Y_UNIT_TEST(AutoSampleWorksWithNamedSubquery) { + UNIT_ASSERT(SqlToYql("$src = select * from plato.Input; select * from $src sample 0.2").IsOk()); + } + + Y_UNIT_TEST(AutoSampleWorksWithSubquery) { + UNIT_ASSERT(SqlToYql("select * from (select * from plato.Input) sample 0.2").IsOk()); + } + + Y_UNIT_TEST(CreateTableTrailingComma) { + UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE tableName (Key Uint32, PRIMARY KEY (Key),);").IsOk()); + UNIT_ASSERT(SqlToYql("USE plato; CREATE TABLE tableName (Key Uint32,);").IsOk()); + } + + Y_UNIT_TEST(BetweenSymmetric) { + UNIT_ASSERT(SqlToYql("select 3 between symmetric 5 and 4;").IsOk()); + UNIT_ASSERT(SqlToYql("select 3 between asymmetric 5 and 4;").IsOk()); + UNIT_ASSERT(SqlToYql("use plato; select key between symmetric and and and from Input;").IsOk()); + UNIT_ASSERT(SqlToYql("use plato; select key between and and and from Input;").IsOk()); + } +} + +Y_UNIT_TEST_SUITE(ExternalFunction) { + Y_UNIT_TEST(ValidUseFunctions) { + + UNIT_ASSERT(SqlToYql( + "PROCESS plato.Input" + " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo', <|a: 123, b: a + 641|>)" + " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>," + " CONCURRENCY=3, OPTIMIZE_FOR='CALLS'").IsOk()); + + // use CALLS without quotes, as keyword + UNIT_ASSERT(SqlToYql( + "PROCESS plato.Input" + " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo')" + " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>," + " OPTIMIZE_FOR=CALLS").IsOk()); + + UNIT_ASSERT(SqlToYql( + "PROCESS plato.Input" + " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo', TableRow())" + " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>," + " CONCURRENCY=3").IsOk()); + + UNIT_ASSERT(SqlToYql( + "PROCESS plato.Input" + " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo')" + " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>," + " CONCURRENCY=3, BATCH_SIZE=1000000, CONNECTION='yc-folder34fse-con'," + " INIT=[0, 900]").IsOk()); + + UNIT_ASSERT(SqlToYql( + "PROCESS plato.Input" + " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'bar', TableRow())" + " WITH UNKNOWN_PARAM_1='837747712', UNKNOWN_PARAM_2=Tuple<Uint16, Utf8>," + " INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>").IsOk()); + } + + + Y_UNIT_TEST(InValidUseFunctions) { + ExpectFailWithError("PROCESS plato.Input USING some::udf(*) WITH INPUT_TYPE=Struct<a:Int32>", + "<main>:1:33: Error: PROCESS without USING EXTERNAL FUNCTION doesn't allow WITH block\n"); + + ExpectFailWithError("PROCESS plato.Input USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'jhhjfh88134d')" + " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>" + " ASSUME ORDER BY key", + "<main>:1:129: Error: PROCESS with USING EXTERNAL FUNCTION doesn't allow ASSUME block\n"); + + ExpectFailWithError("PROCESS plato.Input USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo', 'bar', 'baz')", + "<main>:1:15: Error: EXTERNAL FUNCTION requires from 2 to 3 arguments, but got: 4\n"); + + ExpectFailWithError("PROCESS plato.Input\n" + " USING EXTERNAL FUNCTION('YANDEX-CLOUD', 'foo', <|field_1: a1, field_b: b1|>)\n" + " WITH INPUT_TYPE=Struct<a:Int32>, OUTPUT_TYPE=Struct<b:Int32>,\n" + " CONCURRENCY=3, BATCH_SIZE=1000000, CONNECTION='yc-folder34fse-con',\n" + " CONCURRENCY=5, INPUT_TYPE=Struct<b:Bool>,\n" + " INIT=[0, 900]\n", + "<main>:5:2: Error: WITH \"CONCURRENCY\" clause should be specified only once\n" + "<main>:5:17: Error: WITH \"INPUT_TYPE\" clause should be specified only once\n"); + } +} + +Y_UNIT_TEST_SUITE(SqlToYQLErrors) { + Y_UNIT_TEST(UdfSyntaxSugarMissingCall) { + auto req = "SELECT Udf(DateTime::FromString, \"foo\" as RunConfig);"; + auto res = SqlToYql(req); + TString a1 = Err2Str(res); + TString a2("<main>:1:8: Error: Abstract Udf Node can't be used as a part of expression.\n"); + UNIT_ASSERT_NO_DIFF(a1, a2); + } + + Y_UNIT_TEST(UdfSyntaxSugarIsNotCallable) { + auto req = "SELECT Udf(123, \"foo\" as RunConfig);"; + auto res = SqlToYql(req); + TString a1 = Err2Str(res); + TString a2("<main>:1:8: Error: Udf: first argument must be a callable, like Foo::Bar\n"); + UNIT_ASSERT_NO_DIFF(a1, a2); + } + + Y_UNIT_TEST(UdfSyntaxSugarNoArgs) { + auto req = "SELECT Udf()();"; + auto res = SqlToYql(req); + TString a1 = Err2Str(res); + TString a2("<main>:1:8: Error: Udf: expected at least one argument\n"); + UNIT_ASSERT_NO_DIFF(a1, a2); + } + + Y_UNIT_TEST(StrayUTF8) { + /// 'c' in plato is russian here + NYql::TAstParseResult res = SqlToYql("select * from сedar.Input"); + UNIT_ASSERT(!res.Root); + + TString a1 = Err2Str(res); +#if ANTLR_VER == 3 + TString a2(R"foo(<main>:1:14: Error: Unexpected character 'с' (Unicode character <1089>) : cannot match to any predicted input... + +<main>:1:15: Error: Unexpected character : cannot match to any predicted input... + +)foo"); +#else + TString a2(R"foo(<main>:1:14: Error: token recognition error at: 'с' +)foo"); +#endif + UNIT_ASSERT_NO_DIFF(a1, a2); + } + + Y_UNIT_TEST(IvalidStringLiteralWithEscapedBackslash) { + NYql::TAstParseResult res1 = SqlToYql(R"foo($bar = 'a\\'b';)foo"); + NYql::TAstParseResult res2 = SqlToYql(R"foo($bar = "a\\"b";)foo"); + UNIT_ASSERT(!res1.Root); + UNIT_ASSERT(!res2.Root); + +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res1), "<main>:1:15: Error: Unexpected character : syntax error...\n\n"); + UNIT_ASSERT_NO_DIFF(Err2Str(res2), "<main>:1:15: Error: Unexpected character : syntax error...\n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res1), "<main>:1:13: Error: token recognition error at: '';'\n"); + UNIT_ASSERT_NO_DIFF(Err2Str(res2), "<main>:1:13: Error: token recognition error at: '\";'\n"); +#endif + } + + Y_UNIT_TEST(InvalidHexInStringLiteral) { + NYql::TAstParseResult res = SqlToYql("select \"foo\\x1\\xfe\""); + UNIT_ASSERT(!res.Root); + TString a1 = Err2Str(res); + TString a2 = "<main>:1:15: Error: Failed to parse string literal: Invalid hexadecimal value\n"; + + UNIT_ASSERT_NO_DIFF(a1, a2); + } + + Y_UNIT_TEST(InvalidOctalInMultilineStringLiteral) { + NYql::TAstParseResult res = SqlToYql("select \"foo\n" + "bar\n" + "\\01\""); + UNIT_ASSERT(!res.Root); + TString a1 = Err2Str(res); + TString a2 = "<main>:3:4: Error: Failed to parse string literal: Invalid octal value\n"; + + UNIT_ASSERT_NO_DIFF(a1, a2); + } + + Y_UNIT_TEST(InvalidDoubleAtString) { + NYql::TAstParseResult res = SqlToYql("select @@@@@@"); + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:13: Error: Unexpected character : syntax error...\n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: extraneous input '@' expecting {<EOF>, ';'}\n"); +#endif + } + + Y_UNIT_TEST(InvalidDoubleAtStringWhichWasAcceptedEarlier) { + NYql::TAstParseResult res = SqlToYql("SELECT @@foo@@ @ @@bar@@"); + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:7: Error: Unexpected token '@@foo@@' : cannot match to any predicted input...\n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:15: Error: mismatched input '@' expecting {<EOF>, ';'}\n"); +#endif + } + + Y_UNIT_TEST(InvalidStringFromTable) { + NYql::TAstParseResult res = SqlToYql("select \"FOO\"\"BAR from plato.foo"); + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:31: Error: Unexpected character : syntax error...\n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: token recognition error at: '\"BAR from plato.foo'\n"); +#endif + } + + Y_UNIT_TEST(InvalidDoubleAtStringFromTable) { + NYql::TAstParseResult res = SqlToYql("select @@@@@@ from plato.foo"); + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:28: Error: Unexpected character : syntax error...\n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: mismatched input '@' expecting {<EOF>, ';'}\n"); +#endif + } + + Y_UNIT_TEST(SelectInvalidSyntax) { + NYql::TAstParseResult res = SqlToYql("select 1 form Wat"); + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:14: Error: Unexpected token 'Wat' : cannot match to any predicted input...\n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:14: Error: extraneous input 'Wat' expecting {<EOF>, ';'}\n"); +#endif + } + + Y_UNIT_TEST(SelectNoCluster) { + NYql::TAstParseResult res = SqlToYql("select foo from bar"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: No cluster name given and no default cluster is selected\n"); + } + + Y_UNIT_TEST(SelectDuplicateColumns) { + NYql::TAstParseResult res = SqlToYql("select a, a from plato.Input"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:11: Error: Unable to use duplicate column names. Collision in name: a\n"); + } + + Y_UNIT_TEST(SelectDuplicateLabels) { + NYql::TAstParseResult res = SqlToYql("select a as foo, b as foo from plato.Input"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:18: Error: Unable to use duplicate column names. Collision in name: foo\n"); + } + + Y_UNIT_TEST(SelectCaseWithoutThen) { + NYql::TAstParseResult res = SqlToYql("select case when true 1;"); + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), + "<main>:1:22: Error: Unexpected token absence : Missing THEN \n\n" + "<main>:1:23: Error: Unexpected token absence : Missing END \n\n" + ); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), + "<main>:1:22: Error: missing THEN at \'1\'\n" + "<main>:1:23: Error: extraneous input \';\' expecting {ELSE, END, WHEN}\n" + ); +#endif + } + + Y_UNIT_TEST(SelectComplexCaseWithoutThen) { + NYql::TAstParseResult res = SqlToYql( + "SELECT *\n" + "FROM plato.Input AS a\n" + "WHERE CASE WHEN a.key = \"foo\" a.subkey ELSE a.value END\n" + ); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:30: Error: Unexpected token absence : Missing THEN \n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:30: Error: missing THEN at 'a'\n"); +#endif + } + + Y_UNIT_TEST(SelectCaseWithoutEnd) { + NYql::TAstParseResult res = SqlToYql("select case a when b then c end from plato.Input"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: ELSE is required\n"); + } + + Y_UNIT_TEST(SelectWithBadAggregationNoInput) { + NYql::TAstParseResult res = SqlToYql("select a, Min(b), c"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), + "<main>:1:1: Error: Column references are not allowed without FROM\n" + "<main>:1:8: Error: Column reference 'a'\n" + "<main>:1:1: Error: Column references are not allowed without FROM\n" + "<main>:1:15: Error: Column reference 'b'\n" + "<main>:1:1: Error: Column references are not allowed without FROM\n" + "<main>:1:19: Error: Column reference 'c'\n" + ); + } + + Y_UNIT_TEST(SelectWithBadAggregation) { + ExpectFailWithError("select count(*), 1 + key from plato.Input", + "<main>:1:22: Error: Column `key` must either be a key column in GROUP BY or it should be used in aggregation function\n"); + } + + Y_UNIT_TEST(SelectWithBadAggregatedTerms) { + ExpectFailWithError("select key, 2 * subkey from plato.Input group by key", + "<main>:1:17: Error: Column `subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); + } + + Y_UNIT_TEST(SelectDistinctWithBadAggregation) { + ExpectFailWithError("select distinct count(*), 1 + key from plato.Input", + "<main>:1:31: Error: Column `key` must either be a key column in GROUP BY or it should be used in aggregation function\n"); + ExpectFailWithError("select distinct key, 2 * subkey from plato.Input group by key", + "<main>:1:26: Error: Column `subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); + } + + Y_UNIT_TEST(SelectWithBadAggregationInHaving) { + ExpectFailWithError("select key from plato.Input group by key\n" + "having \"f\" || value == \"foo\"", + "<main>:2:15: Error: Column `value` must either be a key column in GROUP BY or it should be used in aggregation function\n"); + } + + Y_UNIT_TEST(JoinWithNonAggregatedColumnInProjection) { + ExpectFailWithError("select a.key, 1 + b.subkey\n" + "from plato.Input1 as a join plato.Input2 as b using(key)\n" + "group by a.key;", + "<main>:1:19: Error: Column `b.subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); + + ExpectFailWithError("select a.key, 1 + b.subkey.x\n" + "from plato.Input1 as a join plato.Input2 as b using(key)\n" + "group by a.key;", + "<main>:1:19: Error: Column must either be a key column in GROUP BY or it should be used in aggregation function\n"); + } + + Y_UNIT_TEST(SelectWithBadAggregatedTermsWithSources) { + ExpectFailWithError("select key, 1 + a.subkey\n" + "from plato.Input1 as a\n" + "group by a.key;", + "<main>:1:17: Error: Column `a.subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); + ExpectFailWithError("select key, 1 + a.subkey.x\n" + "from plato.Input1 as a\n" + "group by a.key;", + "<main>:1:17: Error: Column must either be a key column in GROUP BY or it should be used in aggregation function\n"); + } + + Y_UNIT_TEST(WarnForAggregationBySelectAlias) { + NYql::TAstParseResult res = SqlToYql("select c + 1 as c from plato.Input\n" + "group by c"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), + "<main>:2:11: Warning: GROUP BY will aggregate by column `c` instead of aggregating by SELECT expression with same alias, code: 4532\n" + "<main>:1:10: Warning: You should probably use alias in GROUP BY instead of using it here. Please consult documentation for more details, code: 4532\n"); + + res = SqlToYql("select c + 1 as c from plato.Input\n" + "group by Math::Floor(c + 2) as c;"); + + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), + "<main>:2:22: Warning: GROUP BY will aggregate by column `c` instead of aggregating by SELECT expression with same alias, code: 4532\n" + "<main>:1:10: Warning: You should probably use alias in GROUP BY instead of using it here. Please consult documentation for more details, code: 4532\n"); + } + + Y_UNIT_TEST(NoWarnForAggregationBySelectAliasWhenAggrFunctionsAreUsedInAlias) { + NYql::TAstParseResult res = SqlToYql("select\n" + " cast(avg(val) as int) as value,\n" + " value as key\n" + "from\n" + " plato.Input\n" + "group by value"); + + UNIT_ASSERT(res.Root); + UNIT_ASSERT(res.Issues.Size() == 0); + + res = SqlToYql("select\n" + " cast(avg(val) over w as int) as value,\n" + " value as key\n" + "from\n" + " plato.Input\n" + "group by value\n" + "window w as ()"); + + UNIT_ASSERT(res.Root); + UNIT_ASSERT(res.Issues.Size() == 0); + } + + Y_UNIT_TEST(NoWarnForAggregationBySelectAliasWhenQualifiedNameIsUsed) { + NYql::TAstParseResult res = SqlToYql("select\n" + " Unwrap(a.key) as key\n" + "from plato.Input as a\n" + "join plato.Input2 as b using(k)\n" + "group by a.key;"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT(res.Issues.Size() == 0); + + res = SqlToYql("select Unwrap(a.key) as key\n" + "from plato.Input as a\n" + "group by a.key;"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT(res.Issues.Size() == 0); + } + + Y_UNIT_TEST(NoWarnForAggregationBySelectAliasWhenTrivialRenamingIsUsed) { + NYql::TAstParseResult res = SqlToYql("select a.key as key\n" + "from plato.Input as a\n" + "group by key;"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT(res.Issues.Size() == 0); + + res = SqlToYql("select key as key\n" + "from plato.Input\n" + "group by key;"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT(res.Issues.Size() == 0); + } + + Y_UNIT_TEST(ErrorByAggregatingByExpressionWithSameExpressionInSelect) { + ExpectFailWithError("select k * 2 from plato.Input group by k * 2", + "<main>:1:8: Error: Column `k` must either be a key column in GROUP BY or it should be used in aggregation function\n"); + } + + Y_UNIT_TEST(ErrorForAggregationBySelectAlias) { + ExpectFailWithError("select key, Math::Floor(1.1 + a.subkey) as foo\n" + "from plato.Input as a\n" + "group by a.key, foo;", + "<main>:3:17: Warning: GROUP BY will aggregate by column `foo` instead of aggregating by SELECT expression with same alias, code: 4532\n" + "<main>:1:19: Warning: You should probably use alias in GROUP BY instead of using it here. Please consult documentation for more details, code: 4532\n" + "<main>:1:31: Error: Column `a.subkey` must either be a key column in GROUP BY or it should be used in aggregation function\n"); + + ExpectFailWithError("select c + 1 as c from plato.Input\n" + "group by Math::Floor(c + 2);", + "<main>:2:22: Warning: GROUP BY will aggregate by column `c` instead of aggregating by SELECT expression with same alias, code: 4532\n" + "<main>:1:10: Warning: You should probably use alias in GROUP BY instead of using it here. Please consult documentation for more details, code: 4532\n" + "<main>:1:8: Error: Column `c` must either be a key column in GROUP BY or it should be used in aggregation function\n"); + } + + Y_UNIT_TEST(SelectWithDuplicateGroupingColumns) { + NYql::TAstParseResult res = SqlToYql("select c from plato.Input group by c, c"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Duplicate grouping column: c\n"); + } + + Y_UNIT_TEST(SelectWithBadAggregationInGrouping) { + NYql::TAstParseResult res = SqlToYql("select a, Min(b), c group by c"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Column references are not allowed without FROM\n" + "<main>:1:30: Error: Column reference 'c'\n"); + } + + Y_UNIT_TEST(SelectWithOpOnBadAggregation) { + ExpectFailWithError("select 1 + a + Min(b) from plato.Input", + "<main>:1:12: Error: Column `a` must either be a key column in GROUP BY or it should be used in aggregation function\n"); + } + + Y_UNIT_TEST(SelectOrderByConstantNum) { + NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by 1"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:36: Error: Unable to ORDER BY constant expression\n"); + } + + Y_UNIT_TEST(SelectOrderByConstantExpr) { + NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by 1 * 42"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:38: Error: Unable to ORDER BY constant expression\n"); + } + + Y_UNIT_TEST(SelectOrderByConstantString) { + NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by \"nest\""); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:36: Error: Unable to ORDER BY constant expression\n"); + } + + Y_UNIT_TEST(SelectOrderByAggregated) { + NYql::TAstParseResult res = SqlToYql("select a from plato.Input order by min(a)"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:36: Error: Unable to ORDER BY aggregated values\n"); + } + + Y_UNIT_TEST(ErrorInOrderByExpresison) { + NYql::TAstParseResult res = SqlToYql("select key, value from plato.Input order by (key as zey)"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:45: Error: You should use in ORDER BY column name, qualified field, callable function or expression\n"); + } + + Y_UNIT_TEST(ErrorsInOrderByWhenColumnIsMissingInProjection) { + ExpectFailWithError("select subkey from (select 1 as subkey) order by key", "<main>:1:50: Error: Column key is not in source column set\n"); + ExpectFailWithError("select subkey from plato.Input as a order by x.key", "<main>:1:46: Error: Unknown correlation name: x\n"); + ExpectFailWithError("select distinct a, b from plato.Input order by c", "<main>:1:48: Error: Column c is not in source column set. Did you mean a?\n"); + ExpectFailWithError("select count(*) as a from plato.Input order by c", "<main>:1:48: Error: Column c is not in source column set. Did you mean a?\n"); + ExpectFailWithError("select count(*) as a, b, from plato.Input group by b order by c", "<main>:1:63: Error: Column c is not in source column set. Did you mean a?\n"); + UNIT_ASSERT(SqlToYql("select a, b from plato.Input order by c").IsOk()); + } + + Y_UNIT_TEST(SelectAggregatedWhere) { + NYql::TAstParseResult res = SqlToYql("select * from plato.Input where count(key)"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:33: Error: Can not use aggregated values in filtering\n"); + } + + Y_UNIT_TEST(DoubleFrom) { + NYql::TAstParseResult res = SqlToYql("from plato.Input select * from plato.Input"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:27: Error: Only one FROM clause is allowed\n"); + } + + Y_UNIT_TEST(SelectJoinMissingCorrName) { + NYql::TAstParseResult res = SqlToYql("select * from plato.Input1 as a join plato.Input2 as b on a.key == key"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:65: Error: JOIN: column requires correlation name\n"); + } + + Y_UNIT_TEST(SelectJoinMissingCorrName1) { + NYql::TAstParseResult res = SqlToYql( + "use plato;\n" + "$foo = select * from Input1;\n" + "select * from Input2 join $foo USING(key);\n" + ); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:27: Error: JOIN: missing correlation name for source\n"); + } + + Y_UNIT_TEST(SelectJoinMissingCorrName2) { + NYql::TAstParseResult res = SqlToYql( + "use plato;\n" + "$foo = select * from Input1;\n" + "select * from Input2 cross join $foo;\n" + ); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:33: Error: JOIN: missing correlation name for source\n"); + } + + Y_UNIT_TEST(SelectJoinEmptyCorrNames) { + NYql::TAstParseResult res = SqlToYql( + "$left = (SELECT * FROM plato.Input1 LIMIT 2);\n" + "$right = (SELECT * FROM plato.Input2 LIMIT 2);\n" + "SELECT * FROM $left FULL JOIN $right USING (key);\n" + ); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:45: Error: At least one correlation name is required in join\n"); + } + + Y_UNIT_TEST(SelectJoinSameCorrNames) { + NYql::TAstParseResult res = SqlToYql("SELECT Input.key FROM plato.Input JOIN plato.Input1 ON Input.key == Input.subkey\n"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:66: Error: JOIN: different correlation names are required for joined tables\n"); + } + + Y_UNIT_TEST(SelectJoinConstPredicateArg) { + NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input1 as A JOIN plato.Input2 as B ON A.key == B.key AND A.subkey == \"wtf\"\n"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:87: Error: JOIN: each equality predicate argument must depend on exactly one JOIN input\n"); + } + + Y_UNIT_TEST(SelectJoinNonEqualityPredicate) { + NYql::TAstParseResult res = SqlToYql("SELECT * FROM plato.Input1 as A JOIN plato.Input2 as B ON A.key == B.key AND A.subkey > B.subkey\n"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:87: Error: JOIN ON expression must be a conjunction of equality predicates\n"); + } + + Y_UNIT_TEST(SelectEquiJoinCorrNameOutOfScope) { + NYql::TAstParseResult res = SqlToYql( + "PRAGMA equijoin;\n" + "SELECT * FROM plato.A JOIN plato.B ON A.key == C.key JOIN plato.C ON A.subkey == C.subkey;\n" + ); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:45: Error: JOIN: can not use source: C in equality predicate, it is out of current join scope\n"); + } + + Y_UNIT_TEST(SelectEquiJoinNoRightSource) { + NYql::TAstParseResult res = SqlToYql( + "PRAGMA equijoin;\n" + "SELECT * FROM plato.A JOIN plato.B ON A.key == B.key JOIN plato.C ON A.subkey == B.subkey;\n" + ); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:79: Error: JOIN ON equality predicate must have one of its arguments from the rightmost source\n"); + } + + Y_UNIT_TEST(SelectEquiJoinOuterWithoutType) { + NYql::TAstParseResult res = SqlToYql( + "SELECT * FROM plato.A Outer JOIN plato.B ON A.key == B.key;\n" + ); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:23: Error: Invalid join type: OUTER JOIN. OUTER keyword is optional and can only be used after LEFT, RIGHT or FULL\n"); + } + + Y_UNIT_TEST(SelectEquiJoinOuterWithWrongType) { + NYql::TAstParseResult res = SqlToYql( + "SELECT * FROM plato.A LEFT semi OUTER JOIN plato.B ON A.key == B.key;\n" + ); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:33: Error: Invalid join type: LEFT SEMI OUTER JOIN. OUTER keyword is optional and can only be used after LEFT, RIGHT or FULL\n"); + } + + Y_UNIT_TEST(InsertNoCluster) { + NYql::TAstParseResult res = SqlToYql("insert into Output (foo) values (1)"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: No cluster name given and no default cluster is selected\n"); + } + + Y_UNIT_TEST(InsertValuesNoLabels) { + NYql::TAstParseResult res = SqlToYql("insert into plato.Output values (1)"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: INSERT INTO ... VALUES requires specification of table columns\n"); + } + + Y_UNIT_TEST(UpsertValuesNoLabelsKikimr) { + NYql::TAstParseResult res = SqlToYql("upsert into plato.Output values (1)", 10, TString(NYql::KikimrProviderName)); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: UPSERT INTO ... VALUES requires specification of table columns\n"); + } + + Y_UNIT_TEST(ReplaceValuesNoLabelsKikimr) { + NYql::TAstParseResult res = SqlToYql("replace into plato.Output values (1)", 10, TString(NYql::KikimrProviderName)); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:20: Error: REPLACE INTO ... VALUES requires specification of table columns\n"); + } + + Y_UNIT_TEST(InsertValuesInvalidLabels) { + NYql::TAstParseResult res = SqlToYql("insert into plato.Output (foo) values (1, 2)"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:27: Error: VALUES have 2 columns, INSERT INTO expects: 1\n"); + } + + Y_UNIT_TEST(BuiltinFileOpNoArgs) { + NYql::TAstParseResult res = SqlToYql("select FilePath()"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: FilePath() requires exactly 1 arguments, given: 0\n"); + } + + Y_UNIT_TEST(ProcessWithHaving) { + NYql::TAstParseResult res = SqlToYql("process plato.Input using some::udf(value) having value == 1"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:15: Error: PROCESS does not allow HAVING yet! You may request it on yql@ maillist.\n"); + } + + Y_UNIT_TEST(ReduceNoBy) { + NYql::TAstParseResult res = SqlToYql("reduce plato.Input using some::udf(value)"); + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: Unexpected token absence : Missing ON \n\n<main>:1:25: Error: Unexpected token absence : Missing USING \n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: mismatched input 'using' expecting {',', ON, PRESORT}\n"); +#endif + } + + Y_UNIT_TEST(ReduceDistinct) { + NYql::TAstParseResult res = SqlToYql("reduce plato.Input on key using some::udf(distinct value)"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:43: Error: DISTINCT can not be used in PROCESS/REDUCE\n"); + } + + Y_UNIT_TEST(CreateTableWithView) { + NYql::TAstParseResult res = SqlToYql("CREATE TABLE plato.foo:bar (key INT);"); + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:22: Error: Unexpected token ':' : syntax error...\n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:22: Error: mismatched input ':' expecting '('\n"); +#endif + } + + Y_UNIT_TEST(AsteriskWithSomethingAfter) { + NYql::TAstParseResult res = SqlToYql("select *, LENGTH(value) from plato.Input;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Unable to use plain '*' with other projection items. Please use qualified asterisk instead: '<table>.*' (<table> can be either table name or table alias).\n"); + } + + Y_UNIT_TEST(AsteriskWithSomethingBefore) { + NYql::TAstParseResult res = SqlToYql("select LENGTH(value), * from plato.Input;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:23: Error: Unable to use plain '*' with other projection items. Please use qualified asterisk instead: '<table>.*' (<table> can be either table name or table alias).\n"); + } + + Y_UNIT_TEST(DuplicatedQualifiedAsterisk) { + NYql::TAstParseResult res = SqlToYql("select in.*, key, in.* from plato.Input as in;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: Unable to use twice same quialified asterisk. Invalid source: in\n"); + } + + Y_UNIT_TEST(BrokenLabel) { + NYql::TAstParseResult res = SqlToYql("select in.*, key as `funny.label` from plato.Input as in;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:14: Error: Unable to use '.' in column name. Invalid column name: funny.label\n"); + } + + Y_UNIT_TEST(KeyConflictDetect0) { + NYql::TAstParseResult res = SqlToYql("select key, in.key as key from plato.Input as in;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:13: Error: Unable to use duplicate column names. Collision in name: key\n"); + } + + Y_UNIT_TEST(KeyConflictDetect1) { + NYql::TAstParseResult res = SqlToYql("select length(key) as key, key from plato.Input;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:28: Error: Unable to use duplicate column names. Collision in name: key\n"); + } + + Y_UNIT_TEST(KeyConflictDetect2) { + NYql::TAstParseResult res = SqlToYql("select key, in.key from plato.Input as in;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); + } + + Y_UNIT_TEST(AutogenerationAliasWithCollisionConflict1) { + UNIT_ASSERT(SqlToYql("select LENGTH(Value), key as column0 from plato.Input;").IsOk()); + } + + Y_UNIT_TEST(AutogenerationAliasWithCollisionConflict2) { + UNIT_ASSERT(SqlToYql("select key as column1, LENGTH(Value) from plato.Input;").IsOk()); + } + + Y_UNIT_TEST(MissedSourceTableForQualifiedAsteriskOnSimpleSelect) { + NYql::TAstParseResult res = SqlToYql("use plato; select Intop.*, Input.key from plato.Input;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: Unknown correlation name: Intop\n"); + } + + Y_UNIT_TEST(MissedSourceTableForQualifiedAsteriskOnJoin) { + NYql::TAstParseResult res = SqlToYql("use plato; select tmissed.*, t2.*, t1.key from plato.Input as t1 join plato.Input as t2 on t1.key==t2.key;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:19: Error: Unknown correlation name for asterisk: tmissed\n"); + } + + Y_UNIT_TEST(UnableToReferenceOnNotExistSubcolumn) { + NYql::TAstParseResult res = SqlToYql("select b.subkey from (select key from plato.Input as a) as b;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Column subkey is not in source column set\n"); + } + + Y_UNIT_TEST(ConflictOnSameNameWithQualify0) { + NYql::TAstParseResult res = SqlToYql("select in.key, in.key as key from plato.Input as in;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); + } + + Y_UNIT_TEST(ConflictOnSameNameWithQualify1) { + NYql::TAstParseResult res = SqlToYql("select in.key, length(key) as key from plato.Input as in;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); + } + + Y_UNIT_TEST(ConflictOnSameNameWithQualify2) { + NYql::TAstParseResult res = SqlToYql("select key, in.key from plato.Input as in;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); + } + + Y_UNIT_TEST(ConflictOnSameNameWithQualify3) { + NYql::TAstParseResult res = SqlToYql("select in.key, subkey as key from plato.Input as in;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Duplicate column: key\n"); + } + + Y_UNIT_TEST(SelectFlattenBySameColumns) { + NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key, key as kk)"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:46: Error: Duplicate column name found: key in FlattenBy section\n"); + } + + Y_UNIT_TEST(SelectFlattenBySameAliases) { + NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key as kk, subkey as kk);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Error: Duplicate alias found: kk in FlattenBy section\n"); + } + + Y_UNIT_TEST(SelectFlattenByExprSameAliases) { + NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key as kk, ListSkip(subkey,1) as kk);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Error: Collision between alias and column name: kk in FlattenBy section\n"); + } + + Y_UNIT_TEST(SelectFlattenByConflictNameAndAlias0) { + NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key, subkey as key);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:46: Error: Collision between alias and column name: key in FlattenBy section\n"); + } + + Y_UNIT_TEST(SelectFlattenByConflictNameAndAlias1) { + NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key as kk, subkey as key);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Error: Collision between alias and column name: key in FlattenBy section\n"); + } + + Y_UNIT_TEST(SelectFlattenByExprConflictNameAndAlias1) { + NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key as kk, ListSkip(subkey,1) as key);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Error: Duplicate column name found: key in FlattenBy section\n"); + } + + Y_UNIT_TEST(SelectFlattenByUnnamedExpr) { + NYql::TAstParseResult res = SqlToYql("select key from plato.Input flatten by (key, ListSkip(key, 1))"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:46: Error: Unnamed expression after FLATTEN BY is not allowed\n"); + } + + Y_UNIT_TEST(UseInOnStrings) { + NYql::TAstParseResult res = SqlToYql("select * from plato.Input where \"foo\" in \"foovalue\";"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:42: Error: Unable to use IN predicate with string argument, it won't search substring - " + "expecting tuple, list, dict or single column table source\n"); + } + + Y_UNIT_TEST(UseSubqueryInScalarContextInsideIn) { + NYql::TAstParseResult res = SqlToYql("$q = (select key from plato.Input); select * from plato.Input where subkey in ($q);"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:79: Warning: Using subrequest in scalar context after IN, " + "perhaps you should remove parenthesis here, code: 4501\n"); + } + + Y_UNIT_TEST(InHintsWithKeywordClash) { + NYql::TAstParseResult res = SqlToYql("SELECT COMPACT FROM plato.Input WHERE COMPACT IN COMPACT `COMPACT`(1,2,3)"); + UNIT_ASSERT(!res.Root); + // should try to parse last compact as call expression + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:58: Error: Unknown builtin: COMPACT\n"); + } + + Y_UNIT_TEST(ErrorColumnPosition) { + NYql::TAstParseResult res = SqlToYql( + "USE plato;\n" + "SELECT \n" + "value FROM (\n" + "select key from Input\n" + ");\n" + ); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:1: Error: Column value is not in source column set\n"); + } + + Y_UNIT_TEST(PrimaryViewAbortMapReduce) { + NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input VIEW PRIMARY KEY"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: primary view is not supported for yt tables\n"); + } + + Y_UNIT_TEST(InsertAbortMapReduce) { + NYql::TAstParseResult res = SqlToYql("INSERT OR ABORT INTO plato.Output SELECT key FROM plato.Input"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: INSERT OR ABORT INTO is not supported for yt tables\n"); + } + + Y_UNIT_TEST(ReplaceIntoMapReduce) { + NYql::TAstParseResult res = SqlToYql("REPLACE INTO plato.Output SELECT key FROM plato.Input"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: Meaning of REPLACE INTO has been changed, now you should use INSERT INTO <table> WITH TRUNCATE ... for yt\n"); + } + + Y_UNIT_TEST(UpsertIntoMapReduce) { + NYql::TAstParseResult res = SqlToYql("UPSERT INTO plato.Output SELECT key FROM plato.Input"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: UPSERT INTO is not supported for yt tables\n"); + } + + Y_UNIT_TEST(UpdateMapReduce) { + NYql::TAstParseResult res = SqlToYql("UPDATE plato.Output SET value = value + 1 WHERE key < 1"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: UPDATE is unsupported for yt\n"); + } + + Y_UNIT_TEST(DeleteMapReduce) { + NYql::TAstParseResult res = SqlToYql("DELETE FROM plato.Output WHERE key < 1"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: DELETE is unsupported for yt\n"); + } + + Y_UNIT_TEST(ReplaceIntoWithTruncate) { + NYql::TAstParseResult res = SqlToYql("REPLACE INTO plato.Output WITH TRUNCATE SELECT key FROM plato.Input"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:32: Error: Unable REPLACE INTO with truncate mode\n"); + } + + Y_UNIT_TEST(UpsertIntoWithTruncate) { + NYql::TAstParseResult res = SqlToYql("UPSERT INTO plato.Output WITH TRUNCATE SELECT key FROM plato.Input"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:31: Error: Unable UPSERT INTO with truncate mode\n"); + } + + Y_UNIT_TEST(InsertIntoWithTruncateKikimr) { + NYql::TAstParseResult res = SqlToYql("INSERT INTO plato.Output WITH TRUNCATE SELECT key FROM plato.Input", 10, TString(NYql::KikimrProviderName)); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:0: Error: INSERT INTO WITH TRUNCATE is not supported for kikimr tables\n"); + } + + Y_UNIT_TEST(InsertIntoWithWrongArgumentCount) { + NYql::TAstParseResult res = SqlToYql("insert into plato.Output with truncate (key, value, subkey) values (5, '1', '2', '3');"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:53: Error: VALUES have 4 columns, INSERT INTO ... WITH TRUNCATE expects: 3\n"); + } + + Y_UNIT_TEST(UpsertWithWrongArgumentCount) { + NYql::TAstParseResult res = SqlToYql("upsert into plato.Output (key, value, subkey) values (2, '3');", 10, TString(NYql::KikimrProviderName)); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:39: Error: VALUES have 2 columns, UPSERT INTO expects: 3\n"); + } + + Y_UNIT_TEST(GroupingSetByExprWithoutAlias) { + NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY GROUPING SETS (cast(key as uint32), subkey);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:53: Error: Unnamed expressions are not supported in GROUPING SETS. Please use '<expr> AS <name>'.\n"); + } + + Y_UNIT_TEST(GroupingSetByExprWithoutAlias2) { + NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY subkey || subkey, GROUPING SETS (\n" + "cast(key as uint32), subkey);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:1: Error: Unnamed expressions are not supported in GROUPING SETS. Please use '<expr> AS <name>'.\n"); + } + + Y_UNIT_TEST(CubeByExprWithoutAlias) { + NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY CUBE (key, subkey / key);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:56: Error: Unnamed expressions are not supported in CUBE. Please use '<expr> AS <name>'.\n"); + } + + Y_UNIT_TEST(RollupByExprWithoutAlias) { + NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY ROLLUP (subkey / key);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:53: Error: Unnamed expressions are not supported in ROLLUP. Please use '<expr> AS <name>'.\n"); + } + + Y_UNIT_TEST(GroupByHugeCubeDeniedNoPragma) { + NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY CUBE (key, subkey, value, key + subkey as sum, key - subkey as sub, key + val as keyval);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:119: Error: GROUP BY CUBE is allowed only for 5 columns, but you use 6\n"); + } + + Y_UNIT_TEST(GroupByInvalidPragma) { + NYql::TAstParseResult res = SqlToYql("PRAGMA GroupByCubeLimit = '-4';"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:27: Error: Expected unsigned integer literal as a single argument for: GroupByCubeLimit\n"); + } + + Y_UNIT_TEST(GroupByHugeCubeDeniedPragme) { + NYql::TAstParseResult res = SqlToYql("PRAGMA GroupByCubeLimit = '4'; SELECT key FROM plato.Input GROUP BY CUBE (key, subkey, value, key + subkey as sum, key - subkey as sub);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:132: Error: GROUP BY CUBE is allowed only for 4 columns, but you use 5\n"); + } + + Y_UNIT_TEST(GroupByFewBigCubes) { + NYql::TAstParseResult res = SqlToYql("SELECT key FROM plato.Input GROUP BY CUBE(key, subkey, key + subkey as sum), CUBE(value, value + key + subkey as total);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Unable to GROUP BY more than 64 groups, you try use 80 groups\n"); + } + + Y_UNIT_TEST(GroupByFewBigCubesWithPragmaLimit) { + NYql::TAstParseResult res = SqlToYql("PRAGMA GroupByLimit = '16'; SELECT key FROM plato.Input GROUP BY GROUPING SETS(key, subkey, key + subkey as sum), ROLLUP(value, value + key + subkey as total);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:29: Error: Unable to GROUP BY more than 16 groups, you try use 18 groups\n"); + } + + Y_UNIT_TEST(NoGroupingColumn0) { + NYql::TAstParseResult res = SqlToYql( + "select count(1), key_first, val_first, grouping(key_first, val_first, nomind) as group\n" + "from plato.Input group by grouping sets (cast(key as uint32) /100 as key_first, Substring(value, 1, 1) as val_first);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:71: Error: Column 'nomind' is not a grouping column\n"); + } + + Y_UNIT_TEST(NoGroupingColumn1) { + NYql::TAstParseResult res = SqlToYql("select count(1), grouping(key, value) as group_duo from plato.Input group by cube (key, subkey);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:32: Error: Column 'value' is not a grouping column\n"); + } + + Y_UNIT_TEST(EmptyAccess0) { + NYql::TAstParseResult res = SqlToYql("insert into plato.Output (list0, list1) values (AsList(0, 1, 2), AsList(``));"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:73: Error: Column reference \"\" is not allowed in current scope\n"); + } + + Y_UNIT_TEST(EmptyAccess1) { + NYql::TAstParseResult res = SqlToYql("insert into plato.Output (list0, list1) values (AsList(0, 1, 2), ``);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:66: Error: Column reference \"\" is not allowed in current scope\n"); + } + + Y_UNIT_TEST(UseUnknownColumnInInsert) { + NYql::TAstParseResult res = SqlToYql("insert into plato.Output (list0, list1) values (AsList(0, 1, 2), AsList(`test`));"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:73: Error: Column reference \"test\" is not allowed in current scope\n"); + } + + Y_UNIT_TEST(GroupByEmptyColumn) { + NYql::TAstParseResult res = SqlToYql("select count(1) from plato.Input group by ``;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:43: Error: Column name can not be empty\n"); + } + + Y_UNIT_TEST(ConvertNumberOutOfBase) { + NYql::TAstParseResult res = SqlToYql("select 0o80l;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to parse number from string: 0o80l, char: '8' is out of base: 8\n"); + } + + Y_UNIT_TEST(ConvertNumberOutOfRangeForInt64ButFitsInUint64) { + NYql::TAstParseResult res = SqlToYql("select 0xc000000000000000l;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to parse 13835058055282163712 as integer literal of Int64 type: value out of range for Int64\n"); + } + + Y_UNIT_TEST(ConvertNumberOutOfRangeUint64) { + NYql::TAstParseResult res = SqlToYql("select 0xc0000000000000000l;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to parse number from string: 0xc0000000000000000l, number limit overflow\n"); + + res = SqlToYql("select 1234234543563435151456;\n"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to parse number from string: 1234234543563435151456, number limit overflow\n"); + } + + Y_UNIT_TEST(ConvertNumberNegativeOutOfRange) { + NYql::TAstParseResult res = SqlToYql("select -9223372036854775808;\n" + "select -9223372036854775809;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:8: Error: Failed to parse negative integer: -9223372036854775809, number limit overflow\n"); + } + + Y_UNIT_TEST(InvaildUsageReal0) { + NYql::TAstParseResult res = SqlToYql("select .0;"); + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:7: Error: Unexpected token '.' : cannot match to any predicted input...\n\n"); +#else + UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "<main>:1:7: Error: extraneous input '.' expecting {"); +#endif + } + + Y_UNIT_TEST(InvaildUsageReal1) { + NYql::TAstParseResult res = SqlToYql("select .0f;"); + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:7: Error: Unexpected token '.' : cannot match to any predicted input...\n\n"); +#else + UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "<main>:1:7: Error: extraneous input '.' expecting {"); +#endif + } + + Y_UNIT_TEST(InvaildUsageWinFunctionWithoutWindow) { + NYql::TAstParseResult res = SqlToYql("select lead(key, 2) from plato.Input;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Failed to use window function Lead without window specification\n"); + } + + Y_UNIT_TEST(DropTableWithIfExists) { + NYql::TAstParseResult res = SqlToYql("DROP TABLE IF EXISTS plato.foo;"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("drop_if_exists")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(TooManyErrors) { + const char* q = R"( + USE plato; + select A, B, C, D, E, F, G, H, I, J, K, L, M, N from (select b from `abc`); +)"; + + NYql::TAstParseResult res = SqlToYql(q, 10); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), + R"(<main>:3:16: Error: Column A is not in source column set. Did you mean b? +<main>:3:19: Error: Column B is not in source column set. Did you mean b? +<main>:3:22: Error: Column C is not in source column set. Did you mean b? +<main>:3:25: Error: Column D is not in source column set. Did you mean b? +<main>:3:28: Error: Column E is not in source column set. Did you mean b? +<main>:3:31: Error: Column F is not in source column set. Did you mean b? +<main>:3:34: Error: Column G is not in source column set. Did you mean b? +<main>:3:37: Error: Column H is not in source column set. Did you mean b? +<main>:3:40: Error: Column I is not in source column set. Did you mean b? +<main>: Error: Too many issues, code: 1 +)"); + }; + + Y_UNIT_TEST(ShouldCloneBindingForNamedParameter) { + NYql::TAstParseResult res = SqlToYql(R"($f = () -> { + $value_type = TypeOf(1); + $pair_type = StructType( + TypeOf("2") AS key, + $value_type AS value + ); + + RETURN TupleType( + ListType($value_type), + $pair_type); +}; + +select FormatType($f()); +)"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(BlockedInvalidFrameBounds) { + auto check = [](const TString& frame, const TString& err) { + const TString prefix = "SELECT SUM(x) OVER w FROM plato.Input WINDOW w AS (PARTITION BY key ORDER BY subkey\n"; + NYql::TAstParseResult res = SqlToYql(prefix + frame + ")"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), err); + }; + + check("ROWS UNBOUNDED FOLLOWING", "<main>:2:5: Error: Frame cannot start from UNBOUNDED FOLLOWING\n"); + check("ROWS BETWEEN 5 PRECEDING AND UNBOUNDED PRECEDING", "<main>:2:29: Error: Frame cannot end with UNBOUNDED PRECEDING\n"); + check("ROWS BETWEEN CURRENT ROW AND 5 PRECEDING", "<main>:2:13: Error: Frame cannot start from CURRENT ROW and end with PRECEDING\n"); + check("ROWS BETWEEN 5 FOLLOWING AND CURRENT ROW", "<main>:2:14: Error: Frame cannot start from FOLLOWING and end with CURRENT ROW\n"); + check("ROWS BETWEEN 5 FOLLOWING AND 5 PRECEDING", "<main>:2:14: Error: Frame cannot start from FOLLOWING and end with PRECEDING\n"); + } + + Y_UNIT_TEST(BlockedRangeValueWithoutSingleOrderBy) { + UNIT_ASSERT(SqlToYql("SELECT COUNT(*) OVER (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM plato.Input").IsOk()); + UNIT_ASSERT(SqlToYql("SELECT COUNT(*) OVER (RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) FROM plato.Input").IsOk()); + + auto res = SqlToYql("SELECT COUNT(*) OVER (RANGE 5 PRECEDING) FROM plato.Input"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:29: Error: RANGE with <offset> PRECEDING/FOLLOWING requires exactly one expression in ORDER BY partition clause\n"); + + res = SqlToYql("SELECT COUNT(*) OVER (ORDER BY key, value RANGE 5 PRECEDING) FROM plato.Input"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:49: Error: RANGE with <offset> PRECEDING/FOLLOWING requires exactly one expression in ORDER BY partition clause\n"); + } + + Y_UNIT_TEST(NoColumnsInFrameBounds) { + NYql::TAstParseResult res = SqlToYql( + "SELECT SUM(x) OVER w FROM plato.Input WINDOW w AS (ROWS BETWEEN\n" + " 1 + key PRECEDING AND 2 + key FOLLOWING);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:6: Error: Column reference \"key\" is not allowed in current scope\n"); + } + + Y_UNIT_TEST(WarnOnEmptyFrameBounds) { + NYql::TAstParseResult res = SqlToYql( + "SELECT SUM(x) OVER w FROM plato.Input WINDOW w AS (PARTITION BY key ORDER BY subkey\n" + "ROWS BETWEEN 10 FOLLOWING AND 5 FOLLOWING)"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:14: Warning: Used frame specification implies empty window frame, code: 4520\n"); + } + + Y_UNIT_TEST(WarnOnRankWithUnorderedWindow) { + NYql::TAstParseResult res = SqlToYql("SELECT RANK() OVER w FROM plato.Input WINDOW w AS ()"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Warning: Rank() is used with unordered window - all rows will be considered equal to each other, code: 4521\n"); + } + + Y_UNIT_TEST(WarnOnRankExprWithUnorderedWindow) { + NYql::TAstParseResult res = SqlToYql("SELECT RANK(key) OVER w FROM plato.Input WINDOW w AS ()"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Warning: Rank(<expression>) is used with unordered window - the result is likely to be undefined, code: 4521\n"); + } + + Y_UNIT_TEST(AnyAsTableName) { + NYql::TAstParseResult res = SqlToYql("use plato; select * from any;"); + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:28: Error: Unexpected token ';' : syntax error...\n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:28: Error: no viable alternative at input 'any;'\n"); +#endif + } + + Y_UNIT_TEST(IncorrectOrderOfLambdaOptionalArgs) { + NYql::TAstParseResult res = SqlToYql("$f = ($x?, $y)->($x + $y); select $f(1);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Non-optional argument can not follow optional one\n"); + } + + Y_UNIT_TEST(IncorrectOrderOfActionOptionalArgs) { + NYql::TAstParseResult res = SqlToYql("define action $f($x?, $y) as select $x,$y; end define; do $f(1);"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:23: Error: Non-optional argument can not follow optional one\n"); + } + + Y_UNIT_TEST(NotAllowedQuestionOnNamedNode) { + NYql::TAstParseResult res = SqlToYql("$f = 1; select $f?;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:18: Error: Unexpected token '?' at the end of expression\n"); + } + + Y_UNIT_TEST(AnyAndCrossJoin) { + NYql::TAstParseResult res = SqlToYql("use plato; select * from any Input1 cross join Input2"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:26: Error: ANY should not be used with Cross JOIN\n"); + + res = SqlToYql("use plato; select * from Input1 cross join any Input2"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:44: Error: ANY should not be used with Cross JOIN\n"); + } + + Y_UNIT_TEST(AnyWithCartesianProduct) { + NYql::TAstParseResult res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; select * from any Input1, Input2"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:56: Error: ANY should not be used with Cross JOIN\n"); + + res = SqlToYql("pragma AnsiImplicitCrossJoin; use plato; select * from Input1, any Input2"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:64: Error: ANY should not be used with Cross JOIN\n"); + } + + Y_UNIT_TEST(ErrorPlainEndAsInlineActionTerminator) { + NYql::TAstParseResult res = SqlToYql( + "do begin\n" + " select 1\n" + "; end\n"); + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:0: Error: Unexpected token absence : Missing DO \n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:0: Error: missing DO at '<EOF>'\n"); +#endif + } + + Y_UNIT_TEST(ErrorMultiWayJoinWithUsing) { + NYql::TAstParseResult res = SqlToYql( + "USE plato;\n" + "PRAGMA DisableSimpleColumns;\n" + "SELECT *\n" + "FROM Input1 AS a\n" + "JOIN Input2 AS b USING(key)\n" + "JOIN Input3 AS c ON a.key = c.key;\n" + ); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), + "<main>:5:24: Error: Multi-way JOINs should be connected with ON clause instead of USING clause\n" + ); + } + + Y_UNIT_TEST(RequireLabelInFlattenByWithDot) { + NYql::TAstParseResult res = SqlToYql("select * from plato.Input flatten by x.y"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), + "<main>:1:40: Error: Unnamed expression after FLATTEN BY is not allowed\n" + ); + } + + Y_UNIT_TEST(WarnUnnamedColumns) { + NYql::TAstParseResult res = SqlToYql( + "PRAGMA WarnUnnamedColumns;\n" + "\n" + "SELECT key, subkey, key || subkey FROM plato.Input ORDER BY subkey;\n"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:28: Warning: Autogenerated column name column2 will be used for expression, code: 4516\n"); + } + + Y_UNIT_TEST(WarnSourceColumnMismatch) { + NYql::TAstParseResult res = SqlToYql( + "insert into plato.Output (key, subkey, new_value, one_more_value) select key as Key, subkey, value, \"x\" from plato.Input;"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:51: Warning: Column names in SELECT don't match column specification in parenthesis. \"key\" doesn't match \"Key\". \"new_value\" doesn't match \"value\", code: 4517\n"); + } + + Y_UNIT_TEST(YtCaseInsensitive) { + NYql::TAstParseResult res = SqlToYql("select * from PlatO.foo;"); + UNIT_ASSERT(res.Root); + + res = SqlToYql("use PlatO; select * from foo;"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(KikimrCaseSensitive) { + NYql::TAstParseResult res = SqlToYql("select * from PlatO.foo;", 10, "kikimr"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:15: Error: Unknown cluster: PlatO\n"); + + res = SqlToYql("use PlatO; select * from foo;", 10, "kikimr"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:5: Error: Unknown cluster: PlatO\n"); + } + + Y_UNIT_TEST(DiscoveryModeForbidden) { + NYql::TAstParseResult res = SqlToYqlWithMode("insert into plato.Output select * from plato.range(\"\", Input1, Input4)", NSQLTranslation::ESqlMode::DISCOVERY); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: range is not allowed in Discovery mode, code: 4600\n"); + + res = SqlToYqlWithMode("insert into plato.Output select * from plato.like(\"\", \"Input%\")", NSQLTranslation::ESqlMode::DISCOVERY); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: like is not allowed in Discovery mode, code: 4600\n"); + + res = SqlToYqlWithMode("insert into plato.Output select * from plato.regexp(\"\", \"Input.\")", NSQLTranslation::ESqlMode::DISCOVERY); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: regexp is not allowed in Discovery mode, code: 4600\n"); + + res = SqlToYqlWithMode("insert into plato.Output select * from plato.filter(\"\", ($name) -> { return find($name, \"Input\") is not null; })", NSQLTranslation::ESqlMode::DISCOVERY); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: filter is not allowed in Discovery mode, code: 4600\n"); + + res = SqlToYqlWithMode("select Path from plato.folder(\"\") where Type == \"table\"", NSQLTranslation::ESqlMode::DISCOVERY); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:18: Error: folder is not allowed in Discovery mode, code: 4600\n"); + } + + Y_UNIT_TEST(YsonFuncWithoutArgs) { + UNIT_ASSERT(SqlToYql("SELECT Yson::SerializeText(Yson::From());").IsOk()); + } + + Y_UNIT_TEST(CanNotUseOrderByInNonLastSelectInUnionAllChain) { + auto req = "pragma AnsiOrderByLimitInUnionAll;\n" + "use plato;\n" + "\n" + "select * from Input order by key\n" + "union all\n" + "select * from Input order by key limit 1;"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:21: Error: ORDER BY within UNION ALL is only allowed after last subquery\n"); + } + + Y_UNIT_TEST(CanNotUseLimitInNonLastSelectInUnionAllChain) { + auto req = "pragma AnsiOrderByLimitInUnionAll;\n" + "use plato;\n" + "\n" + "select * from Input limit 1\n" + "union all\n" + "select * from Input order by key limit 1;"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:21: Error: LIMIT within UNION ALL is only allowed after last subquery\n"); + } + + Y_UNIT_TEST(CanNotUseDiscardInNonFirstSelectInUnionAllChain) { + auto req = "pragma AnsiOrderByLimitInUnionAll;\n" + "use plato;\n" + "\n" + "select * from Input\n" + "union all\n" + "discard select * from Input;"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:6:1: Error: DISCARD within UNION ALL is only allowed before first subquery\n"); + } + + Y_UNIT_TEST(CanNotUseIntoResultInNonLastSelectInUnionAllChain) { + auto req = "use plato;\n" + "pragma AnsiOrderByLimitInUnionAll;\n" + "\n" + "select * from Input\n" + "union all\n" + "discard select * from Input;"; + + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:6:1: Error: DISCARD within UNION ALL is only allowed before first subquery\n"); + } + + Y_UNIT_TEST(YsonStrictInvalidPragma) { + auto res = SqlToYql("pragma yson.Strict = \"wrong\";"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:22: Error: Expected 'true', 'false' or no parameter for: Strict\n"); + } + + Y_UNIT_TEST(WarnTableNameInSomeContexts) { + UNIT_ASSERT(SqlToYql("use plato; select TableName() from Input;").IsOk()); + UNIT_ASSERT(SqlToYql("use plato; select TableName(\"aaaa\");").IsOk()); + UNIT_ASSERT(SqlToYql("select TableName(\"aaaa\", \"yt\");").IsOk()); + + auto res = SqlToYql("select TableName() from plato.Input;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: TableName requires either service name as second argument or current cluster name\n"); + + res = SqlToYql("use plato;\n" + "select TableName() from Input1 as a join Input2 as b using(key);"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:8: Warning: TableName() may produce empty result when used in ambiguous context (with JOIN), code: 4525\n"); + + res = SqlToYql("use plato;\n" + "select SOME(TableName()), key from Input group by key;"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:13: Warning: TableName() will produce empty result when used with aggregation.\n" + "Please consult documentation for possible workaround, code: 4525\n"); + } + + Y_UNIT_TEST(WarnOnDistincWithHavingWithoutAggregations) { + auto res = SqlToYql("select distinct key from plato.Input having key != '0';"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:49: Warning: The usage of HAVING without aggregations with SELECT DISTINCT is non-standard and will stop working soon. Please use WHERE instead., code: 4526\n"); + } + + Y_UNIT_TEST(FlattenByExprWithNestedNull) { + auto res = SqlToYql("USE plato;\n" + "\n" + "SELECT * FROM (SELECT 1 AS region_id)\n" + "FLATTEN BY (\n" + " CAST($unknown(region_id) AS List<String>) AS region\n" + ")"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:10: Error: Unknown name: $unknown\n"); + } + + Y_UNIT_TEST(EmptySymbolNameIsForbidden) { + auto req = " $`` = 1; select $``;"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:5: Error: Empty symbol name is not allowed\n"); + } + + Y_UNIT_TEST(WarnOnBinaryOpWithNullArg) { + auto req = "select * from plato.Input where cast(key as Int32) != NULL"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:52: Warning: Binary operation != will return NULL here, code: 4529\n"); + + req = "select 1 or null"; + res = SqlToYql(req); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), ""); + } + + Y_UNIT_TEST(ErrorIfTableSampleArgUsesColumns) { + auto req = "SELECT key FROM plato.Input TABLESAMPLE BERNOULLI(MIN_OF(100.0, CAST(subkey as Int32)));"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:70: Error: Column reference \"subkey\" is not allowed in current scope\n"); + } + + Y_UNIT_TEST(DerivedColumnListForSelectIsNotSupportedYet) { + auto req = "SELECT a,b,c FROM plato.Input as t(x,y,z);"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:35: Error: Derived column list is only supported for VALUES\n"); + } + + Y_UNIT_TEST(ErrorIfValuesHasDifferentCountOfColumns) { + auto req = "VALUES (1,2,3), (4,5);"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: All VALUES items should have same size: expecting 3, got 2\n"); + } + + Y_UNIT_TEST(ErrorIfDerivedColumnSizeExceedValuesColumnCount) { + auto req = "SELECT * FROM(VALUES (1,2), (3,4)) as t(x,y,z);"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:40: Error: Derived column list size exceeds column count in VALUES\n"); + } + + Y_UNIT_TEST(WarnoOnAutogeneratedNamesForValues) { + auto req = "PRAGMA WarnUnnamedColumns;\n" + "SELECT * FROM (VALUES (1,2,3,4), (5,6,7,8)) as t(x,y);"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:16: Warning: Autogenerated column names column2...column3 will be used here, code: 4516\n"); + } + + Y_UNIT_TEST(ErrUnionAllWithOrderByWithoutExplicitLegacyMode) { + auto req = "use plato;\n" + "\n" + "select * from Input order by key\n" + "union all\n" + "select * from Input order by key;"; + + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:21: Error: ORDER BY within UNION ALL is only allowed after last subquery\n"); + } + + Y_UNIT_TEST(ErrUnionAllWithLimitWithoutExplicitLegacyMode) { + auto req = "use plato;\n" + "\n" + "select * from Input limit 10\n" + "union all\n" + "select * from Input limit 1;"; + + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:21: Error: LIMIT within UNION ALL is only allowed after last subquery\n"); + } + + Y_UNIT_TEST(ErrUnionAllWithIntoResultWithoutExplicitLegacyMode) { + auto req = "use plato;\n" + "\n" + "select * from Input into result aaa\n" + "union all\n" + "select * from Input;"; + + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:21: Error: INTO RESULT within UNION ALL is only allowed after last subquery\n"); + } + + Y_UNIT_TEST(ErrUnionAllWithDiscardWithoutExplicitLegacyMode) { + auto req = "use plato;\n" + "\n" + "select * from Input\n" + "union all\n" + "discard select * from Input;"; + + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:1: Error: DISCARD within UNION ALL is only allowed before first subquery\n"); + } + + Y_UNIT_TEST(ErrUnionAllKeepsIgnoredOrderByWarning) { + auto req = "use plato;\n" + "\n" + "SELECT * FROM (\n" + " SELECT * FROM Input\n" + " UNION ALL\n" + " SELECT t.* FROM Input AS t ORDER BY t.key\n" + ");"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:3: Warning: ORDER BY without LIMIT in subquery will be ignored, code: 4504\n" + "<main>:6:39: Error: Unknown correlation name: t\n"); + } + + Y_UNIT_TEST(ErrOrderByIgnoredButCheckedForMissingColumns) { + auto req = "$src = SELECT key FROM (SELECT 1 as key, 2 as subkey) ORDER BY x; SELECT * FROM $src;"; + ExpectFailWithError(req, "<main>:1:8: Warning: ORDER BY without LIMIT in subquery will be ignored, code: 4504\n" + "<main>:1:64: Error: Column x is not in source column set\n"); + + req = "$src = SELECT key FROM plato.Input ORDER BY x; SELECT * FROM $src;"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Warning: ORDER BY without LIMIT in subquery will be ignored, code: 4504\n"); + } + + Y_UNIT_TEST(InvalidTtlInterval) { + auto req = R"( + USE plato; + CREATE TABLE tableName (Key Uint32, CreatedAt Timestamp, PRIMARY KEY (Key)) + WITH (TTL = 1 On CreatedAt); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:25: Error: Literal of Interval type is expected for TTL\n" + "<main>:4:25: Error: Invalid TTL settings\n"); + } + + Y_UNIT_TEST(InvalidTtlUnit) { + auto req = R"( + USE plato; + CREATE TABLE tableName (Key Uint32, CreatedAt Uint32, PRIMARY KEY (Key)) + WITH (TTL = Interval("P1D") On CreatedAt AS PICOSECONDS); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "<main>:4:56: Error: Unexpected token 'PICOSECONDS'"); +#else + UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "mismatched input 'PICOSECONDS' expecting {MICROSECONDS, MILLISECONDS, NANOSECONDS, SECONDS}"); +#endif + } + + Y_UNIT_TEST(InvalidChangefeedSink) { + auto req = R"( + USE plato; + CREATE TABLE tableName ( + Key Uint32, PRIMARY KEY (Key), + CHANGEFEED feedName WITH (SINK_TYPE = "S3", MODE = "KEYS_ONLY", FORMAT = "json") + ); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:55: Error: Unknown changefeed sink type: S3\n"); + } + + Y_UNIT_TEST(InvalidChangefeedSettings) { + auto req = R"( + USE plato; + CREATE TABLE tableName ( + Key Uint32, PRIMARY KEY (Key), + CHANGEFEED feedName WITH (SINK_TYPE = "local", FOO = "bar") + ); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:64: Error: Unknown changefeed setting: FOO\n"); + } + + Y_UNIT_TEST(InvalidChangefeedInitialScan) { + auto req = R"( + USE plato; + CREATE TABLE tableName ( + Key Uint32, PRIMARY KEY (Key), + CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", INITIAL_SCAN = "foo") + ); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:95: Error: Literal of Bool type is expected for INITIAL_SCAN\n"); + } + + Y_UNIT_TEST(InvalidChangefeedVirtualTimestamps) { + auto req = R"( + USE plato; + CREATE TABLE tableName ( + Key Uint32, PRIMARY KEY (Key), + CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", VIRTUAL_TIMESTAMPS = "foo") + ); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:101: Error: Literal of Bool type is expected for VIRTUAL_TIMESTAMPS\n"); + } + + Y_UNIT_TEST(InvalidChangefeedResolvedTimestamps) { + auto req = R"( + USE plato; + CREATE TABLE tableName ( + Key Uint32, PRIMARY KEY (Key), + CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", BARRIERS_INTERVAL = "foo") + ); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:100: Error: Literal of Interval type is expected for BARRIERS_INTERVAL\n"); + } + + Y_UNIT_TEST(InvalidChangefeedRetentionPeriod) { + auto req = R"( + USE plato; + CREATE TABLE tableName ( + Key Uint32, PRIMARY KEY (Key), + CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", RETENTION_PERIOD = "foo") + ); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:99: Error: Literal of Interval type is expected for RETENTION_PERIOD\n"); + } + + Y_UNIT_TEST(InvalidChangefeedTopicPartitions) { + auto req = R"( + USE plato; + CREATE TABLE tableName ( + Key Uint32, PRIMARY KEY (Key), + CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", TOPIC_MIN_ACTIVE_PARTITIONS = "foo") + ); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:110: Error: Literal of integer type is expected for TOPIC_MIN_ACTIVE_PARTITIONS\n"); + } + + Y_UNIT_TEST(InvalidChangefeedAwsRegion) { + auto req = R"( + USE plato; + CREATE TABLE tableName ( + Key Uint32, PRIMARY KEY (Key), + CHANGEFEED feedName WITH (MODE = "KEYS_ONLY", FORMAT = "json", AWS_REGION = true) + ); + )"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:5:93: Error: Literal of String type is expected for AWS_REGION\n"); + } + + Y_UNIT_TEST(ErrJoinWithGroupingSetsWithoutCorrelationName) { + auto req = "USE plato;\n" + "\n" + "SELECT k1, k2, subkey\n" + "FROM T1 AS a JOIN T2 AS b USING (key)\n" + "GROUP BY GROUPING SETS(\n" + " (a.key as k1, b.subkey as k2),\n" + " (k1),\n" + " (subkey)\n" + ");"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:8:4: Error: Columns in grouping sets should have correlation name, error in key: subkey\n"); + } + + Y_UNIT_TEST(ErrJoinWithGroupByWithoutCorrelationName) { + auto req = "USE plato;\n" + "\n" + "SELECT k1, k2,\n" + " value\n" + "FROM T1 AS a JOIN T2 AS b USING (key)\n" + "GROUP BY a.key as k1, b.subkey as k2,\n" + " value;"; + ExpectFailWithError(req, + "<main>:7:5: Error: Columns in GROUP BY should have correlation name, error in key: value\n"); + } + + Y_UNIT_TEST(ErrWithMissingFrom) { + auto req = "select 1 as key where 1 > 1;"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:25: Error: Filtering is not allowed without FROM\n"); + + req = "select 1 + count(*);"; + res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:12: Error: Aggregation is not allowed without FROM\n"); + + req = "select 1 as key, subkey + value;"; + res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Column references are not allowed without FROM\n" + "<main>:1:18: Error: Column reference 'subkey'\n" + "<main>:1:1: Error: Column references are not allowed without FROM\n" + "<main>:1:27: Error: Column reference 'value'\n"); + + req = "select count(1) group by key;"; + res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:1: Error: Column references are not allowed without FROM\n" + "<main>:1:26: Error: Column reference 'key'\n"); + } + + Y_UNIT_TEST(ErrWithMissingFromForWindow) { + auto req = "$c = () -> (1 + count(1) over w);\n" + "select $c();"; + ExpectFailWithError(req, + "<main>:1:9: Error: Window and aggregation functions are not allowed in this context\n" + "<main>:1:17: Error: Failed to use aggregation function Count without window specification or in wrong place\n"); + + req = "$c = () -> (1 + lead(1) over w);\n" + "select $c();"; + ExpectFailWithError(req, + "<main>:1:17: Error: Window functions are not allowed in this context\n" + "<main>:1:17: Error: Failed to use window function Lead without window specification or in wrong place\n"); + + req = "select 1 + count(1) over w window w as ();"; + ExpectFailWithError(req, + "<main>:1:1: Error: Window and aggregation functions are not allowed without FROM\n" + "<main>:1:12: Error: Failed to use aggregation function Count without window specification or in wrong place\n"); + + req = "select 1 + lead(1) over w window w as ();"; + ExpectFailWithError(req, + "<main>:1:12: Error: Window functions are not allowed without FROM\n" + "<main>:1:12: Error: Failed to use window function Lead without window specification or in wrong place\n"); + } + + Y_UNIT_TEST(ErrWithMissingFromForInplaceWindow) { + auto req = "$c = () -> (1 + count(1) over ());\n" + "select $c();"; + ExpectFailWithError(req, + "<main>:1:26: Error: Window and aggregation functions are not allowed in this context\n"); + + req = "$c = () -> (1 + lead(1) over (rows between unbounded preceding and current row));\n" + "select $c();"; + ExpectFailWithError(req, + "<main>:1:25: Error: Window and aggregation functions are not allowed in this context\n"); + + req = "select 1 + count(1) over ();"; + ExpectFailWithError(req, + "<main>:1:1: Error: Window and aggregation functions are not allowed without FROM\n" + "<main>:1:12: Error: Failed to use aggregation function Count without window specification or in wrong place\n"); + + req = "select 1 + lead(1) over (rows between current row and unbounded following);"; + ExpectFailWithError(req, + "<main>:1:12: Error: Window functions are not allowed without FROM\n" + "<main>:1:12: Error: Failed to use window function Lead without window specification or in wrong place\n"); + } + + Y_UNIT_TEST(ErrDistinctInWrongPlace) { + auto req = "select Some::Udf(distinct key) from plato.Input;"; + ExpectFailWithError(req, + "<main>:1:18: Error: DISTINCT can only be used in aggregation functions\n"); + req = "select sum(key)(distinct foo) from plato.Input;"; + ExpectFailWithError(req, + "<main>:1:17: Error: DISTINCT can only be used in aggregation functions\n"); + + req = "select len(distinct foo) from plato.Input;"; + ExpectFailWithError(req, + "<main>:1:8: Error: DISTINCT can only be used in aggregation functions\n"); + + req = "$foo = ($x) -> ($x); select $foo(distinct key) from plato.Input;"; + ExpectFailWithError(req, + "<main>:1:34: Error: DISTINCT can only be used in aggregation functions\n"); + } + + Y_UNIT_TEST(ErrForNotSingleChildInInlineAST) { + ExpectFailWithError("select YQL::\"\"", + "<main>:1:8: Error: Failed to parse YQL: expecting AST root node with single child, but got 0\n"); + ExpectFailWithError("select YQL::@@ \t@@", + "<main>:1:8: Error: Failed to parse YQL: expecting AST root node with single child, but got 0\n"); + auto req = "$lambda = YQL::@@(lambda '(x)(+ x x)) (lambda '(y)(+ y y))@@;\n" + "select ListMap([1, 2, 3], $lambda);"; + ExpectFailWithError(req, + "<main>:1:11: Error: Failed to parse YQL: expecting AST root node with single child, but got 2\n"); + } + + Y_UNIT_TEST(ErrEmptyColumnName) { + ExpectFailWithError("select * without \"\" from plato.Input", + "<main>:1:18: Error: String literal can not be used here\n"); + + ExpectFailWithError("select * without `` from plato.Input;", + "<main>:1:18: Error: Empty column name is not allowed\n"); + + ExpectFailWithErrorForAnsiLexer("select * without \"\" from plato.Input", + "<main>:1:18: Error: Empty column name is not allowed\n"); + + ExpectFailWithErrorForAnsiLexer("select * without `` from plato.Input;", + "<main>:1:18: Error: Empty column name is not allowed\n"); + } + + Y_UNIT_TEST(ErrOnNonZeroArgumentsForTableRows) { + ExpectFailWithError("$udf=\"\";process plato.Input using $udf(TableRows(k))", + "<main>:1:40: Error: TableRows requires exactly 0 arguments\n"); + } + + Y_UNIT_TEST(ErrGroupByWithAggregationFunctionAndDistinctExpr) { + ExpectFailWithError("select * from plato.Input group by count(distinct key|key)", + "<main>:1:36: Error: Unable to GROUP BY aggregated values\n"); + } + + // FIXME: check if we can get old behaviour +#if 0 + Y_UNIT_TEST(ErrWithSchemaWithColumnsWithoutType) { + ExpectFailWithError("select * from plato.Input with COLUMNs", + "<main>:1:32: Error: Expected type after COLUMNS\n" + "<main>:1:32: Error: Failed to parse table hints\n"); + + ExpectFailWithError("select * from plato.Input with scheMa", + "<main>:1:32: Error: Expected type after SCHEMA\n" + "<main>:1:32: Error: Failed to parse table hints\n"); + } +#endif + + Y_UNIT_TEST(ErrCollectPreaggregatedInListLiteralWithoutFrom) { + ExpectFailWithError("SELECT([VARIANCE(DISTINCT[])])", + "<main>:1:1: Error: Column references are not allowed without FROM\n" + "<main>:1:9: Error: Column reference '_yql_preagg_Variance0'\n"); + } + + Y_UNIT_TEST(ErrGroupBySmartParenAsTuple) { + ExpectFailWithError("SELECT * FROM plato.Input GROUP BY (k, v,)", + "<main>:1:41: Error: Unexpected trailing comma in grouping elements list\n"); + } + + Y_UNIT_TEST(HandleNestedSmartParensInGroupBy) { + ExpectFailWithError("SELECT * FROM plato.Input GROUP BY (+() as k)", + "<main>:1:37: Error: Unable to GROUP BY constant expression\n"); + } + + Y_UNIT_TEST(ErrRenameWithAddColumn) { + ExpectFailWithError("USE plato; ALTER TABLE table RENAME TO moved, ADD COLUMN addc uint64", + "<main>:1:40: Error: RENAME TO can not be used together with another table action\n"); + } + + Y_UNIT_TEST(ErrAddColumnAndRename) { + // FIXME: fix positions in ALTER TABLE + ExpectFailWithError("USE plato; ALTER TABLE table ADD COLUMN addc uint64, RENAME TO moved", + "<main>:1:46: Error: RENAME TO can not be used together with another table action\n"); + } + + Y_UNIT_TEST(InvalidUuidValue) { + ExpectFailWithError("SELECT Uuid('123e4567ae89ba12d3aa456a426614174ab0')", + "<main>:1:8: Error: Invalid value \"123e4567ae89ba12d3aa456a426614174ab0\" for type Uuid\n"); + ExpectFailWithError("SELECT Uuid('123e4567ae89b-12d3-a456-426614174000')", + "<main>:1:8: Error: Invalid value \"123e4567ae89b-12d3-a456-426614174000\" for type Uuid\n"); + } + + Y_UNIT_TEST(WindowFunctionWithoutOver) { + ExpectFailWithError("SELECT LAST_VALUE(foo) FROM plato.Input", + "<main>:1:8: Error: Can't use window function LastValue without window specification (OVER keyword is missing)\n"); + ExpectFailWithError("SELECT LAST_VALUE(foo) FROM plato.Input GROUP BY key", + "<main>:1:8: Error: Can't use window function LastValue without window specification (OVER keyword is missing)\n"); + } + + Y_UNIT_TEST(CreateAlterUserWithoutCluster) { + ExpectFailWithError("\n CREATE USER user ENCRYPTED PASSWORD 'foobar';", "<main>:2:2: Error: USE statement is missing - no default cluster is selected\n"); + ExpectFailWithError("ALTER USER CURRENT_USER RENAME TO $foo;", "<main>:1:1: Error: USE statement is missing - no default cluster is selected\n"); + } + + Y_UNIT_TEST(ModifyPermissionsWithoutCluster) { + ExpectFailWithError("\n GRANT CONNECT ON `/Root` TO user;", "<main>:2:2: Error: USE statement is missing - no default cluster is selected\n"); + ExpectFailWithError("\n REVOKE MANAGE ON `/Root` FROM user;", "<main>:2:2: Error: USE statement is missing - no default cluster is selected\n"); + } + + Y_UNIT_TEST(ReservedRoleNames) { + ExpectFailWithError("USE plato; CREATE USER current_User;", "<main>:1:24: Error: System role CURRENT_USER can not be used here\n"); + ExpectFailWithError("USE plato; ALTER USER current_User RENAME TO Current_role", "<main>:1:46: Error: System role CURRENT_ROLE can not be used here\n"); + UNIT_ASSERT(SqlToYql("USE plato; DROP GROUP IF EXISTS a, b, c, current_User;").IsOk()); + } + + Y_UNIT_TEST(DisableClassicDivisionWithError) { + ExpectFailWithError("pragma ClassicDivision = 'false'; select $foo / 30;", "<main>:1:42: Error: Unknown name: $foo\n"); + } + + Y_UNIT_TEST(AggregationOfAgrregatedDistinctExpr) { + ExpectFailWithError("select sum(sum(distinct x + 1)) from plato.Input", "<main>:1:12: Error: Aggregation of aggregated values is forbidden\n"); + } + + Y_UNIT_TEST(WarnForUnusedSqlHint) { + NYql::TAstParseResult res = SqlToYql("select * from plato.Input1 as a join /*+ merge() */ plato.Input2 as b using(key);\n" + "select --+ foo(bar)\n" + " 1;"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:23: Warning: Hint foo will not be used, code: 4534\n"); + } + + Y_UNIT_TEST(WarnForDeprecatedSchema) { + NSQLTranslation::TTranslationSettings settings; + settings.ClusterMapping["s3bucket"] = NYql::S3ProviderName; + NYql::TAstParseResult res = SqlToYqlWithSettings("select * from s3bucket.`foo` with schema (col1 Int32, String as col2, Int64 as col3);", settings); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "Warning: Deprecated syntax for positional schema: please use 'column type' instead of 'type AS column', code: 4535\n"); + } + + Y_UNIT_TEST(ErrorOnColumnNameInMaxByLimit) { + ExpectFailWithError( + "SELECT AGGREGATE_BY(AsTuple(value, key), AggregationFactory(\"MAX_BY\", subkey)) FROM plato.Input;", + "<main>:1:42: Error: Source does not allow column references\n" + "<main>:1:71: Error: Column reference 'subkey'\n"); + } + + Y_UNIT_TEST(ErrorInLibraryWithTopLevelNamedSubquery) { + TString withUnusedSubq = "$unused = select max(key) from plato.Input;\n" + "\n" + "define subquery $foo() as\n" + " $count = select count(*) from plato.Input;\n" + " select * from plato.Input limit $count / 2;\n" + "end define;\n" + "export $foo;\n"; + UNIT_ASSERT(SqlToYqlWithMode(withUnusedSubq, NSQLTranslation::ESqlMode::LIBRARY).IsOk()); + + TString withTopLevelSubq = "$count = select count(*) from plato.Input;\n" + "\n" + "define subquery $foo() as\n" + " select * from plato.Input limit $count / 2;\n" + "end define;\n" + "export $foo;\n"; + auto res = SqlToYqlWithMode(withTopLevelSubq, NSQLTranslation::ESqlMode::LIBRARY); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: Named subquery can not be used as a top level statement in libraries\n"); + } + + Y_UNIT_TEST(SessionStartAndSessionStateShouldSurviveSessionWindowArgsError){ + TString query = R"( + $init = ($_row) -> (min(1, 2)); -- error: aggregation func min() can not be used here + $calculate = ($_row, $_state) -> (1); + $update = ($_row, $_state) -> (2); + SELECT + SessionStart() over w as session_start, + SessionState() over w as session_state, + FROM plato.Input as t + WINDOW w AS ( + PARTITION BY user, SessionWindow(ts + 1, $init, $update, $calculate) + ) + )"; + ExpectFailWithError(query, "<main>:2:33: Error: Aggregation function Min requires exactly 1 argument(s), given: 2\n"); + } + + Y_UNIT_TEST(ScalarContextUsage1) { + TString query = R"( + $a = (select 1 as x, 2 as y); + select 1 + $a; + )"; + ExpectFailWithError(query, "<main>:2:39: Error: Source used in expression should contain one concrete column\n" + "<main>:3:24: Error: Source is used here\n"); + } + + Y_UNIT_TEST(ScalarContextUsage2) { + TString query = R"( + use plato; + $a = (select 1 as x, 2 as y); + select * from concat($a); + )"; + ExpectFailWithError(query, "<main>:3:39: Error: Source used in expression should contain one concrete column\n" + "<main>:4:34: Error: Source is used here\n"); + } + + Y_UNIT_TEST(ScalarContextUsage3) { + TString query = R"( + use plato; + $a = (select 1 as x, 2 as y); + select * from range($a); + )"; + ExpectFailWithError(query, "<main>:3:39: Error: Source used in expression should contain one concrete column\n" + "<main>:4:33: Error: Source is used here\n"); + } + + Y_UNIT_TEST(ScalarContextUsage4) { + TString query = R"( + use plato; + $a = (select 1 as x, 2 as y); + insert into $a select 1; + )"; + ExpectFailWithError(query, "<main>:3:39: Error: Source used in expression should contain one concrete column\n" + "<main>:4:25: Error: Source is used here\n"); + } +} + +void CheckUnused(const TString& req, const TString& symbol, unsigned row, unsigned col) { + auto res = SqlToYql(req); + + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), TStringBuilder() << "<main>:" << row << ":" << col << ": Warning: Symbol " << symbol << " is not used, code: 4527\n"); +} + +Y_UNIT_TEST_SUITE(WarnUnused) { + Y_UNIT_TEST(ActionOrSubquery) { + TString req = " $a()\n" + "as select 1;\n" + "end define;\n" + "\n" + "select 1;"; + CheckUnused("define action\n" + req, "$a", 2, 3); + CheckUnused("define subquery\n" + req, "$a", 2, 3); + } + + Y_UNIT_TEST(Import) { + TString req = "import lib1 symbols\n" + " $sqr;\n" + "select 1;"; + CheckUnused(req, "$sqr", 2, 3); + + req = "import lib1 symbols\n" + " $sqr as\n" + " $sq;\n" + "select 1;"; + CheckUnused(req, "$sq", 3, 5); + } + + Y_UNIT_TEST(NamedNodeStatement) { + TString req = " $a, $a = AsTuple(1, 2);\n" + "select $a;"; + CheckUnused(req, "$a", 1, 2); + req = "$a, $b = AsTuple(1, 2);\n" + "select $a;"; + CheckUnused(req, "$b", 1, 6); + CheckUnused(" $a = 1; $a = 2; select $a;", "$a", 1, 2); + } + + Y_UNIT_TEST(Declare) { + CheckUnused("declare $a as String;select 1;", "$a", 1, 9); + } + + Y_UNIT_TEST(ActionParams) { + TString req = "define action $a($x, $y) as\n" + " select $x;\n" + "end define;\n" + "\n" + "do $a(1,2);"; + CheckUnused(req, "$y", 1, 22); + } + + Y_UNIT_TEST(SubqueryParams) { + TString req = "use plato;\n" + "define subquery $q($name, $x) as\n" + " select * from $name;\n" + "end define;\n" + "\n" + "select * from $q(\"Input\", 1);"; + CheckUnused(req, "$x", 2, 27); + } + + Y_UNIT_TEST(For) { + TString req = "define action $a() as\n" + " select 1;\n" + "end define;\n" + "\n" + "for $i in ListFromRange(1, 10)\n" + "do $a();"; + CheckUnused(req, "$i", 5, 5); + } + + Y_UNIT_TEST(LambdaParams) { + TString req = "$lambda = ($x, $y) -> ($x);\n" + "select $lambda(1, 2);"; + CheckUnused(req, "$y", 1, 16); + } + + Y_UNIT_TEST(InsideLambdaBody) { + TString req = "$lambda = () -> {\n" + " $x = 1; return 1;\n" + "};\n" + "select $lambda();"; + CheckUnused(req, "$x", 2, 3); + req = "$lambda = () -> {\n" + " $x = 1; $x = 2; return $x;\n" + "};\n" + "select $lambda();"; + CheckUnused(req, "$x", 2, 3); + } + + Y_UNIT_TEST(InsideAction) { + TString req = "define action $a() as\n" + " $x = 1; select 1;\n" + "end define;\n" + "\n" + "do $a();"; + CheckUnused(req, "$x", 2, 3); + req = "define action $a() as\n" + " $x = 1; $x = 2; select $x;\n" + "end define;\n" + "\n" + "do $a();"; + CheckUnused(req, "$x", 2, 3); + } + + Y_UNIT_TEST(NoWarnOnNestedActions) { + auto req = "pragma warning(\"error\", \"4527\");\n" + "define action $action($b) as\n" + " define action $aaa() as\n" + " select $b;\n" + " end define;\n" + " do $aaa();\n" + "end define;\n" + "\n" + "do $action(1);"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(NoWarnForUsageAfterSubquery) { + auto req = "use plato;\n" + "pragma warning(\"error\", \"4527\");\n" + "\n" + "$a = 1;\n" + "\n" + "define subquery $q($table) as\n" + " select * from $table;\n" + "end define;\n" + "\n" + "select * from $q(\"Input\");\n" + "select $a;"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } +} + +Y_UNIT_TEST_SUITE(AnonymousNames) { + Y_UNIT_TEST(ReferenceAnonymousVariableIsForbidden) { + auto req = "$_ = 1; select $_;"; + + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:16: Error: Unable to reference anonymous name $_\n"); + + req = "$`_` = 1; select $`_`;"; + res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:18: Error: Unable to reference anonymous name $_\n"); + } + + Y_UNIT_TEST(Declare) { + auto req = "declare $_ as String;"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:9: Error: Can not use anonymous name '$_' in DECLARE statement\n"); + } + + Y_UNIT_TEST(ActionSubquery) { + auto req = "define action $_() as select 1; end define;"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:15: Error: Can not use anonymous name '$_' as ACTION name\n"); + + req = "define subquery $_() as select 1; end define;"; + res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: Can not use anonymous name '$_' as SUBQUERY name\n"); + } + + Y_UNIT_TEST(Import) { + auto req = "import lib symbols $sqr as $_;"; + auto res = SqlToYql(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:28: Error: Can not import anonymous name $_\n"); + } + + Y_UNIT_TEST(Export) { + auto req = "export $_;"; + auto res = SqlToYqlWithMode(req, NSQLTranslation::ESqlMode::LIBRARY); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:8: Error: Can not export anonymous name $_\n"); + } + + Y_UNIT_TEST(AnonymousInActionArgs) { + auto req = "pragma warning(\"error\", \"4527\");\n" + "define action $a($_, $y, $_) as\n" + " select $y;\n" + "end define;\n" + "\n" + "do $a(1,2,3);"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(AnonymousInSubqueryArgs) { + auto req = "use plato;\n" + "pragma warning(\"error\", \"4527\");\n" + "define subquery $q($_, $y, $_) as\n" + " select * from $y;\n" + "end define;\n" + "\n" + "select * from $q(1,\"Input\",3);"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(AnonymousInLambdaArgs) { + auto req = "pragma warning(\"error\", \"4527\");\n" + "$lambda = ($_, $x, $_) -> ($x);\n" + "select $lambda(1,2,3);"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(AnonymousInFor) { + auto req = "pragma warning(\"error\", \"4527\");\n" + "evaluate for $_ in ListFromRange(1, 10) do begin select 1; end do;"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } + + Y_UNIT_TEST(Assignment) { + auto req = "pragma warning(\"error\", \"4527\");\n" + "$_ = 1;\n" + "$_, $x, $_ = AsTuple(1,2,3);\n" + "select $x;"; + UNIT_ASSERT(SqlToYql(req).IsOk()); + } +} + +Y_UNIT_TEST_SUITE(JsonValue) { + Y_UNIT_TEST(JsonValueArgumentCount) { + NYql::TAstParseResult res = SqlToYql("select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json));"); + + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:49: Error: Unexpected token ')' : syntax error...\n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:49: Error: mismatched input ')' expecting ','\n"); +#endif + } + + Y_UNIT_TEST(JsonValueJsonPathMustBeLiteralString) { + NYql::TAstParseResult res = SqlToYql("$jsonPath = \"strict $.key\"; select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), $jsonPath);"); + + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:79: Error: Unexpected token absence : Missing STRING_VALUE \n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:79: Error: mismatched input '$' expecting STRING_VALUE\n"); +#endif + } + + Y_UNIT_TEST(JsonValueTranslation) { + NYql::TAstParseResult res = SqlToYql("select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\");"); + + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + Y_UNUSED(word); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"strict $.key\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("SafeCast")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("DataType 'Json")); + }; + + TWordCountHive elementStat({"JsonValue"}); + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["JsonValue"]); + } + + Y_UNIT_TEST(JsonValueReturningSection) { + for (const auto& typeName : {"Bool", "Int64", "Double", "String"}) { + NYql::TAstParseResult res = SqlToYql( + TStringBuilder() << "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" RETURNING " << typeName << ");" + ); + + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + Y_UNUSED(word); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'\"strict $.key\"")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("SafeCast")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("DataType 'Json")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(TStringBuilder() << "DataType '" << typeName)); + }; + + TWordCountHive elementStat({typeName}); + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT(elementStat[typeName] > 0); + } + } + + Y_UNIT_TEST(JsonValueInvalidReturningType) { + NYql::TAstParseResult res = SqlToYql("select JSON_VALUE(CAST(@@{'key': 1238}@@ as Json), 'strict $.key' RETURNING invalid);"); + + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:77: Error: Unknown simple type 'invalid'\n"); + } + + Y_UNIT_TEST(JsonValueAndReturningInExpressions) { + NYql::TAstParseResult res = SqlToYql( + "USE plato\n;" + "$json_value = \"some string\";\n" + "SELECT $json_value;\n" + "SELECT 1 as json_value;\n" + "SELECT $json_value as json_value;\n" + "$returning = \"another string\";\n" + "SELECT $returning;\n" + "SELECT 1 as returning;\n" + "SELECT $returning as returning;\n" + ); + + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(JsonValueValidCaseHandlers) { + const TVector<std::pair<TString, TString>> testCases = { + {"", "'DefaultValue (Null)"}, + {"NULL", "'DefaultValue (Null)"}, + {"ERROR", "'Error (Null)"}, + {"DEFAULT 123", "'DefaultValue (Int32 '\"123\")"}, + }; + + for (const auto& onEmpty : testCases) { + for (const auto& onError : testCases) { + TStringBuilder query; + query << "$json = CAST(@@{\"key\": 1238}@@ as Json);\n" + << "SELECT JSON_VALUE($json, \"strict $.key\""; + if (!onEmpty.first.empty()) { + query << " " << onEmpty.first << " ON EMPTY"; + } + if (!onError.first.empty()) { + query << " " << onError.first << " ON ERROR"; + } + query << ");\n"; + + NYql::TAstParseResult res = SqlToYql(query); + + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + Y_UNUSED(word); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(onEmpty.second + " " + onError.second)); + }; + + TWordCountHive elementStat({"JsonValue"}); + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT(elementStat["JsonValue"] > 0); + } + } + } + + Y_UNIT_TEST(JsonValueTooManyCaseHandlers) { + NYql::TAstParseResult res = SqlToYql( + "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" NULL ON EMPTY NULL ON ERROR NULL ON EMPTY);\n" + ); + + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF( + Err2Str(res), + "<main>:1:52: Error: Only 1 ON EMPTY and/or 1 ON ERROR clause is expected\n" + ); + } + + Y_UNIT_TEST(JsonValueTooManyOnEmpty) { + NYql::TAstParseResult res = SqlToYql( + "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" NULL ON EMPTY NULL ON EMPTY);\n" + ); + + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF( + Err2Str(res), + "<main>:1:52: Error: Only 1 ON EMPTY clause is expected\n" + ); + } + + Y_UNIT_TEST(JsonValueTooManyOnError) { + NYql::TAstParseResult res = SqlToYql( + "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" NULL ON ERROR NULL ON ERROR);\n" + ); + + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF( + Err2Str(res), + "<main>:1:52: Error: Only 1 ON ERROR clause is expected\n" + ); + } + + Y_UNIT_TEST(JsonValueOnEmptyAfterOnError) { + NYql::TAstParseResult res = SqlToYql( + "select JSON_VALUE(CAST(@@{\"key\": 1238}@@ as Json), \"strict $.key\" NULL ON ERROR NULL ON EMPTY);\n" + ); + + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF( + Err2Str(res), + "<main>:1:52: Error: ON EMPTY clause must be before ON ERROR clause\n" + ); + } + + Y_UNIT_TEST(JsonValueNullInput) { + NYql::TAstParseResult res = SqlToYql(R"(SELECT JSON_VALUE(NULL, "strict $.key");)"); + + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + Y_UNUSED(word); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Nothing (OptionalType (DataType 'Json)))")); + }; + + TWordCountHive elementStat({"JsonValue"}); + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT(elementStat["JsonValue"] > 0); + } +} + +Y_UNIT_TEST_SUITE(JsonExists) { + Y_UNIT_TEST(JsonExistsValidHandlers) { + const TVector<std::pair<TString, TString>> testCases = { + {"", "(Just (Bool '\"false\"))"}, + {"TRUE ON ERROR", "(Just (Bool '\"true\"))"}, + {"FALSE ON ERROR", "(Just (Bool '\"false\"))"}, + {"UNKNOWN ON ERROR", "(Nothing (OptionalType (DataType 'Bool)))"}, + // NOTE: in this case we expect arguments of JsonExists callable to end immediately + // after variables. This parenthesis at the end of the expression is left on purpose + {"ERROR ON ERROR", "(Utf8 '\"strict $.key\") (JsonVariables))"}, + }; + + for (const auto& item : testCases) { + NYql::TAstParseResult res = SqlToYql( + TStringBuilder() << R"( + $json = CAST(@@{"key": 1238}@@ as Json); + SELECT JSON_EXISTS($json, "strict $.key" )" << item.first << ");\n" + ); + + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + Y_UNUSED(word); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(item.second)); + }; + + TWordCountHive elementStat({"JsonExists"}); + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT(elementStat["JsonExists"] > 0); + } + } + + Y_UNIT_TEST(JsonExistsInvalidHandler) { + NYql::TAstParseResult res = SqlToYql(R"( + $json = CAST(@@{"key": 1238}@@ as Json); + $default = false; + SELECT JSON_EXISTS($json, "strict $.key" $default ON ERROR); + )"); + + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:53: Error: Unexpected token absence : Missing RPAREN \n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:53: Error: mismatched input '$' expecting {')', ERROR, FALSE, TRUE, UNKNOWN}\n"); +#endif + } + + Y_UNIT_TEST(JsonExistsNullInput) { + NYql::TAstParseResult res = SqlToYql(R"(SELECT JSON_EXISTS(NULL, "strict $.key");)"); + + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + Y_UNUSED(word); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Nothing (OptionalType (DataType 'Json)))")); + }; + + TWordCountHive elementStat({"JsonExists"}); + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT(elementStat["JsonExists"] > 0); + } +} + +Y_UNIT_TEST_SUITE(JsonQuery) { + Y_UNIT_TEST(JsonQueryValidHandlers) { + using TTestSuite = const TVector<std::pair<TString, TString>>; + TTestSuite wrapCases = { + {"", "'NoWrap"}, + {"WITHOUT WRAPPER", "'NoWrap"}, + {"WITHOUT ARRAY WRAPPER", "'NoWrap"}, + {"WITH WRAPPER", "'Wrap"}, + {"WITH ARRAY WRAPPER", "'Wrap"}, + {"WITH UNCONDITIONAL WRAPPER", "'Wrap"}, + {"WITH UNCONDITIONAL ARRAY WRAPPER", "'Wrap"}, + {"WITH CONDITIONAL WRAPPER", "'ConditionalWrap"}, + {"WITH CONDITIONAL ARRAY WRAPPER", "'ConditionalWrap"}, + }; + TTestSuite handlerCases = { + {"", "'Null"}, + {"ERROR", "'Error"}, + {"NULL", "'Null"}, + {"EMPTY ARRAY", "'EmptyArray"}, + {"EMPTY OBJECT", "'EmptyObject"}, + }; + + for (const auto& wrap : wrapCases) { + for (const auto& onError : handlerCases) { + for (const auto& onEmpty : handlerCases) { + TStringBuilder query; + query << R"($json = CAST(@@{"key": [123]}@@ as Json); + SELECT JSON_QUERY($json, "strict $.key" )" << wrap.first; + if (!onEmpty.first.empty()) { + if (wrap.first.StartsWith("WITH ")) { + continue; + } + query << " " << onEmpty.first << " ON EMPTY"; + } + if (!onError.first.empty()) { + query << " " << onError.first << " ON ERROR"; + } + query << ");\n"; + + NYql::TAstParseResult res = SqlToYql(query); + + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + Y_UNUSED(word); + const TString args = TStringBuilder() << wrap.second << " " << onEmpty.second << " " << onError.second; + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(args)); + }; + + Cout << wrap.first << " " << onEmpty.first << " " << onError.first << Endl; + + TWordCountHive elementStat({"JsonQuery"}); + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT(elementStat["JsonQuery"] > 0); + } + } + } + } + + Y_UNIT_TEST(JsonQueryOnEmptyWithWrapper) { + NYql::TAstParseResult res = SqlToYql(R"( + $json = CAST(@@{"key": 1238}@@ as Json); + SELECT JSON_QUERY($json, "strict $" WITH ARRAY WRAPPER EMPTY ARRAY ON EMPTY); + )"); + + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:38: Error: ON EMPTY is prohibited because WRAPPER clause is specified\n"); + } + + Y_UNIT_TEST(JsonQueryNullInput) { + NYql::TAstParseResult res = SqlToYql(R"(SELECT JSON_QUERY(NULL, "strict $.key");)"); + + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + Y_UNUSED(word); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("(Nothing (OptionalType (DataType 'Json)))")); + }; + + TWordCountHive elementStat({"JsonQuery"}); + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT(elementStat["JsonQuery"] > 0); + } +} + +Y_UNIT_TEST_SUITE(JsonPassing) { + Y_UNIT_TEST(SupportedVariableTypes) { + const TVector<TString> functions = {"JSON_EXISTS", "JSON_VALUE", "JSON_QUERY"}; + + for (const auto& function : functions) { + const auto query = Sprintf(R"( + pragma CompactNamedExprs; + $json = CAST(@@{"key": 1238}@@ as Json); + SELECT %s( + $json, + "strict $.key" + PASSING + "string" as var1, + 1.234 as var2, + CAST(1 as Int64) as var3, + true as var4, + $json as var5 + ))", + function.data() + ); + NYql::TAstParseResult res = SqlToYql(query); + + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + Y_UNUSED(word); + UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var1" (String '"string")))"), "Cannot find `var1`"); + UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var2" (Double '"1.234")))"), "Cannot find `var2`"); + UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var3" (SafeCast (Int32 '"1") (DataType 'Int64))))"), "Cannot find `var3`"); + UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var4" (Bool '"true")))"), "Cannot find `var4`"); + UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var5" namedexprnode0))"), "Cannot find `var5`"); + }; + + TWordCountHive elementStat({"JsonVariables"}); + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT(elementStat["JsonVariables"] > 0); + } + } + + Y_UNIT_TEST(ValidVariableNames) { + const TVector<TString> functions = {"JSON_EXISTS", "JSON_VALUE", "JSON_QUERY"}; + + for (const auto& function : functions) { + const auto query = Sprintf(R"( + $json = CAST(@@{"key": 1238}@@ as Json); + SELECT %s( + $json, + "strict $.key" + PASSING + "one" as var1, + "two" as "VaR2", + "three" as `var3`, + "four" as VaR4 + ))", + function.data() + ); + NYql::TAstParseResult res = SqlToYql(query); + + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + Y_UNUSED(word); + UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var1" (String '"one")))"), "Cannot find `var1`"); + UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"VaR2" (String '"two")))"), "Cannot find `VaR2`"); + UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"var3" (String '"three")))"), "Cannot find `var3`"); + UNIT_ASSERT_VALUES_UNEQUAL_C(TString::npos, line.find(R"('('"VaR4" (String '"four")))"), "Cannot find `VaR4`"); + }; + + TWordCountHive elementStat({"JsonVariables"}); + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT(elementStat["JsonVariables"] > 0); + } + } +} + +Y_UNIT_TEST_SUITE(MigrationToJsonApi) { + Y_UNIT_TEST(WarningOnDeprecatedJsonUdf) { + NYql::TAstParseResult res = SqlToYql(R"( + $json = CAST(@@{"key": 1234}@@ as Json); + SELECT Json::Parse($json); + )"); + + UNIT_ASSERT(res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:26: Warning: Json UDF is deprecated. Please use JSON API instead, code: 4506\n"); + } +} + +Y_UNIT_TEST_SUITE(AnsiIdentsNegative) { + Y_UNIT_TEST(EnableAnsiLexerFromRequestSpecialComments) { + auto req = "\n" + "\t --!ansi_lexer \n" + "-- Some comment\n" + "-- another comment\n" + "pragma SimpleColumns;\n" + "\n" + "select 1, '''' as empty;"; + + auto res = SqlToYql(req); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + } + + Y_UNIT_TEST(AnsiLexerShouldNotBeEnabledHere) { + auto req = "$str = '\n" + "--!ansi_lexer\n" + "--!syntax_v1\n" + "';\n" + "\n" + "select 1, $str, \"\" as empty;"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + } + + Y_UNIT_TEST(DoubleQuotesInDictsTuplesOrLists) { + auto req = "$d = { 'a': 1, \"b\": 2, 'c': 3,};"; + + auto res = SqlToYqlWithAnsiLexer(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:16: Error: Column reference \"b\" is not allowed in current scope\n"); + + req = "$t = (1, 2, \"a\");"; + + res = SqlToYqlWithAnsiLexer(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:13: Error: Column reference \"a\" is not allowed in current scope\n"); + + req = "$l = ['a', 'b', \"c\"];"; + + res = SqlToYqlWithAnsiLexer(req); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:17: Error: Column reference \"c\" is not allowed in current scope\n"); + } + + Y_UNIT_TEST(MultilineComments) { + auto req = "/*/**/ select 1;"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + res = SqlToYqlWithAnsiLexer(req); +#if ANTLR_VER == 3 + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:16: Error: Unexpected character : syntax error...\n\n"); +#else + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); +#endif + req = "/*\n" + "--/*\n" + "*/ select 1;"; + res = SqlToYql(req); + UNIT_ASSERT(res.Root); + res = SqlToYqlWithAnsiLexer(req); +#if ANTLR_VER == 3 + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:12: Error: Unexpected character : syntax error...\n\n"); +#else + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); +#endif + req = "/*\n" + "/*\n" + "--*/\n" + "*/ select 1;"; + res = SqlToYql(req); + UNIT_ASSERT(!res.Root); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:0: Error: Unexpected token '*' : cannot match to any predicted input...\n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:4:0: Error: mismatched input '*' expecting {';', '(', '$', ALTER, ANALYZE, BACKUP, BATCH, COMMIT, CREATE, DECLARE, DEFINE, DELETE, DISCARD, DO, DROP, EVALUATE, EXPLAIN, EXPORT, FOR, FROM, GRANT, IF, IMPORT, INSERT, PARALLEL, PRAGMA, PROCESS, REDUCE, REPLACE, RESTORE, REVOKE, ROLLBACK, SELECT, SHOW, UPDATE, UPSERT, USE, VALUES}\n"); +#endif + res = SqlToYqlWithAnsiLexer(req); + UNIT_ASSERT(res.Root); + } +} + +Y_UNIT_TEST_SUITE(AnsiOptionalAs) { + Y_UNIT_TEST(OptionalAsInProjection) { + UNIT_ASSERT(SqlToYql("PRAGMA AnsiOptionalAs; SELECT a b, c FROM plato.Input;").IsOk()); + ExpectFailWithError("PRAGMA DisableAnsiOptionalAs;\n" + "SELECT a b, c FROM plato.Input;", + "<main>:2:10: Error: Expecting mandatory AS here. Did you miss comma? Please add PRAGMA AnsiOptionalAs; for ANSI compatibility\n"); + } + + Y_UNIT_TEST(OptionalAsWithKeywords) { + UNIT_ASSERT(SqlToYql("PRAGMA AnsiOptionalAs; SELECT a type, b data, c source FROM plato.Input;").IsOk()); + } +} + +Y_UNIT_TEST_SUITE(SessionWindowNegative) { + Y_UNIT_TEST(SessionWindowWithoutSource) { + ExpectFailWithError("SELECT 1 + SessionWindow(ts, 32);", + "<main>:1:12: Error: SessionWindow requires data source\n"); + } + + Y_UNIT_TEST(SessionWindowInProjection) { + ExpectFailWithError("SELECT 1 + SessionWindow(ts, 32) from plato.Input;", + "<main>:1:12: Error: SessionWindow can only be used as a top-level GROUP BY / PARTITION BY expression\n"); + } + + Y_UNIT_TEST(SessionWindowWithNonConstSecondArg) { + ExpectFailWithError( + "SELECT key, session_start FROM plato.Input\n" + "GROUP BY SessionWindow(ts, 32 + subkey) as session_start, key;", + + "<main>:2:10: Error: Source does not allow column references\n" + "<main>:2:33: Error: Column reference 'subkey'\n"); + } + + Y_UNIT_TEST(SessionWindowWithWrongNumberOfArgs) { + ExpectFailWithError("SELECT * FROM plato.Input GROUP BY SessionWindow()", + "<main>:1:36: Error: SessionWindow requires either two or four arguments\n"); + ExpectFailWithError("SELECT * FROM plato.Input GROUP BY SessionWindow(key, subkey, 100)", + "<main>:1:36: Error: SessionWindow requires either two or four arguments\n"); + } + + Y_UNIT_TEST(DuplicateSessionWindow) { + ExpectFailWithError( + "SELECT\n" + " *\n" + "FROM plato.Input\n" + "GROUP BY\n" + " SessionWindow(ts, 10),\n" + " user,\n" + " SessionWindow(ts, 20)\n" + ";", + + "<main>:7:5: Error: Duplicate session window specification:\n" + "<main>:5:5: Error: Previous session window is declared here\n"); + + ExpectFailWithError( + "SELECT\n" + " MIN(key) over w\n" + "FROM plato.Input\n" + "WINDOW w AS (\n" + " PARTITION BY SessionWindow(ts, 10), user,\n" + " SessionWindow(ts, 20)\n" + ");", + + "<main>:6:5: Error: Duplicate session window specification:\n" + "<main>:5:18: Error: Previous session window is declared here\n"); + } + + Y_UNIT_TEST(SessionStartStateWithoutSource) { + ExpectFailWithError("SELECT 1 + SessionStart();", + "<main>:1:12: Error: SessionStart requires data source\n"); + ExpectFailWithError("SELECT 1 + SessionState();", + "<main>:1:12: Error: SessionState requires data source\n"); + } + + Y_UNIT_TEST(SessionStartStateWithoutGroupByOrWindow) { + ExpectFailWithError("SELECT 1 + SessionStart() from plato.Input;", + "<main>:1:12: Error: SessionStart can not be used without aggregation by SessionWindow\n"); + ExpectFailWithError("SELECT 1 + SessionState() from plato.Input;", + "<main>:1:12: Error: SessionState can not be used without aggregation by SessionWindow\n"); + } + + Y_UNIT_TEST(SessionStartStateWithGroupByWithoutSession) { + ExpectFailWithError("SELECT 1 + SessionStart() from plato.Input group by user;", + "<main>:1:12: Error: SessionStart can not be used here: SessionWindow specification is missing in GROUP BY\n"); + ExpectFailWithError("SELECT 1 + SessionState() from plato.Input group by user;", + "<main>:1:12: Error: SessionState can not be used here: SessionWindow specification is missing in GROUP BY\n"); + } + + Y_UNIT_TEST(SessionStartStateWithoutOverWithWindowWithoutSession) { + ExpectFailWithError("SELECT 1 + SessionStart(), MIN(key) over w from plato.Input window w as ()", + "<main>:1:12: Error: SessionStart can not be used without aggregation by SessionWindow. Maybe you forgot to add OVER `window_name`?\n"); + ExpectFailWithError("SELECT 1 + SessionState(), MIN(key) over w from plato.Input window w as ()", + "<main>:1:12: Error: SessionState can not be used without aggregation by SessionWindow. Maybe you forgot to add OVER `window_name`?\n"); + } + + Y_UNIT_TEST(SessionStartStateWithWindowWithoutSession) { + ExpectFailWithError("SELECT 1 + SessionStart() over w, MIN(key) over w from plato.Input window w as ()", + "<main>:1:12: Error: SessionStart can not be used with window w: SessionWindow specification is missing in PARTITION BY\n"); + ExpectFailWithError("SELECT 1 + SessionState() over w, MIN(key) over w from plato.Input window w as ()", + "<main>:1:12: Error: SessionState can not be used with window w: SessionWindow specification is missing in PARTITION BY\n"); + } + + Y_UNIT_TEST(SessionStartStateWithSessionedWindow) { + ExpectFailWithError("SELECT 1 + SessionStart(), MIN(key) over w from plato.Input group by key window w as (partition by SessionWindow(ts, 1)) ", + "<main>:1:12: Error: SessionStart can not be used here: SessionWindow specification is missing in GROUP BY. Maybe you forgot to add OVER `window_name`?\n"); + ExpectFailWithError("SELECT 1 + SessionState(), MIN(key) over w from plato.Input group by key window w as (partition by SessionWindow(ts, 1)) ", + "<main>:1:12: Error: SessionState can not be used here: SessionWindow specification is missing in GROUP BY. Maybe you forgot to add OVER `window_name`?\n"); + } + + Y_UNIT_TEST(AggregationBySessionStateIsNotSupportedYet) { + ExpectFailWithError("SELECT SOME(1 + SessionState()), key from plato.Input group by key, SessionWindow(ts, 1);", + "<main>:1:17: Error: SessionState with GROUP BY is not supported yet\n"); + } + + Y_UNIT_TEST(SessionWindowInRtmr) { + NYql::TAstParseResult res = SqlToYql( + "SELECT * FROM plato.Input GROUP BY SessionWindow(ts, 10);", + 10, TString(NYql::RtmrProviderName)); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:54: Error: Streaming group by query must have a hopping window specification.\n"); + + res = SqlToYql(R"( + SELECT key, SUM(value) AS value FROM plato.Input + GROUP BY key, HOP(subkey, "PT10S", "PT30S", "PT20S"), SessionWindow(ts, 10); + )", 10, TString(NYql::RtmrProviderName)); + + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:2:13: Error: SessionWindow is unsupported for streaming sources\n"); + } +} + +Y_UNIT_TEST_SUITE(LibraSqlSugar) { + auto makeResult = [](TStringBuf settings) { + return SqlToYql( + TStringBuilder() + << settings + << "\n$udf1 = MyLibra::MakeLibraPreprocessor($settings);" + << "\n$udf2 = CustomLibra::MakeLibraPreprocessor($settings);" + << "\nPROCESS plato.Input USING $udf1(TableRow())" + << "\nUNION ALL" + << "\nPROCESS plato.Input USING $udf2(TableRow());" + ); + }; + + Y_UNIT_TEST(EmptySettings) { + auto res = makeResult(R"( + $settings = AsStruct(); + )"); + UNIT_ASSERT(res.IsOk()); + } + + Y_UNIT_TEST(OnlyEntities) { + auto res = makeResult(R"( + $settings = AsStruct( + AsList("A", "B", "C") AS Entities + ); + )"); + UNIT_ASSERT(res.IsOk()); + } + + Y_UNIT_TEST(EntitiesWithStrategy) { + auto res = makeResult(R"( + $settings = AsStruct( + AsList("A", "B", "C") AS Entities, + "blacklist" AS EntitiesStrategy + ); + )"); + UNIT_ASSERT(res.IsOk()); + } + + Y_UNIT_TEST(AllSettings) { + auto res = makeResult(R"( + $settings = AsStruct( + AsList("A", "B", "C") AS Entities, + "whitelist" AS EntitiesStrategy, + "path" AS BlockstatDict, + false AS ParseWithFat, + "map" AS Mode + ); + )"); + UNIT_ASSERT(res.IsOk()); + } + + Y_UNIT_TEST(BadStrategy) { + auto res = makeResult(R"( + $settings = AsStruct("bad" AS EntitiesStrategy); + )"); + UNIT_ASSERT_STRING_CONTAINS( + Err2Str(res), + "Error: MakeLibraPreprocessor got invalid entities strategy: expected 'whitelist' or 'blacklist'" + ); + } + + Y_UNIT_TEST(BadEntities) { + auto res = makeResult(R"( + $settings = AsStruct(AsList("A", 1) AS Entities); + )"); + UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "Error: MakeLibraPreprocessor entity must be string literal"); + } +} + +Y_UNIT_TEST_SUITE(TrailingQuestionsNegative) { + Y_UNIT_TEST(Basic) { + ExpectFailWithError("SELECT 1?;", "<main>:1:9: Error: Unexpected token '?' at the end of expression\n"); +#if ANTLR_VER == 3 + ExpectFailWithError("SELECT 1? + 1;", "<main>:1:10: Error: Unexpected token '+' : cannot match to any predicted input...\n\n"); +#else + ExpectFailWithError("SELECT 1? + 1;", "<main>:1:10: Error: mismatched input '+' expecting {<EOF>, ';'}\n"); +#endif + ExpectFailWithError("SELECT 1 + 1??? < 2", "<main>:1:13: Error: Unexpected token '?' at the end of expression\n"); + ExpectFailWithError("SELECT 1? > 2? > 3?", + "<main>:1:11: Error: Unexpected token '?' at the end of expression\n" + "<main>:1:16: Error: Unexpected token '?' at the end of expression\n" + "<main>:1:21: Error: Unexpected token '?' at the end of expression\n"); + } + + Y_UNIT_TEST(SmartParen) { + ExpectFailWithError("$x = 1; SELECT (Int32?, $x?)", "<main>:1:27: Error: Unexpected token '?' at the end of expression\n"); + ExpectFailWithError("SELECT (Int32, foo?)", "<main>:1:19: Error: Unexpected token '?' at the end of expression\n"); + } + + Y_UNIT_TEST(LambdaOptArgs) { + ExpectFailWithError("$l = ($x, $y?, $z??, $t?) -> ($x);", "<main>:1:18: Error: Expecting at most one '?' token here (for optional lambda parameters), but got 2\n"); + } +} + +Y_UNIT_TEST_SUITE(FlexibleTypes) { + Y_UNIT_TEST(AssumeOrderByType) { + UNIT_ASSERT(SqlToYql("PRAGMA FlexibleTypes; SELECT 1 AS int32 ASSUME ORDER BY int32").IsOk()); + } + + Y_UNIT_TEST(GroupingSets) { + UNIT_ASSERT(SqlToYql("PRAGMA FlexibleTypes; SELECT COUNT(*) AS cnt, text, uuid FROM plato.Input GROUP BY GROUPING SETS((uuid), (uuid, text));").IsOk()); + } + + Y_UNIT_TEST(WeakField) { + UNIT_ASSERT(SqlToYql("PRAGMA FlexibleTypes; SELECT WeakField(text, string) as text FROM plato.Input").IsOk()); + } + + Y_UNIT_TEST(Aggregation1) { + TString q = + "PRAGMA FlexibleTypes;\n" + "$foo = ($x, $const, $type) -> ($x || $const || FormatType($type));\n" + "SELECT $foo(SOME(x), 'aaa', String) FROM plato.Input GROUP BY y;"; + UNIT_ASSERT(SqlToYql(q).IsOk()); + } + + Y_UNIT_TEST(Aggregation2) { + TString q = + "PRAGMA FlexibleTypes;\n" + "SELECT 1 + String + MAX(key) FROM plato.Input;"; + UNIT_ASSERT(SqlToYql(q).IsOk()); + } +} + +Y_UNIT_TEST_SUITE(ExternalDeclares) { + Y_UNIT_TEST(BasicUsage) { + NSQLTranslation::TTranslationSettings settings; + settings.DeclaredNamedExprs["foo"] = "String"; + auto res = SqlToYqlWithSettings("select $foo;", settings); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "declare") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"__((declare "$foo" (DataType 'String)))__")); + } + }; + + TWordCountHive elementStat = {{TString("declare"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["declare"]); + } + + Y_UNIT_TEST(DeclareOverrides) { + NSQLTranslation::TTranslationSettings settings; + settings.DeclaredNamedExprs["foo"] = "String"; + auto res = SqlToYqlWithSettings("declare $foo as Int32; select $foo;", settings); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "declare") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"__((declare "$foo" (DataType 'Int32)))__")); + } + }; + + TWordCountHive elementStat = {{TString("declare"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["declare"]); + } + + Y_UNIT_TEST(UnusedDeclareDoesNotProduceWarning) { + NSQLTranslation::TTranslationSettings settings; + settings.DeclaredNamedExprs["foo"] = "String"; + auto res = SqlToYqlWithSettings("select 1;", settings); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "declare") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"__((declare "$foo" (DataType 'String)))__")); + } + }; + + TWordCountHive elementStat = {{TString("declare"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["declare"]); + } + + Y_UNIT_TEST(DeclaresWithInvalidTypesFails) { + NSQLTranslation::TTranslationSettings settings; + settings.DeclaredNamedExprs["foo"] = "List<BadType>"; + auto res = SqlToYqlWithSettings("select 1;", settings); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_NO_DIFF(Err2Str(res), + "<main>:0:5: Error: Unknown type: 'BadType'\n" + "<main>: Error: Failed to parse type for externally declared name 'foo'\n"); + } +} + +Y_UNIT_TEST_SUITE(ExternalDataSource) { + Y_UNIT_TEST(CreateExternalDataSourceWithAuthNone) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="ObjectStorage", + LOCATION="my-bucket", + AUTH_METHOD="NONE" + ); + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"NONE") '('"location" '"my-bucket") '('"source_type" '"ObjectStorage"))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateExternalDataSourceWithAuthServiceAccount) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="ObjectStorage", + LOCATION="my-bucket", + AUTH_METHOD="SERVICE_ACCOUNT", + SERVICE_ACCOUNT_ID="sa", + SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name" + ); + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"SERVICE_ACCOUNT") '('"location" '"my-bucket") '('"service_account_id" '"sa") '('"service_account_secret_name" '"sa_secret_name") '('"source_type" '"ObjectStorage"))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateExternalDataSourceWithBasic) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="PostgreSQL", + LOCATION="protocol://host:port/", + AUTH_METHOD="BASIC", + LOGIN="admin", + PASSWORD_SECRET_NAME="secret_name" + ); + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"BASIC") '('"location" '"protocol://host:port/") '('"login" '"admin") '('"password_secret_name" '"secret_name") '('"source_type" '"PostgreSQL"))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateExternalDataSourceWithMdbBasic) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="PostgreSQL", + LOCATION="protocol://host:port/", + AUTH_METHOD="MDB_BASIC", + SERVICE_ACCOUNT_ID="sa", + SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name", + LOGIN="admin", + PASSWORD_SECRET_NAME="secret_name" + ); + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"MDB_BASIC") '('"location" '"protocol://host:port/") '('"login" '"admin") '('"password_secret_name" '"secret_name") '('"service_account_id" '"sa") '('"service_account_secret_name" '"sa_secret_name") '('"source_type" '"PostgreSQL"))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateExternalDataSourceWithAws) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="PostgreSQL", + LOCATION="protocol://host:port/", + AUTH_METHOD="AWS", + AWS_ACCESS_KEY_ID_SECRET_NAME="secred_id_name", + AWS_SECRET_ACCESS_KEY_SECRET_NAME="secret_key_name", + AWS_REGION="ru-central-1" + ); + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"AWS") '('"aws_access_key_id_secret_name" '"secred_id_name") '('"aws_region" '"ru-central-1") '('"aws_secret_access_key_secret_name" '"secret_key_name") '('"location" '"protocol://host:port/") '('"source_type" '"PostgreSQL"))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateExternalDataSourceWithToken) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="YT", + LOCATION="protocol://host:port/", + AUTH_METHOD="TOKEN", + TOKEN_SECRET_NAME="token_name" + ); + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"TOKEN") '('"location" '"protocol://host:port/") '('"source_type" '"YT") '('"token_secret_name" '"token_name"))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateExternalDataSourceWithTablePrefix) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + pragma TablePathPrefix='/aba'; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="ObjectStorage", + LOCATION="my-bucket", + AUTH_METHOD="NONE" + ); + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "/aba/MyDataSource"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateExternalDataSourceIfNotExists) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE IF NOT EXISTS MyDataSource WITH ( + SOURCE_TYPE="ObjectStorage", + LOCATION="my-bucket", + AUTH_METHOD="NONE" + ); + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"NONE") '('"location" '"my-bucket") '('"source_type" '"ObjectStorage"))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObjectIfNotExists")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(AlterExternalDataSource) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + ALTER EXTERNAL DATA SOURCE MyDataSource + SET (SOURCE_TYPE = "ObjectStorage", Login = "Admin"), + SET Location "bucket", + RESET (Auth_Method, Service_Account_Id, Service_Account_Secret_Name); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alterObject))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('features '('('"location" '"bucket") '('"login" '"Admin") '('"source_type" '"ObjectStorage"))))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('resetFeatures '('"auth_method" '"service_account_id" '"service_account_secret_name")))#"); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateExternalDataSourceOrReplace) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; + CREATE OR REPLACE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="ObjectStorage", + LOCATION="my-bucket", + AUTH_METHOD="NONE" + ); + )"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"auth_method" '"NONE") '('"location" '"my-bucket") '('"source_type" '"ObjectStorage"))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObjectOrReplace")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateOrReplaceForUnsupportedTableTypesShouldFail) { + ExpectFailWithError(R"sql( + USE plato; + CREATE OR REPLACE TABLE t (a int32 not null, primary key(a, a)); + )sql" , "<main>:3:23: Error: OR REPLACE feature is supported only for EXTERNAL DATA SOURCE and EXTERNAL TABLE\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE OR REPLACE TABLE t ( + Key Uint64, + Value1 String, + PRIMARY KEY (Key) + ) + WITH ( + STORE = COLUMN, + AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = 10 + ); + )sql" , "<main>:3:23: Error: OR REPLACE feature is supported only for EXTERNAL DATA SOURCE and EXTERNAL TABLE\n"); + } + + Y_UNIT_TEST(CreateExternalDataSourceWithBadArguments) { +#if ANTLR_VER == 3 + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource; + )sql" , "<main>:3:52: Error: Unexpected token ';' : syntax error...\n\n"); +#else + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource; + )sql" , "<main>:3:52: Error: mismatched input ';' expecting WITH\n"); +#endif + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + LOCATION="my-bucket", + AUTH_METHOD="NONE" + ); + )sql" , "<main>:5:33: Error: SOURCE_TYPE requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="ObjectStorage", + LOCATION="my-bucket" + ); + )sql" , "<main>:5:30: Error: AUTH_METHOD requires key\n"); + + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="ObjectStorage", + LOCATION="my-bucket", + AUTH_METHOD="NONE1" + ); + )sql" , "<main>:6:33: Error: Unknown AUTH_METHOD = NONE1\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="ObjectStorage", + LOCATION="my-bucket", + AUTH_METHOD="SERVICE_ACCOUNT" + ); + )sql" , "<main>:6:33: Error: SERVICE_ACCOUNT_ID requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="ObjectStorage", + LOCATION="my-bucket", + AUTH_METHOD="SERVICE_ACCOUNT", + SERVICE_ACCOUNT_ID="s1" + ); + )sql" , "<main>:7:40: Error: SERVICE_ACCOUNT_SECRET_NAME requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="ObjectStorage", + LOCATION="my-bucket", + AUTH_METHOD="SERVICE_ACCOUNT", + SERVICE_ACCOUNT_SECRET_NAME="s1" + ); + )sql" , "<main>:7:49: Error: SERVICE_ACCOUNT_ID requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="PostgreSQL", + LOCATION="protocol://host:port/", + AUTH_METHOD="BASIC", + LOGIN="admin" + ); + )sql" , "<main>:7:27: Error: PASSWORD_SECRET_NAME requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="PostgreSQL", + LOCATION="protocol://host:port/", + AUTH_METHOD="BASIC", + PASSWORD_SECRET_NAME="secret_name" + ); + )sql" , "<main>:7:42: Error: LOGIN requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="PostgreSQL", + LOCATION="protocol://host:port/", + AUTH_METHOD="MDB_BASIC", + SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name", + LOGIN="admin", + PASSWORD_SECRET_NAME="secret_name" + ); + )sql" , "<main>:9:42: Error: SERVICE_ACCOUNT_ID requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="PostgreSQL", + LOCATION="protocol://host:port/", + AUTH_METHOD="MDB_BASIC", + SERVICE_ACCOUNT_ID="sa", + LOGIN="admin", + PASSWORD_SECRET_NAME="secret_name" + ); + )sql" , "<main>:9:42: Error: SERVICE_ACCOUNT_SECRET_NAME requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="PostgreSQL", + LOCATION="protocol://host:port/", + AUTH_METHOD="MDB_BASIC", + SERVICE_ACCOUNT_ID="sa", + SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name", + PASSWORD_SECRET_NAME="secret_name" + ); + )sql" , "<main>:9:42: Error: LOGIN requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="PostgreSQL", + LOCATION="protocol://host:port/", + AUTH_METHOD="MDB_BASIC", + SERVICE_ACCOUNT_ID="sa", + SERVICE_ACCOUNT_SECRET_NAME="sa_secret_name", + LOGIN="admin" + ); + )sql" , "<main>:9:27: Error: PASSWORD_SECRET_NAME requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="PostgreSQL", + LOCATION="protocol://host:port/", + AUTH_METHOD="AWS", + AWS_SECRET_ACCESS_KEY_SECRET_NAME="secret_key_name", + AWS_REGION="ru-central-1" + ); + )sql" , "<main>:8:32: Error: AWS_ACCESS_KEY_ID_SECRET_NAME requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="PostgreSQL", + LOCATION="protocol://host:port/", + AUTH_METHOD="AWS", + AWS_ACCESS_KEY_ID_SECRET_NAME="secred_id_name", + AWS_REGION="ru-central-1" + ); + )sql" , "<main>:8:32: Error: AWS_SECRET_ACCESS_KEY_SECRET_NAME requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL DATA SOURCE MyDataSource WITH ( + SOURCE_TYPE="PostgreSQL", + LOCATION="protocol://host:port/", + AUTH_METHOD="AWS", + AWS_SECRET_ACCESS_KEY_SECRET_NAME="secret_key_name", + AWS_ACCESS_KEY_ID_SECRET_NAME="secred_id_name" + ); + )sql" , "<main>:8:51: Error: AWS_REGION requires key\n"); + } + + Y_UNIT_TEST(DropExternalDataSourceWithTablePrefix) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + DROP EXTERNAL DATA SOURCE MyDataSource; + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DropExternalDataSourceIfExists) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + DROP EXTERNAL DATA SOURCE IF EXISTS MyDataSource; + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "MyDataSource"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObjectIfExists")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DropExternalDataSource) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + pragma TablePathPrefix='/aba'; + DROP EXTERNAL DATA SOURCE MyDataSource; + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "/aba/MyDataSource"); + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } +} + +Y_UNIT_TEST_SUITE(ExternalTable) { + Y_UNIT_TEST(CreateExternalTable) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE EXTERNAL TABLE mytable ( + a int + ) WITH ( + DATA_SOURCE="/Root/mydatasource", + LOCATION="/folder1/*" + ); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('data_source_path (String '"/Root/mydatasource")) '('location (String '"/folder1/*")))) '('tableType 'externalTable)))))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tablescheme")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateExternalTableWithTablePrefix) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + pragma TablePathPrefix='/aba'; + CREATE EXTERNAL TABLE mytable ( + a int + ) WITH ( + DATA_SOURCE="mydatasource", + LOCATION="/folder1/*" + ); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "/aba/mydatasource"); + UNIT_ASSERT_STRING_CONTAINS(line, "/aba/mytable"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tablescheme")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateExternalTableObjectStorage) { + auto res = SqlToYql(R"sql( + USE plato; + CREATE EXTERNAL TABLE mytable ( + a int, + year Int + ) WITH ( + DATA_SOURCE="/Root/mydatasource", + LOCATION="/folder1/*", + FORMAT="json_as_string", + `projection.enabled`="true", + `projection.year.type`="integer", + `projection.year.min`="2010", + `projection.year.max`="2022", + `projection.year.interval`="1", + `projection.month.type`="integer", + `projection.month.min`="1", + `projection.month.max`="12", + `projection.month.interval`="1", + `projection.month.digits`="2", + `storage.location.template`="${year}/${month}", + PARTITONED_BY = "[year, month]" + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToString()); + } + + Y_UNIT_TEST(CreateExternalTableIfNotExists) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE EXTERNAL TABLE IF NOT EXISTS mytable ( + a int + ) WITH ( + DATA_SOURCE="/Root/mydatasource", + LOCATION="/folder1/*" + ); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('data_source_path (String '"/Root/mydatasource")) '('location (String '"/folder1/*")))) '('tableType 'externalTable)))))#"); + UNIT_ASSERT_STRING_CONTAINS(line, "create_if_not_exists"); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateExternalTableOrReplace) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; + CREATE OR REPLACE EXTERNAL TABLE mytable ( + a int + ) WITH ( + DATA_SOURCE="/Root/mydatasource", + LOCATION="/folder1/*" + ); + )"); + UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('data_source_path (String '"/Root/mydatasource")) '('location (String '"/folder1/*")))) '('tableType 'externalTable)))))#"); + UNIT_ASSERT_STRING_CONTAINS(line, "create_or_replace"); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(AlterExternalTableAddColumn) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + ALTER EXTERNAL TABLE mytable + ADD COLUMN my_column int32, + RESET (LOCATION); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('actions '('('addColumns '('('"my_column" (AsOptionalType (DataType 'Int32))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('setTableSettings '('('location)))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('tableType 'externalTable))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alter))#"); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(AlterExternalTableDropColumn) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + ALTER EXTERNAL TABLE mytable + DROP COLUMN my_column, + SET (Location = "abc", Other_Prop = "42"), + SET x 'y'; + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToOneLineString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('actions '('('dropColumns '('"my_column")#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('setTableSettings '('('location (String '"abc")) '('Other_Prop (String '"42")) '('x (String '"y")))))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('tableType 'externalTable))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alter))#"); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateExternalTableWithBadArguments) { +#if ANTLR_VER == 3 + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL TABLE mytable; + )sql" , "<main>:3:41: Error: Unexpected token ';' : syntax error...\n\n"); +#else + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL TABLE mytable; + )sql" , "<main>:3:45: Error: mismatched input ';' expecting '('\n"); +#endif + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL TABLE mytable ( + a int + ); + )sql" , "<main>:4:23: Error: DATA_SOURCE requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL TABLE mytable ( + a int + ) WITH ( + DATA_SOURCE="/Root/mydatasource" + ); + )sql" , "<main>:6:33: Error: LOCATION requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL TABLE mytable ( + a int + ) WITH ( + LOCATION="/folder1/*" + ); + )sql" , "<main>:6:30: Error: DATA_SOURCE requires key\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE EXTERNAL TABLE mytable ( + a int, + PRIMARY KEY(a) + ) WITH ( + DATA_SOURCE="/Root/mydatasource", + LOCATION="/folder1/*" + ); + )sql" , "<main>:8:30: Error: PRIMARY KEY is not supported for external table\n"); + } + + Y_UNIT_TEST(DropExternalTable) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + DROP EXTERNAL TABLE MyExternalTable; + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("tablescheme")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DropExternalTableWithTablePrefix) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + pragma TablePathPrefix='/aba'; + DROP EXTERNAL TABLE MyExternalTable; + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "/aba/MyExternalTable"); + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'tablescheme")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DropExternalTableIfExists) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + DROP EXTERNAL TABLE IF EXISTS MyExternalTable; + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("tablescheme")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("drop_if_exists")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } +} + +Y_UNIT_TEST_SUITE(TopicsDDL) { + void TestQuery(const TString& query, bool expectOk = true) { + TStringBuilder finalQuery; + + finalQuery << "use plato;" << Endl << query; + auto res = SqlToYql(finalQuery, 10, "kikimr"); + if (expectOk) { + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToString()); + } else { + UNIT_ASSERT(!res.IsOk()); + } + } + + Y_UNIT_TEST(CreateTopicSimple) { + TestQuery(R"( + CREATE TOPIC topic1; + )"); + TestQuery(R"( + CREATE TOPIC `cluster1.topic1`; + )"); + TestQuery(R"( + CREATE TOPIC topic1 WITH (metering_mode = "str_value", partition_count_limit = 123, retention_period = Interval('PT1H')); + )"); + } + + Y_UNIT_TEST(CreateTopicConsumer) { + TestQuery(R"( + CREATE TOPIC topic1 (CONSUMER cons1); + )"); + TestQuery(R"( + CREATE TOPIC topic1 (CONSUMER cons1, CONSUMER cons2 WITH (important = false)); + )"); + TestQuery(R"( + CREATE TOPIC topic1 (CONSUMER cons1, CONSUMER cons2 WITH (important = false)) WITH (supported_codecs = "1,2,3"); + )"); + } + + Y_UNIT_TEST(AlterTopicSimple) { + TestQuery(R"( + ALTER TOPIC topic1 SET (retention_period = Interval('PT1H')); + )"); + TestQuery(R"( + ALTER TOPIC topic1 SET (retention_storage_mb = 3, partition_count_limit = 50); + )"); + TestQuery(R"( + ALTER TOPIC topic1 RESET (supported_codecs, retention_period); + )"); + TestQuery(R"( + ALTER TOPIC topic1 RESET (partition_write_speed_bytes_per_second), + SET (partition_write_burst_bytes = 11111, min_active_partitions = 1); + )"); + } + Y_UNIT_TEST(AlterTopicConsumer) { + TestQuery(R"( + ALTER TOPIC topic1 ADD CONSUMER consumer1, + ADD CONSUMER consumer2 WITH (important = false, supported_codecs = "RAW"), + ALTER CONSUMER consumer3 SET (important = false, read_from = 1), + ALTER CONSUMER consumer3 RESET (supported_codecs), + DROP CONSUMER consumer4, + SET (partition_count_limit = 11, retention_period = Interval('PT1H')), + RESET(metering_mode) + )"); + } + Y_UNIT_TEST(DropTopic) { + TestQuery(R"( + DROP TOPIC topic1; + )"); + } + + Y_UNIT_TEST(TopicBadRequests) { + TestQuery(R"( + CREATE TOPIC topic1(); + )", false); + TestQuery(R"( + CREATE TOPIC topic1 SET setting1 = value1; + )", false); + TestQuery(R"( + ALTER TOPIC topic1 SET setting1 value1; + )", false); + TestQuery(R"( + ALTER TOPIC topic1 RESET setting1; + )", false); + + TestQuery(R"( + ALTER TOPIC topic1 DROP CONSUMER consumer4 WITH (k1 = v1); + )", false); + + TestQuery(R"( + CREATE TOPIC topic1 WITH (retention_period = 123); + )", false); + TestQuery(R"( + CREATE TOPIC topic1 (CONSUMER cons1, CONSUMER cons1 WITH (important = false)); + )", false); + TestQuery(R"( + CREATE TOPIC topic1 (CONSUMER cons1 WITH (bad_option = false)); + )", false); + TestQuery(R"( + ALTER TOPIC topic1 ADD CONSUMER cons1, ALTER CONSUMER cons1 RESET (important); + )", false); + TestQuery(R"( + ALTER TOPIC topic1 ADD CONSUMER consumer1, + ALTER CONSUMER consumer3 SET (supported_codecs = "RAW", read_from = 1), + ALTER CONSUMER consumer3 RESET (supported_codecs); + )", false); + TestQuery(R"( + ALTER TOPIC topic1 ADD CONSUMER consumer1, + ALTER CONSUMER consumer3 SET (supported_codecs = "RAW", read_from = 1), + ALTER CONSUMER consumer3 SET (read_from = 2); + )", false); + } + + Y_UNIT_TEST(TopicWithPrefix) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; + PRAGMA TablePathPrefix = '/database/path/to/tables'; + ALTER TOPIC `my_table/my_feed` ADD CONSUMER `my_consumer`; + )"); + UNIT_ASSERT(res.Root); + + TWordCountHive elementStat = {{TString("/database/path/to/tables/my_table/my_feed"), 0}, {"topic", 0}}; + VerifyProgram(res, elementStat); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["topic"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["/database/path/to/tables/my_table/my_feed"]); + } +} + +Y_UNIT_TEST_SUITE(BlockEnginePragma) { + Y_UNIT_TEST(Basic) { + const TVector<TString> values = {"auto", "force", "disable"}; + for (const auto& value : values) { + const auto query = TStringBuilder() << "pragma Blockengine='" << value << "'; select 1;"; + NYql::TAstParseResult res = SqlToYql(query); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + Y_UNUSED(word); + UNIT_ASSERT_STRING_CONTAINS(line, TStringBuilder() << R"(Configure! world (DataSource '"config") '"BlockEngine" '")" << value << "\""); + }; + + TWordCountHive elementStat({"BlockEngine"}); + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT(elementStat["BlockEngine"] == ((value == "disable") ? 0 : 1)); + } + } + + Y_UNIT_TEST(UnknownSetting) { + ExpectFailWithError("use plato; pragma BlockEngine='foo';", + "<main>:1:31: Error: Expected `disable|auto|force' argument for: BlockEngine\n"); + } +} + +Y_UNIT_TEST_SUITE(TViewSyntaxTest) { + Y_UNIT_TEST(CreateViewSimple) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; + CREATE VIEW TheView WITH (security_invoker = TRUE) AS SELECT 1; + )" + ); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + } + + Y_UNIT_TEST(CreateViewIfNotExists) { + constexpr const char* name = "TheView"; + NYql::TAstParseResult res = SqlToYql(std::format(R"( + USE plato; + CREATE VIEW IF NOT EXISTS {} AS SELECT 1; + )", name + )); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_STRING_CONTAINS(line, name); + UNIT_ASSERT_STRING_CONTAINS(line, "createObjectIfNotExists"); + } + }; + + TWordCountHive elementStat = { {"Write!"} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); + } + + Y_UNIT_TEST(CreateViewFromTable) { + constexpr const char* path = "/PathPrefix/TheView"; + constexpr const char* query = R"( + SELECT * FROM SomeTable + )"; + + NYql::TAstParseResult res = SqlToYql(std::format(R"( + USE plato; + CREATE VIEW `{}` WITH (security_invoker = TRUE) AS {}; + )", + path, + query + ) + ); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_STRING_CONTAINS(line, path); + UNIT_ASSERT_STRING_CONTAINS(line, "createObject"); + } + }; + TWordCountHive elementStat = { {"Write!"} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); + } + + Y_UNIT_TEST(CheckReconstructedQuery) { + constexpr const char* path = "/PathPrefix/TheView"; + constexpr const char* query = R"( + SELECT * FROM FirstTable JOIN SecondTable ON FirstTable.key == SecondTable.key + )"; + + NYql::TAstParseResult res = SqlToYql(std::format(R"( + USE plato; + CREATE VIEW `{}` WITH (security_invoker = TRUE) AS {}; + )", + path, + query + ) + ); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TString reconstructedQuery = ToString(Tokenize(query)); + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + if (word == "query_text") { + UNIT_ASSERT_STRING_CONTAINS(line, reconstructedQuery); + } + }; + TWordCountHive elementStat = { {"Write!"} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); + } + + Y_UNIT_TEST(DropView) { + constexpr const char* path = "/PathPrefix/TheView"; + NYql::TAstParseResult res = SqlToYql(std::format(R"( + USE plato; + DROP VIEW `{}`; + )", + path + ) + ); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_STRING_CONTAINS(line, path); + UNIT_ASSERT_STRING_CONTAINS(line, "dropObject"); + } + }; + TWordCountHive elementStat = { {"Write!"} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); + } + + Y_UNIT_TEST(DropViewIfExists) { + constexpr const char* name = "TheView"; + NYql::TAstParseResult res = SqlToYql(std::format(R"( + USE plato; + DROP VIEW IF EXISTS {}; + )", name + )); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [&](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_STRING_CONTAINS(line, name); + UNIT_ASSERT_STRING_CONTAINS(line, "dropObjectIfExists"); + } + }; + + TWordCountHive elementStat = { {"Write!"} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); + } + + Y_UNIT_TEST(CreateViewWithTablePrefix) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; + PRAGMA TablePathPrefix='/PathPrefix'; + CREATE VIEW TheView WITH (security_invoker = TRUE) AS SELECT 1; + )" + ); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write!") { + UNIT_ASSERT_STRING_CONTAINS(line, "/PathPrefix/TheView"); + UNIT_ASSERT_STRING_CONTAINS(line, "createObject"); + } + }; + + TWordCountHive elementStat = { {"Write!"} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); + } + + Y_UNIT_TEST(DropViewWithTablePrefix) { + NYql::TAstParseResult res = SqlToYql(R"( + USE plato; + PRAGMA TablePathPrefix='/PathPrefix'; + DROP VIEW TheView; + )" + ); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "/PathPrefix/TheView"); + UNIT_ASSERT_STRING_CONTAINS(line, "dropObject"); + } + }; + + TWordCountHive elementStat = { {"Write!"} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(elementStat["Write!"], 1); + } + + Y_UNIT_TEST(YtAlternativeSchemaSyntax) { + NYql::TAstParseResult res = SqlToYql(R"( + SELECT * FROM plato.Input WITH schema(y Int32, x String not null); + )"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "userschema") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, + line.find(R"__('('('"userschema" (StructType '('"y" (AsOptionalType (DataType 'Int32))) '('"x" (DataType 'String))))))__")); + } + }; + + TWordCountHive elementStat = {{TString("userschema"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["userschema"]); + } + + Y_UNIT_TEST(UseViewAndFullColumnId) { + NYql::TAstParseResult res = SqlToYql("USE plato; SELECT Input.x FROM Input VIEW uitzicht;"); + UNIT_ASSERT(res.Root); + + TWordCountHive elementStat = {{TString("SqlAccess"), 0}, {"SqlProjectItem", 0}, {"Read!", 0}}; + VerifyProgram(res, elementStat); + UNIT_ASSERT_VALUES_EQUAL(0, elementStat["SqlAccess"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["SqlProjectItem"]); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Read!"]); + } +} + +Y_UNIT_TEST_SUITE(CompactNamedExprs) { + Y_UNIT_TEST(SourceCallablesInWrongContext) { + TString query = R"( + pragma CompactNamedExprs; + $foo = %s(); + select $foo from plato.Input; + )"; + + THashMap<TString, TString> errs = { + {"TableRow", "<main>:3:20: Error: TableRow requires data source\n"}, + {"JoinTableRow", "<main>:3:20: Error: JoinTableRow requires data source\n"}, + {"TableRecordIndex", "<main>:3:20: Error: Unable to use function: TableRecord without source\n"}, + {"TablePath", "<main>:3:20: Error: Unable to use function: TablePath without source\n"}, + {"SystemMetadata", "<main>:3:20: Error: Unable to use function: SystemMetadata without source\n"}, + }; + + for (TString callable : { "TableRow", "JoinTableRow", "TableRecordIndex", "TablePath", "SystemMetadata"}) { + auto req = Sprintf(query.c_str(), callable.c_str()); + ExpectFailWithError(req, errs[callable]); + } + } + + Y_UNIT_TEST(ValidateUnusedExprs) { + TString query = R"( + pragma warning("disable", "4527"); + pragma CompactNamedExprs; + pragma ValidateUnusedExprs; + + $foo = count(1); + select 1; + )"; + ExpectFailWithError(query, "<main>:6:20: Error: Aggregation is not allowed in this context\n"); + query = R"( + pragma warning("disable", "4527"); + pragma CompactNamedExprs; + pragma ValidateUnusedExprs; + + define subquery $x() as + select count(1, 2); + end define; + select 1; + )"; + ExpectFailWithError(query, "<main>:7:24: Error: Aggregation function Count requires exactly 1 argument(s), given: 2\n"); + } + + Y_UNIT_TEST(DisableValidateUnusedExprs) { + TString query = R"( + pragma warning("disable", "4527"); + pragma CompactNamedExprs; + pragma DisableValidateUnusedExprs; + + $foo = count(1); + select 1; + )"; + SqlToYql(query).IsOk(); + query = R"( + pragma warning("disable", "4527"); + pragma CompactNamedExprs; + pragma DisableValidateUnusedExprs; + + define subquery $x() as + select count(1, 2); + end define; + select 1; + )"; + SqlToYql(query).IsOk(); + } +} + +Y_UNIT_TEST_SUITE(ResourcePool) { + Y_UNIT_TEST(CreateResourcePool) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE RESOURCE POOL MyResourcePool WITH ( + CONCURRENT_QUERY_LIMIT=20, + QUERY_CANCEL_AFTER_SECONDS=86400, + QUEUE_TYPE="FIFO" + ); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"concurrent_query_limit" (Int32 '"20")) '('"query_cancel_after_seconds" (Int32 '"86400")) '('"queue_type" '"FIFO"))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateResourcePoolWithBadArguments) { +#if ANTLR_VER == 3 + ExpectFailWithError(R"sql( + USE plato; + CREATE RESOURCE POOL MyResourcePool; + )sql" , "<main>:3:47: Error: Unexpected token ';' : syntax error...\n\n"); +#else + ExpectFailWithError(R"sql( + USE plato; + CREATE RESOURCE POOL MyResourcePool; + )sql" , "<main>:3:47: Error: mismatched input ';' expecting WITH\n"); +#endif + + ExpectFailWithError(R"sql( + USE plato; + CREATE RESOURCE POOL MyResourcePool WITH ( + DUPLICATE_SETTING="first_value", + DUPLICATE_SETTING="second_value" + ); + )sql" , "<main>:5:21: Error: DUPLICATE_SETTING duplicate keys\n"); + } + + Y_UNIT_TEST(AlterResourcePool) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + ALTER RESOURCE POOL MyResourcePool + SET (CONCURRENT_QUERY_LIMIT = 30, Weight = 5, QUEUE_TYPE = "UNORDERED"), + RESET (Query_Cancel_After_Seconds, Query_Count_Limit); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alterObject))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('features '('('"concurrent_query_limit" (Int32 '"30")) '('"queue_type" '"UNORDERED") '('"weight" (Int32 '"5")))))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('resetFeatures '('"query_cancel_after_seconds" '"query_count_limit")))#"); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DropResourcePool) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + DROP RESOURCE POOL MyResourcePool; + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } +} + +Y_UNIT_TEST_SUITE(BackupCollection) { + Y_UNIT_TEST(CreateBackupCollection) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE BACKUP COLLECTION TestCollection WITH ( + STORAGE="local", + TAG="test" -- for testing purposes, not a real thing + ); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"storage" (String '"local")))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"tag" (String '"test"))))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('entries '()))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'create")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateBackupCollectionWithDatabase) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE BACKUP COLLECTION TestCollection DATABASE WITH ( + STORAGE="local", + TAG="test" -- for testing purposes, not a real thing + ); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"storage" (String '"local")))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"tag" (String '"test"))))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('entries '('('('type 'database)))))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'create")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateBackupCollectionWithTables) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE BACKUP COLLECTION TestCollection ( + TABLE someTable, + TABLE `prefix/anotherTable` + ) WITH ( + STORAGE="local", + TAG="test" -- for testing purposes, not a real thing + ); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"storage" (String '"local")))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"tag" (String '"test"))))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('entries '('('('type 'table) '('path '"someTable")) '('('type 'table) '('path '"prefix/anotherTable")))))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'create")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateBackupCollectionWithBadArguments) { +#if ANTLR_VER == 3 + ExpectFailWithError(R"sql( + USE plato; + CREATE BACKUP COLLECTION TestCollection; + )sql" , "<main>:3:51: Error: Unexpected token ';' : syntax error...\n\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE BACKUP COLLECTION TABLE TestCollection; + )sql" , "<main>:3:43: Error: Unexpected token 'TestCollection' : syntax error...\n\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE BACKUP COLLECTION DATABASE `test` TestCollection; + )sql" , "<main>:3:46: Error: Unexpected token '`test`' : syntax error...\n\n"); +#else + ExpectFailWithError(R"sql( + USE plato; + CREATE BACKUP COLLECTION TestCollection; + )sql" , "<main>:3:55: Error: mismatched input ';' expecting {'(', DATABASE, WITH}\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE BACKUP COLLECTION TABLE TestCollection; + )sql" , "<main>:3:47: Error: mismatched input 'TestCollection' expecting {'(', DATABASE, WITH}\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE BACKUP COLLECTION DATABASE `test` TestCollection; + )sql" , "<main>:3:50: Error: mismatched input '`test`' expecting {'(', DATABASE, WITH}\n"); +#endif + + ExpectFailWithError(R"sql( + USE plato; + CREATE BACKUP COLLECTION TestCollection WITH ( + DUPLICATE_SETTING="first_value", + DUPLICATE_SETTING="second_value" + ); + )sql" , "<main>:5:21: Error: DUPLICATE_SETTING duplicate keys\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE BACKUP COLLECTION TestCollection WITH ( + INT_SETTING=1 + ); + )sql" , "<main>:4:21: Error: INT_SETTING value should be a string literal\n"); + } + + Y_UNIT_TEST(AlterBackupCollection) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + ALTER BACKUP COLLECTION TestCollection + SET (STORAGE="remote"), -- also just for test + SET (TAG1 = "123"), + RESET (TAG2, TAG3); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alter))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"storage" (String '"remote")))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('"tag1" (String '"123"))))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('resetSettings '('"tag2" '"tag3")))#"); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(AlterBackupCollectionEntries) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + ALTER BACKUP COLLECTION TestCollection + DROP TABLE `test`, + ADD DATABASE; + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alter))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('alterEntries)#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('type 'table) '('path '"test") '('action 'drop)))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('type 'database) '('action 'add)))#"); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DropBackupCollection) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + DROP BACKUP COLLECTION TestCollection; + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("drop")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } +} + +Y_UNIT_TEST_SUITE(ResourcePoolClassifier) { + Y_UNIT_TEST(CreateResourcePoolClassifier) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + RANK=20, + RESOURCE_POOL='wgUserQueries', + MEMBER_NAME='yandex_query@abc' + ); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"member_name" '"yandex_query@abc") '('"rank" (Int32 '"20")) '('"resource_pool" '"wgUserQueries"))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateResourcePoolClassifierWithBadArguments) { +#if ANTLR_VER == 3 + ExpectFailWithError(R"sql( + USE plato; + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier; + )sql" , "<main>:3:68: Error: Unexpected token ';' : syntax error...\n\n"); +#else + ExpectFailWithError(R"sql( + USE plato; + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier; + )sql" , "<main>:3:72: Error: mismatched input ';' expecting WITH\n"); +#endif + + ExpectFailWithError(R"sql( + USE plato; + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + DUPLICATE_SETTING="first_value", + DUPLICATE_SETTING="second_value" + ); + )sql" , "<main>:5:21: Error: DUPLICATE_SETTING duplicate keys\n"); + } + + Y_UNIT_TEST(AlterResourcePoolClassifier) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + ALTER RESOURCE POOL CLASSIFIER MyResourcePoolClassifier + SET (RANK = 30, Weight = 5, MEMBER_NAME = "test@user"), + RESET (Resource_Pool); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alterObject))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('features '('('"member_name" '"test@user") '('"rank" (Int32 '"30")) '('"weight" (Int32 '"5")))))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('resetFeatures '('"resource_pool")))#"); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DropResourcePoolClassifier) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + DROP RESOURCE POOL CLASSIFIER MyResourcePoolClassifier; + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(BacktickMatching) { + auto req = "select\n" + " 1 as `Schema has \\`RealCost\\``\n" + " -- foo`bar"; + auto res = SqlToYql(req); + UNIT_ASSERT(res.Root); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + res = SqlToYqlWithAnsiLexer(req); + UNIT_ASSERT(res.Root); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + + req = "select 1 as `a``b`, 2 as ````, 3 as `\\x60a\\x60`, 4 as ```b```, 5 as `\\`c\\``"; + res = SqlToYql(req); + UNIT_ASSERT(res.Root); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + res = SqlToYqlWithAnsiLexer(req); + UNIT_ASSERT(res.Root); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + } +} + +Y_UNIT_TEST_SUITE(Backup) { + Y_UNIT_TEST(Simple) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + BACKUP TestCollection; + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'Incremental")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'backup")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(Incremental) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + BACKUP TestCollection INCREMENTAL; + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'backupIncremental")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } +} + +Y_UNIT_TEST_SUITE(Restore) { + Y_UNIT_TEST(Simple) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + RESTORE TestCollection; + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'restore")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(AtPoint) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + RESTORE TestCollection AT '2024-06-16_20-14-02'; + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find(R"#('"TestCollection")#")); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('at '"2024-06-16_20-14-02")#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("'restore")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } +} + +Y_UNIT_TEST_SUITE(ColumnFamily) { + Y_UNIT_TEST(CompressionLevelCorrectUsage) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + CREATE TABLE tableName ( + Key Uint32 FAMILY default, + Value String FAMILY family1, + PRIMARY KEY (Key), + FAMILY default ( + DATA = "test", + COMPRESSION = "lz4", + COMPRESSION_LEVEL = 5 + ), + FAMILY family1 ( + DATA = "test", + COMPRESSION = "lz4", + COMPRESSION_LEVEL = 3 + ) + ); + )"); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("compression_level")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("5")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("3")); + } + }; + + TWordCountHive elementStat = { { TString("Write"), 0 }, { TString("compression_level"), 0 } }; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["compression_level"]); + } + + Y_UNIT_TEST(FieldDataIsNotString) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + CREATE TABLE tableName ( + Key Uint32 FAMILY default, + PRIMARY KEY (Key), + FAMILY default ( + DATA = 1, + COMPRESSION = "lz4", + COMPRESSION_LEVEL = 5 + ) + ); + )"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 1); + UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "DATA value should be a string literal"); + } + + Y_UNIT_TEST(FieldCompressionIsNotString) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + CREATE TABLE tableName ( + Key Uint32 FAMILY default, + PRIMARY KEY (Key), + FAMILY default ( + DATA = "test", + COMPRESSION = 2, + COMPRESSION_LEVEL = 5 + ), + ); + )"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 1); + UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "COMPRESSION value should be a string literal"); + } + + Y_UNIT_TEST(FieldCompressionLevelIsNotInteger) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + CREATE TABLE tableName ( + Key Uint32 FAMILY default, + PRIMARY KEY (Key), + FAMILY default ( + DATA = "test", + COMPRESSION = "lz4", + COMPRESSION_LEVEL = "5" + ) + ); + )"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 1); + UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "COMPRESSION_LEVEL value should be an integer"); + } + + Y_UNIT_TEST(AlterCompressionCorrectUsage) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION "lz4"; + )"); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + } + + Y_UNIT_TEST(AlterCompressionFieldIsNotString) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION lz4; + )"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 1); +#if ANTLR_VER == 3 + UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "Unexpected token 'lz4' : cannot match to any predicted input"); +#else + UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "mismatched input 'lz4' expecting {STRING_VALUE, DIGITS, INTEGER_VALUE}"); +#endif + } + + Y_UNIT_TEST(AlterCompressionLevelCorrectUsage) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION_LEVEL 5; + )"); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + } + + Y_UNIT_TEST(AlterCompressionLevelFieldIsNotInteger) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION_LEVEL "5"; + )"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 1); + UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "COMPRESSION_LEVEL value should be an integer"); + } +} + +Y_UNIT_TEST_SUITE(Transfer) { + Y_UNIT_TEST(Lambda) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + -- Русский коммент, empty statement + ; + + -- befor comment + $a = "А"; + + SELECT * FROM Input; + + $b = ($x) -> { return $a || $x; }; + + CREATE TRANSFER `TransferName` + FROM `TopicName` TO `TableName` + USING ($x) -> { + -- internal comment + return $b($x); + } + WITH ( + CONNECTION_STRING = "grpc://localhost:2135/?database=/Root" + ); + )"); + + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToString()); + UNIT_ASSERT_VALUES_EQUAL_C(res.Issues.Size(), 0, res.Issues.ToString()); + + const auto programm = GetPrettyPrint(res); + + Cerr << ">>>>> Root " << programm << Endl; + auto expected = R"('transformLambda 'use plato; +-- befor comment + $a = "А"; +$b = ($x) -> { return $a || $x; }; +$__ydb_transfer_lambda = ($x) -> { + -- internal comment + return $b($x); + }; +))"; + + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, programm.find(expected)); + + } +} + +Y_UNIT_TEST_SUITE(MatchRecognizeMeasuresAggregation) { + Y_UNIT_TEST(InsideSelect) { + ExpectFailWithError(R"sql( + SELECT FIRST(0), LAST(1); + )sql", + "<main>:2:20: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" + "<main>:2:30: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" + ); + } + + Y_UNIT_TEST(OutsideSelect) { + ExpectFailWithError(R"sql( + $lambda = ($x) -> (FIRST($x) + LAST($x)); + SELECT $lambda(x) FROM plato.Input; + )sql", + "<main>:2:32: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" + "<main>:2:44: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" + ); + } + + Y_UNIT_TEST(AsAggregateFunction) { + ExpectFailWithError(R"sql( + SELECT FIRST(x), LAST(x) FROM plato.Input; + )sql", + "<main>:2:20: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" + "<main>:2:30: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" + ); + } + + Y_UNIT_TEST(AsWindowFunction) { + ExpectFailWithError(R"sql( + SELECT FIRST(x) OVER(), LAST(x) OVER() FROM plato.Input; + )sql", + "<main>:2:20: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" + "<main>:2:37: Error: Cannot use FIRST and LAST outside the MATCH_RECOGNIZE context\n" + ); + } +} + +Y_UNIT_TEST_SUITE(OlapPartitionCount) { + Y_UNIT_TEST(CorrectUsage) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE TABLE `mytable` (id Uint32, PRIMARY KEY (id)) + PARTITION BY HASH(id) + WITH (STORE = COLUMN, PARTITION_COUNT = 8); + )sql"); + + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToString()); + } + + Y_UNIT_TEST(UseWithoutColumnStore) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE TABLE `mytable` (id Uint32, PRIMARY KEY (id)) + WITH (PARTITION_COUNT = 8); + )sql"); + + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 1); + UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "PARTITION_COUNT can be used only with STORE=COLUMN"); + } +} |