diff options
Diffstat (limited to 'yql/essentials/sql')
| -rw-r--r-- | yql/essentials/sql/pg/pg_sql.cpp | 59 | ||||
| -rw-r--r-- | yql/essentials/sql/pg/pg_sql_ut.cpp | 28 | ||||
| -rw-r--r-- | yql/essentials/sql/pg/ya.make | 1 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/SQLv1Antlr4.g.in | 11 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/builtin.cpp | 127 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/format/sql_format_ut.h | 26 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/node.h | 8 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/query.cpp | 17 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/select.cpp | 16 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/source.h | 2 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/sql_query.cpp | 16 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/sql_select.cpp | 34 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/sql_translation.cpp | 71 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/sql_ut_common.h | 440 |
14 files changed, 789 insertions, 67 deletions
diff --git a/yql/essentials/sql/pg/pg_sql.cpp b/yql/essentials/sql/pg/pg_sql.cpp index 6990cbfefa7..81b9eb396ee 100644 --- a/yql/essentials/sql/pg/pg_sql.cpp +++ b/yql/essentials/sql/pg/pg_sql.cpp @@ -40,6 +40,7 @@ extern "C" { #include <yql/essentials/providers/common/provider/yql_provider_names.h> #include <yql/essentials/minikql/mkql_type_builder.h> #include <yql/essentials/core/issue/yql_issue.h> +#include <yql/essentials/core/langver/feature.gen.h> #include <yql/essentials/public/issue/yql_warning.h> #include <yql/essentials/core/sql_types/yql_callable_names.h> #include <yql/essentials/utils/log/log_level.h> @@ -2517,7 +2518,7 @@ public: return nullptr; } } else if (name == "warning") { - if (auto langver = NYql::MakeLangVersion(2026, 01); + if (auto langver = NYql::NFeature::PgPragmaWarning.MinLangVer; !NYql::IsBackwardCompatibleFeatureAvailable( Settings_.LangVer, langver, Settings_.BackportMode)) { @@ -3725,6 +3726,33 @@ public: return result; } + bool ExtractCollationName(const CollateClause* value, TString& name) { + auto len = ListLength(value->collname); + if (len == 0) { + AddError("CollateClause: missing collation name"); + return false; + } + + auto x = ListNodeNth(value->collname, len - 1); + if (NodeTag(x) != T_String) { + NodeNotImplemented(value, x); + return false; + } + + name = StrVal(x); + return true; + } + + TAstNode* ParseCollateClause(const CollateClause* value, const TExprSettings& settings) { + AT_LOCATION(value); + // Collation propagation through arbitrary expressions (COALESCE, CASE, plain + // projections, ...) is not implemented - only ParseFuncCall picks up an explicit + // COLLATE that directly wraps one of its arguments (see there). Elsewhere COLLATE + // only affects collation-sensitive semantics (comparison, case mapping), not the + // expression's runtime value, so we just parse through to the wrapped expression. + return ParseExpr(value->arg, settings); + } + TAstNode* ParseExpr(const Node* node, const TExprSettings& settings) { switch (NodeTag(node)) { case T_A_Const: { @@ -3742,6 +3770,9 @@ public: case T_TypeCast: { return ParseTypeCast(CAST_NODE(TypeCast, node), settings); } + case T_CollateClause: { + return ParseCollateClause(CAST_NODE(CollateClause, node), settings); + } case T_BoolExpr: { return ParseBoolExpr(CAST_NODE(BoolExpr, node), settings); } @@ -4267,6 +4298,32 @@ public: callSettings.push_back(QL(QA("range"))); } + if (!value->agg_star) { + TMaybe<TString> collation; + for (int i = 0; i < ListLength(value->args); ++i) { + auto x = ListNodeNth(value->args, i); + if (NodeTag(x) != T_CollateClause) { + continue; + } + + TString collationName; + if (!ExtractCollationName(CAST_NODE(CollateClause, x), collationName)) { + return nullptr; + } + + if (collation && *collation != collationName) { + AddError(TStringBuilder() << "FuncCall: conflicting explicit collations: " << *collation << " and " << collationName); + return nullptr; + } + + collation = collationName; + } + + if (collation) { + callSettings.push_back(QL(QA("collation"), QAX(*collation))); + } + } + args.push_back(QVL(callSettings.data(), callSettings.size())); if (value->agg_star) { if (name != "count") { diff --git a/yql/essentials/sql/pg/pg_sql_ut.cpp b/yql/essentials/sql/pg/pg_sql_ut.cpp index 2d07350d6ea..18477d3a807 100644 --- a/yql/essentials/sql/pg/pg_sql_ut.cpp +++ b/yql/essentials/sql/pg/pg_sql_ut.cpp @@ -1,5 +1,7 @@ #include "ut/util.h" +#include <yql/essentials/core/langver/feature.gen.h> + #include <library/cpp/testing/unittest/registar.h> #include <yql/essentials/parser/pg_wrapper/interface/parser.h> @@ -1138,7 +1140,7 @@ Y_UNIT_TEST(WarningAsError) { )sql"; TTranslationSettings settings; - settings.LangVer = NYql::MakeLangVersion(2026, 01); + settings.LangVer = NYql::NFeature::PgPragmaWarning.MinLangVer; auto res = SqlToYqlWithMode(query, NSQLTranslation::ESqlMode::QUERY, 10, {}, EDebugOutput::None, /*ansiLexer=*/false, settings); UNIT_ASSERT(!res.Root); UNIT_ASSERT(res.Issues.Size() > 0); @@ -1155,7 +1157,7 @@ Y_UNIT_TEST(WarningDisable) { )sql"; TTranslationSettings settings; - settings.LangVer = NYql::MakeLangVersion(2026, 01); + settings.LangVer = NYql::NFeature::PgPragmaWarning.MinLangVer; auto res = SqlToYqlWithMode(query, NSQLTranslation::ESqlMode::QUERY, 10, {}, EDebugOutput::None, /*ansiLexer=*/false, settings); UNIT_ASSERT(res.Root); UNIT_ASSERT_EQUAL(res.Issues.Size(), 0); @@ -1168,7 +1170,7 @@ Y_UNIT_TEST(WarningDefault) { )sql"; TTranslationSettings settings; - settings.LangVer = NYql::MakeLangVersion(2026, 01); + settings.LangVer = NYql::NFeature::PgPragmaWarning.MinLangVer; auto res = SqlToYqlWithMode(query, NSQLTranslation::ESqlMode::QUERY, 10, {}, EDebugOutput::None, /*ansiLexer=*/false, settings); UNIT_ASSERT(res.Root); UNIT_ASSERT(res.Issues.Size() > 0); @@ -1184,7 +1186,7 @@ Y_UNIT_TEST(WarningInvalidAction) { )sql"; TTranslationSettings settings; - settings.LangVer = NYql::MakeLangVersion(2026, 01); + settings.LangVer = NYql::NFeature::PgPragmaWarning.MinLangVer; auto res = SqlToYqlWithMode(query, NSQLTranslation::ESqlMode::QUERY, 10, {}, EDebugOutput::None, /*ansiLexer=*/false, settings); UNIT_ASSERT(!res.Root); UNIT_ASSERT(res.Issues.Size() > 0); @@ -1200,7 +1202,7 @@ Y_UNIT_TEST(WarningInvalidPattern) { )sql"; TTranslationSettings settings; - settings.LangVer = NYql::MakeLangVersion(2026, 01); + settings.LangVer = NYql::NFeature::PgPragmaWarning.MinLangVer; auto res = SqlToYqlWithMode(query, NSQLTranslation::ESqlMode::QUERY, 10, {}, EDebugOutput::None, /*ansiLexer=*/false, settings); UNIT_ASSERT(!res.Root); UNIT_ASSERT(res.Issues.Size() > 0); @@ -1216,7 +1218,7 @@ Y_UNIT_TEST(WarningMissingArguments) { )sql"; TTranslationSettings settings; - settings.LangVer = NYql::MakeLangVersion(2026, 01); + settings.LangVer = NYql::NFeature::PgPragmaWarning.MinLangVer; auto res = SqlToYqlWithMode(query, NSQLTranslation::ESqlMode::QUERY, 10, {}, EDebugOutput::None, /*ansiLexer=*/false, settings); UNIT_ASSERT(!res.Root); UNIT_ASSERT(res.Issues.Size() > 0); @@ -1232,7 +1234,7 @@ Y_UNIT_TEST(WarningTooManyArguments) { )sql"; TTranslationSettings settings; - settings.LangVer = NYql::MakeLangVersion(2026, 01); + settings.LangVer = NYql::NFeature::PgPragmaWarning.MinLangVer; auto res = SqlToYqlWithMode(query, NSQLTranslation::ESqlMode::QUERY, 10, {}, EDebugOutput::None, /*ansiLexer=*/false, settings); UNIT_ASSERT(!res.Root); UNIT_ASSERT(res.Issues.Size() > 0); @@ -1249,7 +1251,7 @@ Y_UNIT_TEST(WarningSpecificCode) { )sql"; TTranslationSettings settings; - settings.LangVer = NYql::MakeLangVersion(2026, 01); + settings.LangVer = NYql::NFeature::PgPragmaWarning.MinLangVer; auto res = SqlToYqlWithMode(query, NSQLTranslation::ESqlMode::QUERY, 10, {}, EDebugOutput::None, /*ansiLexer=*/false, settings); UNIT_ASSERT(!res.Root); UNIT_ASSERT(res.Issues.Size() > 0); @@ -1266,7 +1268,7 @@ Y_UNIT_TEST(WarningSpecificCodeDisable) { )sql"; TTranslationSettings settings; - settings.LangVer = NYql::MakeLangVersion(2026, 01); + settings.LangVer = NYql::NFeature::PgPragmaWarning.MinLangVer; auto res = SqlToYqlWithMode(query, NSQLTranslation::ESqlMode::QUERY, 10, {}, EDebugOutput::None, /*ansiLexer=*/false, settings); UNIT_ASSERT(res.Root); UNIT_ASSERT_EQUAL(res.Issues.Size(), 0); @@ -1278,7 +1280,7 @@ Y_UNIT_TEST(WarningNonStringAction) { )sql"; TTranslationSettings settings; - settings.LangVer = NYql::MakeLangVersion(2026, 01); + settings.LangVer = NYql::NFeature::PgPragmaWarning.MinLangVer; auto res = SqlToYqlWithMode(query, NSQLTranslation::ESqlMode::QUERY, 10, {}, EDebugOutput::None, /*ansiLexer=*/false, settings); UNIT_ASSERT(!res.Root); UNIT_ASSERT(res.Issues.Size() > 0); @@ -1294,7 +1296,7 @@ Y_UNIT_TEST(WarningNonStringPattern) { )sql"; TTranslationSettings settings; - settings.LangVer = NYql::MakeLangVersion(2026, 01); + settings.LangVer = NYql::NFeature::PgPragmaWarning.MinLangVer; auto res = SqlToYqlWithMode(query, NSQLTranslation::ESqlMode::QUERY, 10, {}, EDebugOutput::None, /*ansiLexer=*/false, settings); UNIT_ASSERT(!res.Root); UNIT_ASSERT(res.Issues.Size() > 0); @@ -1312,7 +1314,7 @@ Y_UNIT_TEST(WarningMultipleRules) { )sql"; TTranslationSettings settings; - settings.LangVer = NYql::MakeLangVersion(2026, 01); + settings.LangVer = NYql::NFeature::PgPragmaWarning.MinLangVer; auto res = SqlToYqlWithMode(query, NSQLTranslation::ESqlMode::QUERY, 10, {}, EDebugOutput::None, /*ansiLexer=*/false, settings); UNIT_ASSERT(!res.Root); UNIT_ASSERT(res.Issues.Size() > 0); @@ -1328,7 +1330,7 @@ Y_UNIT_TEST(WarningNoArguments) { )sql"; TTranslationSettings settings; - settings.LangVer = NYql::MakeLangVersion(2026, 01); + settings.LangVer = NYql::NFeature::PgPragmaWarning.MinLangVer; auto res = SqlToYqlWithMode(query, NSQLTranslation::ESqlMode::QUERY, 10, {}, EDebugOutput::None, /*ansiLexer=*/false, settings); UNIT_ASSERT(!res.Root); UNIT_ASSERT(res.Issues.Size() > 0); diff --git a/yql/essentials/sql/pg/ya.make b/yql/essentials/sql/pg/ya.make index ab493603bf5..edbcfa2aa30 100644 --- a/yql/essentials/sql/pg/ya.make +++ b/yql/essentials/sql/pg/ya.make @@ -6,6 +6,7 @@ PROVIDES( PEERDIR( yql/essentials/ast + yql/essentials/core/langver yql/essentials/core/sql_types yql/essentials/parser/pg_catalog yql/essentials/minikql diff --git a/yql/essentials/sql/v1/SQLv1Antlr4.g.in b/yql/essentials/sql/v1/SQLv1Antlr4.g.in index d1eba59441a..54ffbdeec54 100644 --- a/yql/essentials/sql/v1/SQLv1Antlr4.g.in +++ b/yql/essentials/sql/v1/SQLv1Antlr4.g.in @@ -882,6 +882,7 @@ column_option: | default_value | compression | encoding + | generated_always ; compression: COMPRESSION LPAREN (compression_setting_entry (COMMA compression_setting_entry)*)? COMMA? RPAREN; @@ -896,6 +897,7 @@ encoding_config: encoding_config_name (LPAREN (encoding_setting_entry (COMMA enc encoding_config_name: an_id_or_type; encoding_setting_entry: an_id EQUALS encoding_setting_value; encoding_setting_value: integer | id; +generated_always: (GENERATED ALWAYS)? AS LPAREN expr RPAREN (STORED | VIRTUAL)?; column_order_by_specification: an_id (ASC | DESC)?; @@ -1476,6 +1478,7 @@ keyword_as_compat: | ADD | AFTER | ALTER + | ALWAYS | ANALYZE | AND | ANSI @@ -1551,6 +1554,7 @@ keyword_as_compat: | FOR | FOREIGN | FUNCTION + | GENERATED | GLOB | GLOBAL | GRANT @@ -1669,6 +1673,7 @@ keyword_as_compat: | SOURCE | START | STATISTICS + | STORED | STREAMING | SUBQUERY | SUBSET @@ -1717,6 +1722,7 @@ keyword_compat: ( | ADD | AFTER | ALTER + | ALWAYS | ANALYZE | AND | ANSI @@ -1792,6 +1798,7 @@ keyword_compat: ( | FOR | FOREIGN | FUNCTION + | GENERATED | GLOB | GLOBAL | GRANT @@ -1910,6 +1917,7 @@ keyword_compat: ( | SOURCE | START | STATISTICS + | STORED | STREAMING | SUBQUERY | SUBSET @@ -2055,6 +2063,7 @@ ADD: A D D; AFTER: A F T E R; ALL: A L L; ALTER: A L T E R; +ALWAYS: A L W A Y S; ANALYZE: A N A L Y Z E; AND: A N D; ANSI: A N S I; @@ -2159,6 +2168,7 @@ FOREIGN: F O R E I G N; FROM: F R O M; FULL: F U L L; FUNCTION: F U N C T I O N; +GENERATED: G E N E R A T E D; GLOB: G L O B; GLOBAL: G L O B A L; GRANT: G R A N T; @@ -2300,6 +2310,7 @@ SECRET: S E C R E T; SEQUENCE: S E Q U E N C E; SOURCE: S O U R C E; START: S T A R T; +STORED: S T O R E D; STATISTICS: S T A T I S T I C S; STREAM: S T R E A M; STREAMING: S T R E A M I N G; diff --git a/yql/essentials/sql/v1/builtin.cpp b/yql/essentials/sql/v1/builtin.cpp index cb91f956f89..8fcb3ae0945 100644 --- a/yql/essentials/sql/v1/builtin.cpp +++ b/yql/essentials/sql/v1/builtin.cpp @@ -10,6 +10,7 @@ #include <yql/essentials/ast/yql_type_string.h> #include <yql/essentials/public/udf/udf_data_type.h> #include <yql/essentials/core/sql_types/simple_types.h> +#include <yql/essentials/core/langver/feature.gen.h> #include <yql/essentials/minikql/mkql_program_builder.h> #include <yql/essentials/minikql/mkql_type_ops.h> #include <yql/essentials/public/issue/yql_issue_id.h> @@ -860,7 +861,35 @@ public: } Args_[0] = BuildQuotedAtom(Args_[0]->GetPos(), Args_[0]->GetLiteralValue()); - Args_.insert(Args_.begin() + 1, RangeFunction ? Q(Y(Q(Y(Q("range"))))) : Q(Y())); + + TNodePtr settingsList = Y(); + if (RangeFunction) { + settingsList = L(settingsList, Q(Y(Q("range")))); + } + + // Optional named argument: PgCall("upper", x, "de-DE-x-icu" as Collation) + for (size_t i = 1; i < Args_.size();) { + const auto& label = Args_[i]->GetLabel(); + if (label.empty()) { + ++i; + continue; + } + + if (label != "Collation") { + ctx.Error(Args_[i]->GetPos()) << "PgCall: unsupported named argument: " << label << "; expected: Collation"; + return false; + } + + auto arg = Args_[i]; + if (!arg->Init(ctx, src)) { + return false; + } + + settingsList = L(settingsList, Q(Y(Q("collation"), MakeAtomFromExpression(Pos_, ctx, arg).Build()))); + Args_.erase(Args_.begin() + i); + } + + Args_.insert(Args_.begin() + 1, Q(settingsList)); return TCallNode::DoInit(ctx, src); } @@ -3115,7 +3144,7 @@ struct TBuiltinFuncData { {"byteat", {"ByteAt", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("ByteAt", 2, 2)}}, {"startswith", {"StartsWith", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("StartsWith", 2, 2)}}, {"endswith", {"EndsWith", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("EndsWith", 2, 2)}}, - {"concat", {"Concat", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("SqlConcat", 1, -1), NYql::MakeLangVersion(2025, 4)}}, + {"concat", {"Concat", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("SqlConcat", 1, -1), NYql::NFeature::ConcatFunction.MinLangVer}}, // Numeric builtins {"abs", {"Abs", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("Abs", 1, 1)}}, @@ -3207,30 +3236,30 @@ struct TBuiltinFuncData { {"dictlookup", {"DictLookup", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("Lookup", 2, 2)}}, {"dictcontains", {"DictContains", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("Contains", 2, 2)}}, - {"todynamiclinear", {"ToDynamicLinear", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("ToDynamicLinear", 1, 1), NYql::MakeLangVersion(2025, 4)}}, - {"fromdynamiclinear", {"FromDynamicLinear", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("FromDynamicLinear", 1, 1), NYql::MakeLangVersion(2025, 4)}}, - {"lineardestroy", {"LinearDestroy", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("LinearDestroy", 1, -1), NYql::MakeLangVersion(2025, 5)}}, + {"todynamiclinear", {"ToDynamicLinear", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("ToDynamicLinear", 1, 1), NYql::NFeature::LinearTypes.MinLangVer}}, + {"fromdynamiclinear", {"FromDynamicLinear", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("FromDynamicLinear", 1, 1), NYql::NFeature::LinearTypes.MinLangVer}}, + {"lineardestroy", {"LinearDestroy", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("LinearDestroy", 1, -1), NYql::NFeature::LinearDestroy.MinLangVer}}, // MutDict builtins - {"mutdictcreate", {"MutDictCreate", "Normal", BuildSimpleBuiltinFactoryCallback<TMutDictCreateBuiltin>(), NYql::MakeLangVersion(2025, 4)}}, - {"tomutdict", {"ToMutDict", "Normal", BuildSimpleBuiltinFactoryCallback<TToMutDictBuiltin>(), NYql::MakeLangVersion(2025, 4)}}, - {"frommutdict", {"FromMutDict", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("FromMutDict", 1, 1), NYql::MakeLangVersion(2025, 4)}}, - {"mutdictinsert", {"MutDictInsert", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictInsert", 3, 3), NYql::MakeLangVersion(2025, 4)}}, - {"mutdictupsert", {"MutDictUpsert", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictUpsert", 3, 3), NYql::MakeLangVersion(2025, 4)}}, - {"mutdictupdate", {"MutDictUpdate", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictUpdate", 3, 3), NYql::MakeLangVersion(2025, 4)}}, - {"mutdictremove", {"MutDictRemove", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictRemove", 2, 2), NYql::MakeLangVersion(2025, 4)}}, - {"mutdictpop", {"MutDictPop", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictPop", 2, 2), NYql::MakeLangVersion(2025, 4)}}, - {"mutdictcontains", {"MutDictContains", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictContains", 2, 2), NYql::MakeLangVersion(2025, 4)}}, - {"mutdictlookup", {"MutDictLookup", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictLookup", 2, 2), NYql::MakeLangVersion(2025, 4)}}, - {"mutdicthasitems", {"MutDictHasItems", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictHasItems", 1, 1), NYql::MakeLangVersion(2025, 4)}}, - {"mutdictlength", {"MutDictLength", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictLength", 1, 1), NYql::MakeLangVersion(2025, 4)}}, - {"mutdictitems", {"MutDictItems", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictItems", 1, 1), NYql::MakeLangVersion(2025, 4)}}, - {"mutdictkeys", {"MutDictKeys", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictKeys", 1, 1), NYql::MakeLangVersion(2025, 4)}}, - {"mutdictpayloads", {"MutDictPayloads", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictPayloads", 1, 1), NYql::MakeLangVersion(2025, 4)}}, + {"mutdictcreate", {"MutDictCreate", "Normal", BuildSimpleBuiltinFactoryCallback<TMutDictCreateBuiltin>(), NYql::NFeature::LinearTypes.MinLangVer}}, + {"tomutdict", {"ToMutDict", "Normal", BuildSimpleBuiltinFactoryCallback<TToMutDictBuiltin>(), NYql::NFeature::LinearTypes.MinLangVer}}, + {"frommutdict", {"FromMutDict", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("FromMutDict", 1, 1), NYql::NFeature::LinearTypes.MinLangVer}}, + {"mutdictinsert", {"MutDictInsert", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictInsert", 3, 3), NYql::NFeature::LinearTypes.MinLangVer}}, + {"mutdictupsert", {"MutDictUpsert", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictUpsert", 3, 3), NYql::NFeature::LinearTypes.MinLangVer}}, + {"mutdictupdate", {"MutDictUpdate", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictUpdate", 3, 3), NYql::NFeature::LinearTypes.MinLangVer}}, + {"mutdictremove", {"MutDictRemove", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictRemove", 2, 2), NYql::NFeature::LinearTypes.MinLangVer}}, + {"mutdictpop", {"MutDictPop", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictPop", 2, 2), NYql::NFeature::LinearTypes.MinLangVer}}, + {"mutdictcontains", {"MutDictContains", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictContains", 2, 2), NYql::NFeature::LinearTypes.MinLangVer}}, + {"mutdictlookup", {"MutDictLookup", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictLookup", 2, 2), NYql::NFeature::LinearTypes.MinLangVer}}, + {"mutdicthasitems", {"MutDictHasItems", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictHasItems", 1, 1), NYql::NFeature::LinearTypes.MinLangVer}}, + {"mutdictlength", {"MutDictLength", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictLength", 1, 1), NYql::NFeature::LinearTypes.MinLangVer}}, + {"mutdictitems", {"MutDictItems", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictItems", 1, 1), NYql::NFeature::LinearTypes.MinLangVer}}, + {"mutdictkeys", {"MutDictKeys", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictKeys", 1, 1), NYql::NFeature::LinearTypes.MinLangVer}}, + {"mutdictpayloads", {"MutDictPayloads", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("MutDictPayloads", 1, 1), NYql::NFeature::LinearTypes.MinLangVer}}, - {"dictinsert", {"DictInsert", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DictInsert", 3, 3), NYql::MakeLangVersion(2025, 4)}}, - {"dictupsert", {"DictUpsert", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DictUpsert", 3, 3), NYql::MakeLangVersion(2025, 4)}}, - {"dictupdate", {"DictUpdate", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DictUpdate", 3, 3), NYql::MakeLangVersion(2025, 4)}}, - {"dictremove", {"DictRemove", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DictRemove", 2, 2), NYql::MakeLangVersion(2025, 4)}}, + {"dictinsert", {"DictInsert", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DictInsert", 3, 3), NYql::NFeature::LinearTypes.MinLangVer}}, + {"dictupsert", {"DictUpsert", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DictUpsert", 3, 3), NYql::NFeature::LinearTypes.MinLangVer}}, + {"dictupdate", {"DictUpdate", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DictUpdate", 3, 3), NYql::NFeature::LinearTypes.MinLangVer}}, + {"dictremove", {"DictRemove", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DictRemove", 2, 2), NYql::NFeature::LinearTypes.MinLangVer}}, // Atom builtins {"asatom", {"AsAtom", "Normal", BuildSimpleBuiltinFactoryCallback<TYqlAsAtom>()}}, @@ -3252,8 +3281,8 @@ struct TBuiltinFuncData { {"ensuretype", {"EnsureType", "Normal", BuildSimpleBuiltinFactoryCallback<TYqlTypeAssert<true>>()}}, {"ensureconvertibleto", {"EnsureConvertibleTo", "Normal", BuildSimpleBuiltinFactoryCallback<TYqlTypeAssert<false>>()}}, {"ensure", {"Ensure", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("EnsureWarn", 2, 3)}}, - {"withsideeffects", {"WithSideEffects", "Normal", BuildSimpleBuiltinFactoryCallback<TSideEffects<false>>(), NYql::MakeLangVersion(2025, 4)}}, - {"withsideeffectsmode", {"WithSideEffectsMode", "Normal", BuildSimpleBuiltinFactoryCallback<TSideEffects<true>>(), NYql::MakeLangVersion(2025, 4)}}, + {"withsideeffects", {"WithSideEffects", "Normal", BuildSimpleBuiltinFactoryCallback<TSideEffects<false>>(), NYql::NFeature::SideEffects.MinLangVer}}, + {"withsideeffectsmode", {"WithSideEffectsMode", "Normal", BuildSimpleBuiltinFactoryCallback<TSideEffects<true>>(), NYql::NFeature::SideEffects.MinLangVer}}, {"evaluateexpr", {"EvaluateExpr", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("EvaluateExpr", 1, 1)}}, {"evaluateatom", {"EvaluateAtom", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("EvaluateAtom", 1, 1)}}, {"evaluatetype", {"EvaluateType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("EvaluateType", 1, 1)}}, @@ -3278,12 +3307,12 @@ struct TBuiltinFuncData { {"pgarray", {"PgArray", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("PgArray", 1, -1)}}, {"typeof", {"TypeOf", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("TypeOf", 1, 1)}}, {"instanceof", {"InstanceOf", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("InstanceOf", 1, 1)}}, - {"positionof", {"PositionOf", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("PositionOf", 1, 1), NYql::MakeLangVersion(2026, 1)}}, - {"withissue", {"WithIssue", "Normal", BuildSimpleBuiltinFactoryCallback<TYqlWithIssue>(), NYql::MakeLangVersion(2026, 1)}}, + {"positionof", {"PositionOf", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("PositionOf", 1, 1), NYql::NFeature::PositionOfWithIssueFunction.MinLangVer}}, + {"withissue", {"WithIssue", "Normal", BuildSimpleBuiltinFactoryCallback<TYqlWithIssue>(), NYql::NFeature::PositionOfWithIssueFunction.MinLangVer}}, {"datatype", {"DataType", "Normal", BuildSimpleBuiltinFactoryCallback<TYqlDataType>()}}, {"optionaltype", {"OptionalType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("OptionalType", 1, 1)}}, - {"asoptionaltype", {"AsOptionalType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("AsOptionalType", 1, 1), NYql::MakeLangVersion(2026, 1)}}, - {"asoptional", {"AsOptional", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("AsOptional", 1, 1), NYql::MakeLangVersion(2026, 1)}}, + {"asoptionaltype", {"AsOptionalType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("AsOptionalType", 1, 1), NYql::NFeature::AsOptionalFunction.MinLangVer}}, + {"asoptional", {"AsOptional", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("AsOptional", 1, 1), NYql::NFeature::AsOptionalFunction.MinLangVer}}, {"listtype", {"ListType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("ListType", 1, 1)}}, {"streamtype", {"StreamType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("StreamType", 1, 1)}}, {"dicttype", {"DictType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DictType", 2, 2)}}, @@ -3298,9 +3327,9 @@ struct TBuiltinFuncData { {"taggedtype", {"TaggedType", "Normal", BuildSimpleBuiltinFactoryCallback<TYqlTaggedType>()}}, {"varianttype", {"VariantType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("VariantType", 1, 1)}}, {"callabletype", {"CallableType", "Normal", BuildSimpleBuiltinFactoryCallback<TYqlCallableType>()}}, - {"lineartype", {"LinearType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("LinearType", 1, 1), NYql::MakeLangVersion(2025, 4)}}, - {"dynamiclineartype", {"DynamicLinearType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DynamicLinearType", 1, 1), NYql::MakeLangVersion(2025, 4)}}, - {"linearitemtype", {"LinearItemType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("LinearItemType", 1, 1), NYql::MakeLangVersion(2025, 4)}}, + {"lineartype", {"LinearType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("LinearType", 1, 1), NYql::NFeature::LinearTypes.MinLangVer}}, + {"dynamiclineartype", {"DynamicLinearType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DynamicLinearType", 1, 1), NYql::NFeature::LinearTypes.MinLangVer}}, + {"linearitemtype", {"LinearItemType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("LinearItemType", 1, 1), NYql::NFeature::LinearTypes.MinLangVer}}, {"optionalitemtype", {"OptionalItemType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("OptionalItemType", 1, 1)}}, {"listitemtype", {"ListItemType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("ListItemType", 1, 1)}}, {"streamitemtype", {"ListItemType", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("StreamItemType", 1, 1)}}, @@ -3335,8 +3364,8 @@ struct TBuiltinFuncData { {"datatypecomponents", {"DataTypeComponents", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DataTypeComponents", 1, 1)}}, {"datatypehandle", {"DataTypeHandle", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DataTypeHandle", 1, 1)}}, {"optionaltypehandle", {"OptionalTypeHandle", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("OptionalTypeHandle", 1, 1)}}, - {"lineartypehandle", {"LinearTypeHandle", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("LinearTypeHandle", 1, 1), NYql::MakeLangVersion(2025, 4)}}, - {"dynamiclineartypehandle", {"DynamicLinearTypeHandle", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DynamicLinearTypeHandle", 1, 1), NYql::MakeLangVersion(2025, 4)}}, + {"lineartypehandle", {"LinearTypeHandle", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("LinearTypeHandle", 1, 1), NYql::NFeature::LinearTypes.MinLangVer}}, + {"dynamiclineartypehandle", {"DynamicLinearTypeHandle", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("DynamicLinearTypeHandle", 1, 1), NYql::NFeature::LinearTypes.MinLangVer}}, {"listtypehandle", {"ListTypeHandle", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("ListTypeHandle", 1, 1)}}, {"streamtypehandle", {"StreamTypeHandle", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("StreamTypeHandle", 1, 1)}}, {"tupletypecomponents", {"TupleTypeComponents", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("TupleTypeComponents", 1, 1)}}, @@ -3412,7 +3441,7 @@ struct TBuiltinFuncData { {"coalesce", {"Coalesce", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("Coalesce", 1, -1)}}, {"nvl", {"Nvl", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("Coalesce", 1, -1)}}, {"nanvl", {"Nanvl", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("Nanvl", 2, 2)}}, - {"nullif", {"NullIf", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("NullIf", 2, 2), NYql::MakeLangVersion(2025, 4)}}, + {"nullif", {"NullIf", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("NullIf", 2, 2), NYql::NFeature::NullIfFunction.MinLangVer}}, {"likely", {"Likely", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("Likely", 1, -1)}}, {"assumestrict", {"AssumeStrict", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("AssumeStrict", 1, 1)}}, {"assumenonstrict", {"AssumeNonStrict", "Normal", BuildNamedArgcBuiltinFactoryCallback<TCallNodeImpl>("AssumeNonStrict", 1, 1)}}, @@ -3572,8 +3601,8 @@ struct TBuiltinFuncData { {"first", {"First", "MatchRec", BuildAggrFuncFactoryCallback("First", "first_traits_factory")}}, {"last", {"Last", "MatchRec", BuildAggrFuncFactoryCallback("Last", "last_traits_factory")}}, - {"randomsample", {"RandomSample", "Agg", BuildAggrFuncFactoryCallback("RandomSample", "random_sample_factory", RANDOM_SAMPLE), NYql::MakeLangVersion(2025, 4)}}, - {"randomvalue", {"RandomValue", "Agg", BuildAggrFuncFactoryCallback("RandomValue", "random_value_factory", RANDOM_VALUE), NYql::MakeLangVersion(2025, 4)}}, + {"randomsample", {"RandomSample", "Agg", BuildAggrFuncFactoryCallback("RandomSample", "random_sample_factory", RANDOM_SAMPLE), NYql::NFeature::RandomValueSampleFunction.MinLangVer}}, + {"randomvalue", {"RandomValue", "Agg", BuildAggrFuncFactoryCallback("RandomValue", "random_value_factory", RANDOM_VALUE), NYql::NFeature::RandomValueSampleFunction.MinLangVer}}, }; // NOLINTEND(modernize-use-designated-initializers) return aggrFuncs; @@ -4218,6 +4247,28 @@ TNodeResult BuildBuiltinFunc( return TNonNull(TNodePtr(new TInvalidBuiltin(pos, "DISTINCT can only be used in aggregation functions"))); } + if ((normalizedName == "pgcall" || normalizedName == "pgrangecall") && mustUseNamed && *mustUseNamed) { + // PgCall/PgRangeCall support one named argument (collation => ...); TYqlPgCall::DoInit + // finds it by label, so just flatten the (positional tuple, named struct) pair that + // named-arg calls get reshaped into back into a single labeled arg list. + *mustUseNamed = false; + YQL_ENSURE(args.size() == 2); + auto positional = args[0]->GetTupleNode(); + YQL_ENSURE(positional); + auto named = args[1]->GetStructNode(); + YQL_ENSURE(named); + + TVector<TNodePtr> flatArgs(positional->Elements()); + for (const auto& arg : named->GetExprs()) { + flatArgs.push_back(arg); + } + + const bool isRange = (normalizedName == "pgrangecall"); + return TNonNull(isRange + ? TNodePtr(new TYqlPgCall<true>(pos, flatArgs)) + : TNodePtr(new TYqlPgCall<false>(pos, flatArgs))); + } + auto builtinCallback = builtinFuncs.find(normalizedName); if (builtinCallback != builtinFuncs.end()) { const auto& funcInfo = builtinCallback->second; diff --git a/yql/essentials/sql/v1/format/sql_format_ut.h b/yql/essentials/sql/v1/format/sql_format_ut.h index ce5d448c9dd..6406608c26b 100644 --- a/yql/essentials/sql/v1/format/sql_format_ut.h +++ b/yql/essentials/sql/v1/format/sql_format_ut.h @@ -432,6 +432,20 @@ Y_UNIT_TEST(CreateTable) { {"create table\tuser(key int32, val String encoding(off))", "CREATE TABLE user (\n\tkey int32,\n\tval String ENCODING (off)\n);\n"}, {"create table\tuser(key int32, val String encoding())", "CREATE TABLE user (\n\tkey int32,\n\tval String ENCODING ()\n);\n"}, {"create table user(key int32, val String encoding(dict(max_size=100)))", "CREATE TABLE user (\n\tkey int32,\n\tval String ENCODING (dict (max_size = 100))\n);\n"}, + {"create table user(key int32, val int64 generated always as (key+1) stored)", + "CREATE TABLE user (\n\tkey int32,\n\tval int64 GENERATED ALWAYS AS (key + 1) STORED\n);\n"}, + {"create table user(key int32, val int64 generated always as (key+1) virtual)", + "CREATE TABLE user (\n\tkey int32,\n\tval int64 GENERATED ALWAYS AS (key + 1) VIRTUAL\n);\n"}, + {"create table user(key int32, val int64 generated always as (key+1))", + "CREATE TABLE user (\n\tkey int32,\n\tval int64 GENERATED ALWAYS AS (key + 1)\n);\n"}, + {"create table user(key int32, val int64 as (key+1) stored)", + "CREATE TABLE user (\n\tkey int32,\n\tval int64 AS (key + 1) STORED\n);\n"}, + {"create table user(key int32, val int64 as (key+1))", + "CREATE TABLE user (\n\tkey int32,\n\tval int64 AS (key + 1)\n);\n"}, + {"create table user(key int32, val int64 GeNeRaTeD AlWaYs As (key+1) StOrEd)", + "CREATE TABLE user (\n\tkey int32,\n\tval int64 GENERATED ALWAYS AS (key + 1) STORED\n);\n"}, + {"create table user(key int32, val int64 (not null, generated always as (key+1) stored))", + "CREATE TABLE user (\n\tkey int32,\n\tval int64 (NOT NULL, GENERATED ALWAYS AS (key + 1) STORED)\n);\n"}, }; TSetup setup; @@ -691,6 +705,18 @@ Y_UNIT_TEST(AlterTable) { "ALTER TABLE t\n\tALTER COLUMN c SET ENCODING ()\n;\n"}, {"alter table t alter column c set encoding(dict(max_size=100))", "ALTER TABLE t\n\tALTER COLUMN c SET ENCODING (dict (max_size = 100))\n;\n"}, + {"alter table user add column val int64 generated always as (key+1) stored", + "ALTER TABLE user\n\tADD COLUMN val int64 GENERATED ALWAYS AS (key + 1) STORED\n;\n"}, + {"alter table user add column val int64 generated always as (key+1) virtual", + "ALTER TABLE user\n\tADD COLUMN val int64 GENERATED ALWAYS AS (key + 1) VIRTUAL\n;\n"}, + {"alter table user add column val int64 generated always as (key+1)", + "ALTER TABLE user\n\tADD COLUMN val int64 GENERATED ALWAYS AS (key + 1)\n;\n"}, + {"alter table user add column val int64 as (key+1) stored", + "ALTER TABLE user\n\tADD COLUMN val int64 AS (key + 1) STORED\n;\n"}, + {"alter table user add val int64 GeNeRaTeD AlWaYs As (key+1) StOrEd", + "ALTER TABLE user\n\tADD val int64 GENERATED ALWAYS AS (key + 1) STORED\n;\n"}, + {"alter table user add column val int64 (not null, generated always as (key+1) stored)", + "ALTER TABLE user\n\tADD COLUMN val int64 (NOT NULL, GENERATED ALWAYS AS (key + 1) STORED)\n;\n"}, }; TSetup setup; diff --git a/yql/essentials/sql/v1/node.h b/yql/essentials/sql/v1/node.h index c9e8b16a799..b79eb7d04bc 100644 --- a/yql/essentials/sql/v1/node.h +++ b/yql/essentials/sql/v1/node.h @@ -791,12 +791,19 @@ struct TEncoding { TMap<TString, TNodePtr> Entries; }; +struct TGeneratedColumn { + TString ContextPrefix; + TString ExprBody; + bool Stored = false; +}; + struct TColumnOptions { TNodePtr DefaultExpr; TVector<TIdentifier> Families; TMaybe<TCompression> Compression; bool Nullable = true; TMaybe<TVector<TEncoding>> ColumnEncoding; + TMaybe<TGeneratedColumn> Generated; }; struct TColumnSchema { @@ -821,6 +828,7 @@ struct TColumnSchema { bool Nullable = false; bool Serial = false; TMaybe<TVector<TEncoding>> ColumnEncoding; + TMaybe<TGeneratedColumn> Generated; }; struct TColumns: public TSimpleRefCount<TColumns> { diff --git a/yql/essentials/sql/v1/query.cpp b/yql/essentials/sql/v1/query.cpp index 6ec40c25ce5..0276df12534 100644 --- a/yql/essentials/sql/v1/query.cpp +++ b/yql/essentials/sql/v1/query.cpp @@ -490,6 +490,13 @@ INode::TPtr CreateEncodingsListSettings(const TVector<TEncoding>& columnEncoding return encodingsList; } +INode::TPtr CreateGeneratedValue(const TGeneratedColumn& generated, TPosition pos, const INode& node) { + const ui32 flags = NYql::TAstNodeFlags::ArbitraryContent | NYql::TAstNodeFlags::UnstableFormat; + auto contextText = BuildQuotedAtom(pos, generated.ContextPrefix, flags); + auto exprText = BuildQuotedAtom(pos, generated.ExprBody, flags); + return node.Q(node.Y(contextText, exprText, node.Q(generated.Stored ? "stored" : "virtual"))); +} + } // namespace class TPrepTableKeys: public ITableKeys { @@ -1357,6 +1364,11 @@ public: columnConstraints = L(columnConstraints, Q(Y(Q("default"), col.DefaultExpr))); } + if (col.Generated) { + auto generatedValue = CreateGeneratedValue(*col.Generated, Pos_, *this); + columnConstraints = L(columnConstraints, Q(Y(Q("generated"), generatedValue))); + } + columnDesc = L(columnDesc, Q(Y(Q("columnConstrains"), Q(columnConstraints)))); if (col.Compression) { @@ -1715,6 +1727,11 @@ public: columnConstraints = L(columnConstraints, Q(Y(Q("default"), col.DefaultExpr))); } + if (col.Generated) { + auto generatedValue = CreateGeneratedValue(*col.Generated, Pos_, *this); + columnConstraints = L(columnConstraints, Q(Y(Q("generated"), generatedValue))); + } + columnDesc = L(columnDesc, Q(Y(Q("columnConstrains"), Q(columnConstraints)))); if (col.Compression) { diff --git a/yql/essentials/sql/v1/select.cpp b/yql/essentials/sql/v1/select.cpp index c1dc2acea1d..a2af18a0a6e 100644 --- a/yql/essentials/sql/v1/select.cpp +++ b/yql/essentials/sql/v1/select.cpp @@ -3916,15 +3916,16 @@ TSourcePtr BuildCombine(TPosition pos, TSourcePtr leftSource, TVector<TSortSpeci class TWatermarkSource: public IProxySource { public: - TWatermarkSource(TPosition pos, TSourcePtr src, TNodePtr watermarkLambda) + TWatermarkSource(TPosition pos, TSourcePtr src, TNodePtr watermarkLambda, TNodePtr watermarkSettings) : IProxySource(pos, src.Get()) , SourcePtr_(src) , WatermarkLambda_(std::move(watermarkLambda)) + , WatermarkSettings_(std::move(watermarkSettings)) { } TNodePtr Build(TContext& ctx) final { - return Y("WatermarkGenerator", SourcePtr_->Build(ctx), WatermarkLambda_); + return Y("WatermarkGenerator", SourcePtr_->Build(ctx), WatermarkLambda_, WatermarkSettings_); } bool DoInit(TContext& ctx, ISource* src) final { @@ -3936,20 +3937,25 @@ public: return false; } + if (!WatermarkSettings_->Init(ctx, this)) { + return false; + } + return IProxySource::DoInit(ctx, src); } TNodePtr DoClone() const final { - return MakeIntrusive<TWatermarkSource>(Pos_, SourcePtr_->CloneSource(), WatermarkLambda_->Clone()); + return MakeIntrusive<TWatermarkSource>(Pos_, SourcePtr_->CloneSource(), WatermarkLambda_->Clone(), WatermarkSettings_->Clone()); } private: TSourcePtr SourcePtr_; TNodePtr WatermarkLambda_; + TNodePtr WatermarkSettings_; }; -TSourcePtr BuildWatermarkSource(TPosition pos, TSourcePtr src, TNodePtr watermarkLambda) { - return MakeIntrusive<TWatermarkSource>(pos, std::move(src), std::move(watermarkLambda)); +TSourcePtr BuildWatermarkSource(TPosition pos, TSourcePtr src, TNodePtr watermarkLambda, TNodePtr watermarkSettings) { + return MakeIntrusive<TWatermarkSource>(pos, std::move(src), std::move(watermarkLambda), std::move(watermarkSettings)); } } // namespace NSQLTranslationV1 diff --git a/yql/essentials/sql/v1/source.h b/yql/essentials/sql/v1/source.h index 1ebd69d050d..54a1127f0c6 100644 --- a/yql/essentials/sql/v1/source.h +++ b/yql/essentials/sql/v1/source.h @@ -314,7 +314,7 @@ TSourcePtr BuildSelectCore( TColumnsSets&& distinctSets); TSourcePtr BuildSelect(TPosition pos, TSourcePtr source, TNodePtr skipTake); TSourcePtr BuildAnyColumnSource(TPosition pos); -TSourcePtr BuildWatermarkSource(TPosition pos, TSourcePtr src, TNodePtr watermarkLambda); +TSourcePtr BuildWatermarkSource(TPosition pos, TSourcePtr src, TNodePtr watermarkLambda, TNodePtr watermarkSettings); enum class EReduceMode { ByPartition, diff --git a/yql/essentials/sql/v1/sql_query.cpp b/yql/essentials/sql/v1/sql_query.cpp index c6de747288b..42228fa5586 100644 --- a/yql/essentials/sql/v1/sql_query.cpp +++ b/yql/essentials/sql/v1/sql_query.cpp @@ -2778,6 +2778,17 @@ bool TSqlQuery::AlterTableAddColumn(const TRule_alter_table_add_column& node, TA Ctx_.Error() << "Several column families for a single column are not yet supported"; return false; } + if (columnSchema->Generated) { + const TString& service = Ctx_.Scoped->CurrService; + if (service != KikimrProviderName && service != YdbProviderName) { + Ctx_.Error(columnSchema->Pos) << "GENERATED ALWAYS AS columns are supported only for the ydb provider"; + return false; + } + if (params.TableType != ETableType::Table) { + Ctx_.Error(columnSchema->Pos) << "GENERATED ALWAYS AS columns are supported only for CREATE TABLE and ALTER TABLE"; + return false; + } + } params.AddColumns.push_back(*columnSchema); return true; } @@ -5020,6 +5031,11 @@ bool BuildColumnFeatures(std::map<TString, TDeferredAtom>& result, const TRule_c return false; } + if (options->Generated) { + translation.Context().Error(pos) << "GENERATED ALWAYS AS columns are supported only for CREATE TABLE and ALTER TABLE"; + return false; + } + auto& typeBind = columnSchema.GetRule_type_name_or_bind2(); switch (typeBind.Alt_case()) { case TRule_type_name_or_bind::kAltTypeNameOrBind1: { diff --git a/yql/essentials/sql/v1/sql_select.cpp b/yql/essentials/sql/v1/sql_select.cpp index 48a87dd301c..6d1dbe6fac4 100644 --- a/yql/essentials/sql/v1/sql_select.cpp +++ b/yql/essentials/sql/v1/sql_select.cpp @@ -630,11 +630,28 @@ TSourcePtr TSqlSelect::HintedSingleSource( } TNodePtr watermarkLambda; - if (auto it = hints.find("watermark"); it != hints.end() && !ret->IsTableSource()) { - auto& exprs = it->second; - YQL_ENSURE(exprs.size() == 1); - watermarkLambda = std::move(exprs[0]); - hints.erase(it); + TTableHints watermarkHints; + if (!ret->IsTableSource()) { + for (auto it = hints.begin(); it != hints.end();) { + auto& [name, value] = *it; + auto normalizedName = name; + auto normalizeError = NormalizeName(Ctx_.Pos(), normalizedName); + if (!normalizeError.Empty()) { + YQL_ENSURE(value.empty() || value.front()); + const auto pos = value ? value.front()->GetPos() : Ctx_.Pos(); + Ctx_.Error(pos) << normalizeError->GetMessage(); + return nullptr; + } else if (normalizedName == "watermark") { + YQL_ENSURE(value.size() == 1); + watermarkLambda = std::move(value[0]); + it = hints.erase(it); + } else if (normalizedName.StartsWith("watermark")) { + watermarkHints.emplace(normalizedName, std::move(value)); + it = hints.erase(it); + } else { + ++it; + } + } } if (hints || contextHints) { @@ -649,7 +666,12 @@ TSourcePtr TSqlSelect::HintedSingleSource( if (watermarkLambda) { auto pos = watermarkLambda->GetPos(); - ret = BuildWatermarkSource(std::move(pos), std::move(ret), std::move(watermarkLambda)); + auto watermarkSettings = BuildInputOptions(pos, watermarkHints); + if (!watermarkSettings) { + watermarkSettings = BuildList(pos); + } + watermarkSettings = BuildQuote(pos, watermarkSettings); + ret = BuildWatermarkSource(std::move(pos), std::move(ret), std::move(watermarkLambda), std::move(watermarkSettings)); } return ret; diff --git a/yql/essentials/sql/v1/sql_translation.cpp b/yql/essentials/sql/v1/sql_translation.cpp index 141d416f6d4..c40afa7705e 100644 --- a/yql/essentials/sql/v1/sql_translation.cpp +++ b/yql/essentials/sql/v1/sql_translation.cpp @@ -17,6 +17,7 @@ #include <util/generic/scope.h> #include <util/string/join.h> +#include <util/string/strip.h> #include <library/cpp/protobuf/util/simple_reflection.h> @@ -1731,6 +1732,7 @@ TMaybe<TColumnOptions> ColumnOptions(const TRule_column_schema& node, TSqlTransl TMaybe<TCompression> compression; bool nullable = true; TMaybe<TVector<TEncoding>> columnEncoding; + TMaybe<TGeneratedColumn> generated; const auto& optionsList = node.GetRule_column_option_list3(); @@ -1740,9 +1742,10 @@ TMaybe<TColumnOptions> ColumnOptions(const TRule_column_schema& node, TSqlTransl DefaultValue, Compression, Encoding, + Generated, }; - TVector<TRule_column_option> columnOptions(Reserve(static_cast<size_t>(EOption::Encoding) + 1)); + TVector<TRule_column_option> columnOptions(Reserve(static_cast<size_t>(EOption::Generated) + 1)); { switch (optionsList.Alt_case()) { @@ -1873,6 +1876,57 @@ TMaybe<TColumnOptions> ColumnOptions(const TRule_column_schema& node, TSqlTransl break; } + case TRule_column_option::kAltColumnOption6: { // generated_always + const auto& opt = rule.GetAlt_column_option6().GetRule_generated_always1(); + if (std::find(usedOptions.begin(), usedOptions.end(), EOption::Generated) != usedOptions.end()) { + TPosition pos = ctx.Context().TokenPosition(opt.GetToken2()); + ctx.Context().Error(pos) << "'GENERATED ALWAYS AS' option can be specified only once"; + return {}; + } + + usedOptions.push_back(EOption::Generated); + + { + TColumnRefScope scope(ctx.Context(), EColumnRefState::Allow); + TSqlExpression expr(ctx); + if (!Unwrap(expr.Build(opt.GetRule_expr4()))) { + return {}; + } + } + + TStringBuilder contextPrefix; + if (!BuildContextRecreationQuery(ctx.Context(), contextPrefix)) { + return {}; + } + + const auto& lparen = opt.GetToken3(); + const auto& rparen = opt.GetToken5(); + auto begin = GetQueryPosition(ctx.Context().Query, lparen); + auto end = GetQueryPosition(ctx.Context().Query, rparen); + + if (begin == std::string::npos || end == std::string::npos) { + TPosition pos = ctx.Context().TokenPosition(opt.GetToken2()); + ctx.Context().Error(pos) << "Failed to extract GENERATED ALWAYS AS expression text"; + return {}; + } + + begin += lparen.value().size(); + TString exprBody = StripString(ctx.Context().Query.substr(begin, end - begin)); + + bool generatedStored = false; + if (opt.HasBlock6()) { + const auto tokenId = opt.GetBlock6().GetToken1().GetId(); + generatedStored = IS_TOKEN(tokenId, STORED); + } + + generated = TGeneratedColumn{ + .ContextPrefix = std::move(contextPrefix), + .ExprBody = std::move(exprBody), + .Stored = generatedStored, + }; + + break; + } case TRule_column_option::ALT_NOT_SET: YQL_ENSURE(false, "Unreachable"); } @@ -1885,6 +1939,7 @@ TMaybe<TColumnOptions> ColumnOptions(const TRule_column_schema& node, TSqlTransl .Compression = std::move(compression), .Nullable = nullable, .ColumnEncoding = std::move(columnEncoding), + .Generated = std::move(generated), }; } @@ -1899,7 +1954,7 @@ TMaybe<TColumnSchema> TSqlTranslation::ColumnSchemaImpl(const TRule_column_schem return {}; } - auto&& [defaultExpr, families, compression, nullable, columnEncoding] = columnOptions.GetRef(); + auto&& [defaultExpr, families, compression, nullable, columnEncoding, generated] = columnOptions.GetRef(); if (!type) { type = TypeNodeOrBind(node.GetRule_type_name_or_bind2()); @@ -1919,6 +1974,7 @@ TMaybe<TColumnSchema> TSqlTranslation::ColumnSchemaImpl(const TRule_column_schem .Nullable = nullable, .Serial = serial, .ColumnEncoding = columnEncoding, + .Generated = generated, }; } @@ -2056,6 +2112,17 @@ bool TSqlTranslation::CreateTableEntry(const TRule_create_table_entry& node, TCr Ctx_.Error() << "Several column families for a single column are not yet supported"; return false; } + if (columnSchema->Generated) { + const TString& service = Ctx_.Scoped->CurrService; + if (service != KikimrProviderName && service != YdbProviderName) { + Ctx_.Error(columnSchema->Pos) << "GENERATED ALWAYS AS columns are supported only for the ydb provider"; + return false; + } + if (params.TableType != ETableType::Table) { + Ctx_.Error(columnSchema->Pos) << "GENERATED ALWAYS AS columns are supported only for CREATE TABLE and ALTER TABLE"; + return false; + } + } params.Columns.push_back(*columnSchema); break; } diff --git a/yql/essentials/sql/v1/sql_ut_common.h b/yql/essentials/sql/v1/sql_ut_common.h index 05e7d38b87f..8558598e1da 100644 --- a/yql/essentials/sql/v1/sql_ut_common.h +++ b/yql/essentials/sql/v1/sql_ut_common.h @@ -12209,6 +12209,33 @@ Y_UNIT_TEST(ParseProtoseq) { UNIT_ASSERT_VALUES_EQUAL(stat["WatermarkGenerator"], 1); } +Y_UNIT_TEST(ComplexParseWithOptions) { + auto res = SqlToYql(R"sql( + USE plato; + + $input = SELECT Protobuf::Parse(records) AS event FROM Input + FLATTEN LIST BY (ChunksSplitters::Protoseq(Data).SplitRecords AS records); + + SELECT * FROM $input + WITH ( + WATERMARK = event.ts - Interval("PT1S"), + WATERMARK_GRANULARITY = "PT1S", + WATERMARK_IDLE_TIMEOUT = "PT1M" + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + TWordCountHive stat = { + "WatermarkGenerator", + "watermarkgranularity", + "watermarkidletimeout", + }; + VerifyProgram(res, stat); + UNIT_ASSERT_VALUES_EQUAL(stat["WatermarkGenerator"], 1); + UNIT_ASSERT_VALUES_EQUAL(stat["watermarkgranularity"], 1); + UNIT_ASSERT_VALUES_EQUAL(stat["watermarkidletimeout"], 1); +} + Y_UNIT_TEST(ExprList) { auto res = SqlToYql(R"sql( USE plato; @@ -13532,7 +13559,7 @@ Y_UNIT_TEST(ReadsNamedNodeExpresionSubquery) { Y_UNIT_TEST(ReadsProjectionFromSubquery) { NSQLTranslation::TTranslationSettings s; - s.LangVer = NYql::MakeLangVersion(2025, 4); + s.LangVer = NYql::NFeature::InlineSubquery.MinLangVer; NYql::TAstParseResult res = SqlToYqlWithSettings(R"sql( SELECT (SELECT a FROM plato.x) FROM (SELECT * FROM plato.y); @@ -16163,4 +16190,415 @@ Y_UNIT_TEST(OptionalNull) { } // Y_UNIT_TEST_SUITE(NullAsTypeName) +Y_UNIT_TEST_SUITE(GeneratedColumns) { + +Y_UNIT_TEST(CreateTableGeneratedVirtualByDefault) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE TABLE tbl ( + key Uint32, + gen Int32 AS (5), + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"5" 'virtual)))#"); + UNIT_ASSERT(!program.Contains("stored")); + UNIT_ASSERT(!program.Contains(R"#((Int32 '"5"))#")); +} + +Y_UNIT_TEST(CreateTableGeneratedExplicitVirtual) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE TABLE tbl ( + key Uint32, + gen Int32 AS (5) VIRTUAL, + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"5" 'virtual)))#"); + UNIT_ASSERT(!program.Contains("stored")); +} + +Y_UNIT_TEST(CreateTableGeneratedStored) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE TABLE tbl ( + key Uint32, + gen Int32 AS (5) STORED, + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"5" 'stored)))#"); + UNIT_ASSERT(!program.Contains("virtual")); +} + +Y_UNIT_TEST(CreateTableGeneratedAlwaysKeyword) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE TABLE tbl ( + key Uint32, + gen Int32 GENERATED ALWAYS AS (5) STORED, + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"5" 'stored)))#"); + UNIT_ASSERT(!program.Contains("virtual")); +} + +Y_UNIT_TEST(CreateTableGeneratedWithoutAlways) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE TABLE tbl ( + id Uint32, + Col2 Uint32, + Col3 Uint32, + Col1 Uint32 AS (2 + 3), + PRIMARY KEY (id) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"2 + 3" 'virtual)))#"); + UNIT_ASSERT(!program.Contains("stored")); +} + +Y_UNIT_TEST(CreateTableGeneratedDeterministicExpr) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE TABLE tbl ( + key Uint32, + gen Int32 AS (1 + 2) VIRTUAL, + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"1 + 2" 'virtual)))#"); + UNIT_ASSERT(!program.Contains("stored")); +} + +Y_UNIT_TEST(CreateTableGeneratedNonDeterministicExpr) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE TABLE tbl ( + key Uint32, + gen Uint64 AS (RandomNumber(1)) STORED, + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"RandomNumber(1)" 'stored)))#"); + UNIT_ASSERT(!program.Contains("virtual")); +} + +Y_UNIT_TEST(CreateTableGeneratedReferencesColumns) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE TABLE tbl ( + a Int32, + b Int32, + gen Int32 GENERATED ALWAYS AS (a + b) STORED, + PRIMARY KEY (a) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"a + b" 'stored)))#"); + UNIT_ASSERT(!program.Contains("virtual")); +} + +Y_UNIT_TEST(CreateTableMultipleGeneratedColumns) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE TABLE tbl ( + key Uint32, + x Int32 AS (1) VIRTUAL, + y Int32 AS (2) STORED, + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"1" 'virtual)))#"); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"2" 'stored)))#"); +} + +Y_UNIT_TEST(AlterTableAddGeneratedVirtualColumn) { + auto res = SqlToYql(R"sql( + USE ydb; + ALTER TABLE tbl ADD COLUMN gen Int32 AS (5); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, "addColumns"); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"5" 'virtual)))#"); + UNIT_ASSERT(!program.Contains("stored")); +} + +Y_UNIT_TEST(AlterTableAddGeneratedStoredColumn) { + auto res = SqlToYql(R"sql( + USE ydb; + ALTER TABLE tbl ADD COLUMN gen Int32 GENERATED ALWAYS AS (a + b) STORED; + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, "addColumns"); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"a + b" 'stored)))#"); + UNIT_ASSERT(!program.Contains("virtual")); + UNIT_ASSERT(!program.Contains("(Member row")); +} + +Y_UNIT_TEST(AlterTableAddGeneratedColumnWithNamedLambda) { + auto res = SqlToYql(R"sql( + USE ydb; + $f = ($x) -> ($x * 2); + ALTER TABLE tbl ADD COLUMN gen Int32 GENERATED ALWAYS AS ($f(a)) STORED; + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, "addColumns"); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n$f = ($x) -> ($x * 2);\n" '"$f(a)" 'stored)))#"); + UNIT_ASSERT(!program.Contains("virtual")); +} + +Y_UNIT_TEST(CreateTableGeneratedColumnWithNamedNodeRef) { + auto res = SqlToYql(R"sql( + USE ydb; + $factor = 10; + CREATE TABLE tbl ( + key Uint32, + num Int32, + gen Int32 GENERATED ALWAYS AS (num * $factor) STORED, + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n$factor = 10;\n" '"num * $factor" 'stored)))#"); + UNIT_ASSERT(!program.Contains("virtual")); + UNIT_ASSERT(!program.Contains("Read")); +} + +Y_UNIT_TEST(CreateTableGeneratedColumnWithSubqueryNamedNodeRef) { + auto res = SqlToYql(R"sql( + USE ydb; + $ids = (SELECT id FROM other); + CREATE TABLE tbl ( + key Uint32, + num Int32, + gen Bool GENERATED ALWAYS AS (num IN $ids) STORED, + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n$ids = (SELECT id FROM other);\n" '"num IN $ids" 'stored)))#"); + UNIT_ASSERT(!program.Contains("virtual")); + UNIT_ASSERT(!program.Contains("Read")); +} + +Y_UNIT_TEST(CreateTableGeneratedColumnWithInlineSubquery) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE TABLE tbl ( + key Uint32, + num Int32, + gen Bool GENERATED ALWAYS AS (num IN (SELECT id FROM other)) STORED, + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"num IN (SELECT id FROM other)" 'stored)))#"); + UNIT_ASSERT(!program.Contains("virtual")); + UNIT_ASSERT(!program.Contains("Read")); +} + +Y_UNIT_TEST(CreateTableGeneratedColumnWithScalarSubquery) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE TABLE tbl ( + key Uint32, + num Int32, + gen Int32 GENERATED ALWAYS AS ((SELECT MAX(id) FROM other)) STORED, + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"(SELECT MAX(id) FROM other)" 'stored)))#"); + UNIT_ASSERT(!program.Contains("virtual")); + UNIT_ASSERT(!program.Contains("Read")); +} + +Y_UNIT_TEST(CreateTableGeneratedWithPragma) { + auto res = SqlToYql(R"sql( + USE ydb; + PRAGMA FlexibleTypes; + CREATE TABLE tbl ( + key Uint32, + num Int32, + gen Int32 GENERATED ALWAYS AS (num / 2) STORED, + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\nPRAGMA FlexibleTypes;\n" '"num / 2" 'stored)))#"); + UNIT_ASSERT(!program.Contains("virtual")); +} + +Y_UNIT_TEST(CreateTableGeneratedNestedPath) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE TABLE `a/b/tbl` ( + key Uint32, + num Int32, + gen Int32 GENERATED ALWAYS AS (num / 2) STORED, + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"num / 2" 'stored)))#"); +} + +Y_UNIT_TEST(AlterTableAddGeneratedColumnNestedPath) { + auto res = SqlToYql(R"sql( + USE ydb; + ALTER TABLE `a/b/tbl` ADD COLUMN gen Int32 GENERATED ALWAYS AS (a + b) STORED; + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"a + b" 'stored)))#"); +} + +Y_UNIT_TEST(CreateTableStoreGeneratedColumnFails) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE TABLESTORE tbl ( + key Uint32 NOT NULL, + gen Int32 AS (5) STORED, + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "GENERATED ALWAYS AS columns are supported only for CREATE TABLE and ALTER TABLE"); +} + +Y_UNIT_TEST(CreateExternalTableGeneratedColumnFails) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE EXTERNAL TABLE tbl ( + a Int32, + gen Int32 AS (a) STORED + ) WITH ( + DATA_SOURCE="/Root/mydatasource", + LOCATION="/folder1/*" + ); + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "GENERATED ALWAYS AS columns are supported only for CREATE TABLE and ALTER TABLE"); +} + +Y_UNIT_TEST(AlterExternalTableAddGeneratedColumnFails) { + auto res = SqlToYql(R"sql( + USE ydb; + ALTER EXTERNAL TABLE tbl ADD COLUMN gen Int32 AS (5) STORED; + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "GENERATED ALWAYS AS columns are supported only for CREATE TABLE and ALTER TABLE"); +} + +Y_UNIT_TEST(AlterTableStoreAddGeneratedColumnFails) { + auto res = SqlToYql(R"sql( + USE ydb; + ALTER TABLESTORE tbl ADD COLUMN gen Int32 AS (5) STORED; + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "GENERATED ALWAYS AS columns are supported only for CREATE TABLE and ALTER TABLE"); +} + +Y_UNIT_TEST(CreateTableGeneratedColumnNonYdbProviderFails) { + auto res = SqlToYql(R"sql( + USE plato; + CREATE TABLE tbl ( + a Int32, + gen Int32 AS (a) STORED, + PRIMARY KEY (a) + ); + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "GENERATED ALWAYS AS columns are supported only for the ydb provider"); +} + +Y_UNIT_TEST(CreateTableGeneratedColumnNonYdbProviderVirtualFails) { + auto res = SqlToYql(R"sql( + USE plato; + CREATE TABLE tbl ( + key Uint32, + gen Int32 GENERATED ALWAYS AS (5), + PRIMARY KEY (key) + ); + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "GENERATED ALWAYS AS columns are supported only for the ydb provider"); +} + +Y_UNIT_TEST(AlterTableAddGeneratedColumnNonYdbProviderFails) { + auto res = SqlToYql(R"sql( + USE plato; + ALTER TABLE tbl ADD COLUMN gen Int32 AS (5) STORED; + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_STRING_CONTAINS(Err2Str(res), "ALTER TABLE is not supported for yt provider"); +} + +Y_UNIT_TEST(CreateTableGeneratedStringFunction) { + auto res = SqlToYql(R"sql( + USE ydb; + CREATE TABLE tbl ( + id Int32, + name String, + upper_name String GENERATED ALWAYS AS (String::AsciiToUpper(name)) STORED, + PRIMARY KEY (id) + ); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + const auto program = GetPrettyPrint(res); + UNIT_ASSERT_STRING_CONTAINS(program, R"#('('generated '('"USE ydb;\n" '"String::AsciiToUpper(name)" 'stored)))#"); + UNIT_ASSERT(!program.Contains("virtual")); +} + +} // Y_UNIT_TEST_SUITE(GeneratedColumns) + // NOLINTEND(misc-definitions-in-headers) |
