summaryrefslogtreecommitdiffstats
path: root/yql/essentials/sql/pg/pg_sql.cpp
diff options
context:
space:
mode:
authorvvvv <[email protected]>2024-11-07 12:29:36 +0300
committervvvv <[email protected]>2024-11-07 13:49:47 +0300
commitd4c258e9431675bab6745c8638df6e3dfd4dca6b (patch)
treeb5efcfa11351152a4c872fccaea35749141c0b11 /yql/essentials/sql/pg/pg_sql.cpp
parent13a4f274caef5cfdaf0263b24e4d6bdd5521472b (diff)
Moved other yql/essentials libs YQL-19206
init commit_hash:7d4c435602078407bbf20dd3c32f9c90d2bbcbc0
Diffstat (limited to 'yql/essentials/sql/pg/pg_sql.cpp')
-rw-r--r--yql/essentials/sql/pg/pg_sql.cpp6421
1 files changed, 6421 insertions, 0 deletions
diff --git a/yql/essentials/sql/pg/pg_sql.cpp b/yql/essentials/sql/pg/pg_sql.cpp
new file mode 100644
index 00000000000..fb69420d490
--- /dev/null
+++ b/yql/essentials/sql/pg/pg_sql.cpp
@@ -0,0 +1,6421 @@
+#include "../../parser/pg_wrapper/pg_compat.h"
+
+#ifdef _WIN32
+#define __restrict
+#endif
+
+#define TypeName PG_TypeName
+#define SortBy PG_SortBy
+#undef SIZEOF_SIZE_T
+extern "C" {
+#include "postgres.h"
+#include "nodes/pg_list.h"
+#include "nodes/parsenodes.h"
+#include "nodes/value.h"
+#undef Min
+#undef Max
+#undef TypeName
+#undef SortBy
+
+#undef TRACE
+#undef INFO
+#undef WARNING
+#undef ERROR
+#undef FATAL
+#undef NOTICE
+}
+
+#include "util/charset/utf8.h"
+#include "utils.h"
+#include <yql/essentials/ast/yql_expr.h>
+#include <yql/essentials/sql/settings/partitioning.h>
+#include <yql/essentials/parser/pg_wrapper/interface/config.h>
+#include <yql/essentials/parser/pg_wrapper/interface/parser.h>
+#include <yql/essentials/parser/pg_wrapper/interface/utils.h>
+#include <yql/essentials/parser/pg_wrapper/interface/raw_parser.h>
+#include <yql/essentials/parser/pg_wrapper/postgresql/src/backend/catalog/pg_type_d.h>
+#include <yql/essentials/parser/pg_catalog/catalog.h>
+#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/sql_types/yql_callable_names.h>
+#include <yql/essentials/parser/pg_catalog/catalog.h>
+#include <yql/essentials/utils/log/log_level.h>
+#include <yql/essentials/utils/log/log.h>
+#include <util/string/builder.h>
+#include <util/string/cast.h>
+#include <util/string/join.h>
+#include <util/string/split.h>
+#include <util/generic/scope.h>
+#include <util/generic/stack.h>
+#include <util/generic/hash_set.h>
+
+constexpr auto PREPARED_PARAM_PREFIX = "$p";
+constexpr auto AUTO_PARAM_PREFIX = "a";
+constexpr auto DEFAULT_PARAM_TYPE = "unknown";
+
+namespace NSQLTranslationPG {
+
+using namespace NYql;
+
+static const THashSet<TString> SystemColumns = { "tableoid", "xmin", "cmin", "xmax", "cmax", "ctid" };
+
+template <typename T>
+const T* CastNode(const void* nodeptr, int tag) {
+ Y_ENSURE(nodeTag(nodeptr) == tag);
+ return static_cast<const T*>(nodeptr);
+}
+
+const Node* Expr2Node(const Expr* e) {
+ return reinterpret_cast<const Node*>(e);
+}
+
+int NodeTag(const Node* node) {
+ return nodeTag(node);
+}
+
+int NodeTag(const ValUnion& val) {
+ return NodeTag(&val.node);
+}
+
+int IntVal(const ValUnion& val) {
+ Y_ENSURE(val.node.type == T_Integer);
+ return intVal(&val.node);
+}
+
+bool BoolVal(const ValUnion& val) {
+ Y_ENSURE(val.node.type == T_Boolean);
+ return boolVal(&val.node);
+}
+
+const char* StrFloatVal(const ValUnion& val) {
+ Y_ENSURE(val.node.type == T_Float);
+ return strVal(&val.node);
+}
+
+const char* StrVal(const ValUnion& val) {
+ Y_ENSURE(val.node.type == T_String || val.node.type == T_BitString);
+ return strVal(&val.node);
+}
+
+int BoolVal(const Node* node) {
+ Y_ENSURE(node->type == T_Boolean);
+ return boolVal(node);
+}
+
+int IntVal(const Node* node) {
+ Y_ENSURE(node->type == T_Integer);
+ return intVal(node);
+}
+
+double FloatVal(const Node* node) {
+ Y_ENSURE(node->type == T_Float);
+ return floatVal(node);
+}
+
+const char* StrFloatVal(const Node* node) {
+ Y_ENSURE(node->type == T_Float);
+ return strVal(node);
+}
+
+const char* StrVal(const Node* node) {
+ Y_ENSURE(node->type == T_String || node->type == T_BitString);
+ return strVal(node);
+}
+
+bool ValueAsString(const ValUnion& val, bool isNull, TString& ret) {
+ if (isNull) {
+ ret = "NULL";
+ return true;
+ }
+
+ switch (NodeTag(val)) {
+ case T_Boolean: {
+ ret = BoolVal(val) ? "t" : "f";
+ return true;
+ }
+ case T_Integer: {
+ ret = ToString(IntVal(val));
+ return true;
+ }
+ case T_Float: {
+ ret = StrFloatVal(val);
+ return true;
+ }
+ case T_String:
+ case T_BitString: {
+ ret = StrVal(val);
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+
+int ListLength(const List* list) {
+ return list_length(list);
+}
+
+int StrLength(const char* s) {
+ return s ? strlen(s) : 0;
+}
+
+int StrCompare(const char* s1, const char* s2) {
+ return strcmp(s1 ? s1 : "", s2 ? s2 : "");
+}
+
+int StrICompare(const char* s1, const char* s2) {
+ return stricmp(s1 ? s1 : "", s2 ? s2 : "");
+}
+
+std::shared_ptr<List> ListMake1(void* cell) {
+ return std::shared_ptr<List>(list_make1(cell), list_free);
+}
+
+#define CAST_NODE(nodeType, nodeptr) CastNode<nodeType>(nodeptr, T_##nodeType)
+#define CAST_NODE_EXT(nodeType, tag, nodeptr) CastNode<nodeType>(nodeptr, tag)
+#define LIST_CAST_NTH(nodeType, list, index) CAST_NODE(nodeType, list_nth(list, index))
+#define LIST_CAST_EXT_NTH(nodeType, tag, list, index) CAST_NODE_EXT(nodeType, tag, list_nth(list, i))
+
+const Node* ListNodeNth(const List* list, int index) {
+ return static_cast<const Node*>(list_nth(list, index));
+}
+
+const IndexElem* IndexElement(const Node* node) {
+ Y_ENSURE(node->type == T_IndexElem);
+ return ((const IndexElem*)node);
+}
+
+#define AT_LOCATION(node) \
+ TLocationGuard guard(this, node->location);
+
+#define AT_LOCATION_EX(node, field) \
+ TLocationGuard guard(this, node->field);
+
+std::tuple<TStringBuf, TStringBuf> getSchemaAndObjectName(const List* nameList) {
+ switch (ListLength(nameList)) {
+ case 2: {
+ const auto clusterName = StrVal(ListNodeNth(nameList, 0));
+ const auto tableName = StrVal(ListNodeNth(nameList, 1));
+ return {clusterName, tableName};
+ }
+ case 1: {
+ const auto tableName = StrVal(ListNodeNth(nameList, 0));
+ return {"", tableName};
+ }
+ default: {
+ return {"", ""};
+ }
+ }
+}
+
+struct TPgConst {
+ TMaybe<TString> value;
+ enum class Type {
+ boolean,
+ int4,
+ int8,
+ numeric,
+ text,
+ unknown,
+ bit,
+ nil,
+ };
+
+ static TString ToString(const TPgConst::Type& type) {
+ switch (type) {
+ case TPgConst::Type::boolean:
+ return "bool";
+ case TPgConst::Type::int4:
+ return "int4";
+ case TPgConst::Type::int8:
+ return "int8";
+ case TPgConst::Type::numeric:
+ return "numeric";
+ case TPgConst::Type::text:
+ return "text";
+ case TPgConst::Type::unknown:
+ return "unknown";
+ case TPgConst::Type::bit:
+ return "bit";
+ case TPgConst::Type::nil:
+ return "unknown";
+ }
+ }
+
+ Type type;
+};
+
+TMaybe<TPgConst> GetValueNType(const A_Const* value) {
+ TPgConst pgConst;
+ if (value->isnull) {
+ pgConst.type = TPgConst::Type::nil;
+ return pgConst;
+ }
+
+ const auto& val = value->val;
+ switch (NodeTag(val)) {
+ case T_Boolean: {
+ pgConst.value = BoolVal(val) ? "t" : "f";
+ pgConst.type = TPgConst::Type::boolean;
+ return pgConst;
+ }
+ case T_Integer: {
+ pgConst.value = ToString(IntVal(val));
+ pgConst.type = TPgConst::Type::int4;
+ return pgConst;
+ }
+ case T_Float: {
+ auto s = StrFloatVal(val);
+ i64 v;
+ const bool isInt8 = TryFromString<i64>(s, v);
+ pgConst.value = ToString(s);
+ pgConst.type = isInt8 ? TPgConst::Type::int8 : TPgConst::Type::numeric;
+ return pgConst;
+ }
+ case T_String: {
+ pgConst.value = ToString(StrVal(val));
+ pgConst.type = TPgConst::Type::unknown; // to support implicit casts
+ return pgConst;
+ }
+ case T_BitString: {
+ pgConst.value = ToString(StrVal(val));
+ pgConst.type = TPgConst::Type::bit;
+ return pgConst;
+ }
+ default: {
+ return {};
+ }
+ }
+}
+
+class TConverter : public IPGParseEvents {
+ friend class TLocationGuard;
+
+private:
+ class TLocationGuard {
+ private:
+ TConverter* Owner;
+
+ public:
+ TLocationGuard(TConverter* owner, int location)
+ : Owner(owner)
+ {
+ Owner->PushPosition(location);
+ }
+
+ ~TLocationGuard() {
+ Owner->PopPosition();
+ }
+ };
+
+public:
+ struct TFromDesc {
+ TAstNode* Source = nullptr;
+ TString Alias;
+ TVector<TString> ColNames;
+ bool InjectRead = false;
+ };
+
+ struct TReadWriteKeyExprs {
+ TAstNode* SinkOrSource = nullptr;
+ TAstNode* Key = nullptr;
+ };
+
+ struct TExprSettings {
+ bool AllowColumns = false;
+ bool AllowAggregates = false;
+ bool AllowOver = false;
+ bool AllowReturnSet = false;
+ bool AllowSubLinks = false;
+ bool AutoParametrizeEnabled = true;
+ TVector<TAstNode*>* WindowItems = nullptr;
+ TString Scope;
+ };
+
+ struct TView {
+ TString Name;
+ TVector<TString> ColNames;
+ TAstNode* Source = nullptr;
+ };
+
+ using TViews = THashMap<TString, TView>;
+
+ struct TState {
+ TMaybe<TString> ApplicationName;
+ TString CostBasedOptimizer;
+ TVector<TAstNode*> Statements;
+ ui32 ReadIndex = 0;
+ TViews Views;
+ TVector<TViews> CTE;
+ const TView* CurrentRecursiveView = nullptr;
+ TVector<NYql::TPosition> Positions = {NYql::TPosition()};
+ THashMap<TString, TString> ParamNameToPgTypeName;
+ NYql::IAutoParamBuilderPtr AutoParamValues;
+ };
+
+ TConverter(TVector<TAstParseResult>& astParseResults, const NSQLTranslation::TTranslationSettings& settings,
+ const TString& query, TVector<TStmtParseInfo>* stmtParseInfo, bool perStatementResult,
+ TMaybe<ui32> sqlProcArgsCount)
+ : AstParseResults(astParseResults)
+ , Settings(settings)
+ , DqEngineEnabled(Settings.DqDefaultAuto->Allow())
+ , BlockEngineEnabled(Settings.BlockDefaultAuto->Allow())
+ , StmtParseInfo(stmtParseInfo)
+ , PerStatementResult(perStatementResult)
+ , SqlProcArgsCount(sqlProcArgsCount)
+ {
+ Y_ENSURE(settings.Mode == NSQLTranslation::ESqlMode::QUERY || settings.Mode == NSQLTranslation::ESqlMode::LIMITED_VIEW);
+ Y_ENSURE(settings.Mode != NSQLTranslation::ESqlMode::LIMITED_VIEW || !perStatementResult);
+ State.ApplicationName = Settings.ApplicationName;
+ AstParseResults.push_back({});
+ if (StmtParseInfo) {
+ StmtParseInfo->push_back({});
+ }
+ ScanRows(query);
+
+ for (auto& flag : Settings.Flags) {
+ if (flag == "DqEngineEnable") {
+ DqEngineEnabled = true;
+ } else if (flag == "DqEngineForce") {
+ DqEngineForce = true;
+ } else if (flag == "BlockEngineEnable") {
+ BlockEngineEnabled = true;
+ } else if (flag == "BlockEngineForce") {
+ BlockEngineForce = true;
+ } if (flag == "UnorderedResult") {
+ UnorderedResult = true;
+ }
+ }
+
+ if (Settings.PathPrefix) {
+ TablePathPrefix = Settings.PathPrefix + "/";
+ }
+
+ for (const auto& [cluster, provider] : Settings.ClusterMapping) {
+ if (provider != PgProviderName) {
+ Provider = provider;
+ break;
+ }
+ }
+ if (!Provider) {
+ Provider = PgProviderName;
+ }
+ Y_ENSURE(!Provider.empty());
+
+ for (size_t i = 0; i < Settings.PgParameterTypeOids.size(); ++i) {
+ const auto paramName = PREPARED_PARAM_PREFIX + ToString(i + 1);
+ const auto typeOid = Settings.PgParameterTypeOids[i];
+ const auto& typeName =
+ typeOid != UNKNOWNOID ? NPg::LookupType(typeOid).Name : DEFAULT_PARAM_TYPE;
+ State.ParamNameToPgTypeName[paramName] = typeName;
+ }
+
+ }
+
+ void OnResult(const List* raw) {
+ if (!PerStatementResult) {
+ AstParseResults[StatementId].Pool = std::make_unique<TMemoryPool>(4096);
+ AstParseResults[StatementId].Root = ParseResult(raw);
+ AstParseResults[StatementId].PgAutoParamValues = State.AutoParamValues;
+ return;
+ }
+ AstParseResults.resize(ListLength(raw));
+ if (StmtParseInfo) {
+ StmtParseInfo->resize(AstParseResults.size());
+ }
+ for (; StatementId < AstParseResults.size(); ++StatementId) {
+ AstParseResults[StatementId].Pool = std::make_unique<TMemoryPool>(4096);
+ AstParseResults[StatementId].Root = ParseResult(raw, StatementId);
+ AstParseResults[StatementId].PgAutoParamValues = State.AutoParamValues;
+ State = {};
+ }
+ }
+
+ void OnError(const TIssue& issue) {
+ AstParseResults[StatementId].Issues.AddIssue(issue);
+ }
+
+ void PrepareStatements() {
+ auto configSource = L(A("DataSource"), QA(TString(NYql::ConfigProviderName)));
+ State.Statements.push_back(L(A("let"), A("world"), L(A(TString(NYql::ConfigureName)), A("world"), configSource,
+ QA("OrderedColumns"))));
+ }
+
+ TAstNode* ParseResult(const List* raw, const TMaybe<ui32> statementId = Nothing()) {
+ PrepareStatements();
+
+ auto configSource = L(A("DataSource"), QA(TString(NYql::ConfigProviderName)));
+ ui32 blockEnginePgmPos = State.Statements.size();
+ State.Statements.push_back(configSource);
+ ui32 costBasedOptimizerPos = State.Statements.size();
+ State.Statements.push_back(configSource);
+ ui32 dqEnginePgmPos = State.Statements.size();
+ State.Statements.push_back(configSource);
+
+ if (statementId) {
+ if (!ParseRawStmt(LIST_CAST_NTH(RawStmt, raw, *statementId))) {
+ return nullptr;
+ }
+ } else {
+ for (int i = 0; i < ListLength(raw); ++i) {
+ if (!ParseRawStmt(LIST_CAST_NTH(RawStmt, raw, i))) {
+ return nullptr;
+ }
+ }
+ }
+
+ if (!State.Views.empty()) {
+ AddError("Not all views have been dropped");
+ return nullptr;
+ }
+
+ if (Settings.EndOfQueryCommit && Settings.Mode != NSQLTranslation::ESqlMode::LIMITED_VIEW) {
+ State.Statements.push_back(L(A("let"), A("world"), L(A("CommitAll!"),
+ A("world"))));
+ }
+
+ AddVariableDeclarations();
+
+ if (Settings.Mode != NSQLTranslation::ESqlMode::LIMITED_VIEW) {
+ State.Statements.push_back(L(A("return"), A("world")));
+ }
+
+ if (DqEngineEnabled) {
+ State.Statements[dqEnginePgmPos] = L(A("let"), A("world"), L(A(TString(NYql::ConfigureName)), A("world"), configSource,
+ QA("DqEngine"), QA(DqEngineForce ? "force" : "auto")));
+ } else {
+ State.Statements.erase(State.Statements.begin() + dqEnginePgmPos);
+ }
+
+ if (State.CostBasedOptimizer) {
+ State.Statements[costBasedOptimizerPos] = L(A("let"), A("world"), L(A(TString(NYql::ConfigureName)), A("world"), configSource,
+ QA("CostBasedOptimizer"), QA(State.CostBasedOptimizer)));
+ } else {
+ State.Statements.erase(State.Statements.begin() + costBasedOptimizerPos);
+ }
+
+ if (BlockEngineEnabled) {
+ State.Statements[blockEnginePgmPos] = L(A("let"), A("world"), L(A(TString(NYql::ConfigureName)), A("world"), configSource,
+ QA("BlockEngine"), QA(BlockEngineForce ? "force" : "auto")));
+ } else {
+ State.Statements.erase(State.Statements.begin() + blockEnginePgmPos);
+ }
+
+ return FinishStatements();
+ }
+
+ TAstNode* FinishStatements() {
+ return VL(State.Statements.data(), State.Statements.size());
+ }
+
+ [[nodiscard]]
+ bool ParseRawStmt(const RawStmt* value) {
+ AT_LOCATION_EX(value, stmt_location);
+ auto node = value->stmt;
+ if (Settings.Mode == NSQLTranslation::ESqlMode::LIMITED_VIEW) {
+ if (NodeTag(node) != T_SelectStmt && NodeTag(node) != T_VariableSetStmt) {
+ AddError("Unsupported statement in LIMITED_VIEW mode");
+ return false;
+ }
+ }
+ if (StmtParseInfo) {
+ (*StmtParseInfo)[StatementId].CommandTagName = GetCommandName(node);
+ }
+ switch (NodeTag(node)) {
+ case T_SelectStmt:
+ return ParseSelectStmt(CAST_NODE(SelectStmt, node), {.Inner = false}) != nullptr;
+ case T_InsertStmt:
+ return ParseInsertStmt(CAST_NODE(InsertStmt, node)) != nullptr;
+ case T_UpdateStmt:
+ return ParseUpdateStmt(CAST_NODE(UpdateStmt, node)) != nullptr;
+ case T_ViewStmt:
+ return ParseViewStmt(CAST_NODE(ViewStmt, node)) != nullptr;
+ case T_CreateStmt:
+ return ParseCreateStmt(CAST_NODE(CreateStmt, node)) != nullptr;
+ case T_DropStmt:
+ return ParseDropStmt(CAST_NODE(DropStmt, node)) != nullptr;
+ case T_VariableSetStmt:
+ {
+ // YQL-16284
+ const char* node_name = CAST_NODE(VariableSetStmt, node)->name;
+ const char* skip_statements[] = {
+ "extra_float_digits", // jdbc
+ "application_name", // jdbc
+ "statement_timeout", // pg_dump
+ "lock_timeout", // pg_dump
+ "idle_in_transaction_session_timeout", // pg_dump
+ "client_encoding", // pg_dump
+ "standard_conforming_strings", // pg_dump
+ "check_function_bodies", // pg_dump
+ "xmloption", // pg_dump
+ "client_min_messages", // pg_dump
+ "row_security", // pg_dump
+ "escape_string_warning", // zabbix
+ "bytea_output", // zabbix
+ "datestyle", // pgadmin 4
+ "timezone", // mediawiki
+ NULL,
+ };
+
+ for (int i = 0; skip_statements[i] != NULL; i++){
+ const char *skip_name = skip_statements[i];
+ if (stricmp(node_name, skip_name) == 0){
+ return true;
+ }
+ };
+ };
+
+ return ParseVariableSetStmt(CAST_NODE(VariableSetStmt, node)) != nullptr;
+ case T_DeleteStmt:
+ return ParseDeleteStmt(CAST_NODE(DeleteStmt, node)) != nullptr;
+ case T_VariableShowStmt:
+ return ParseVariableShowStmt(CAST_NODE(VariableShowStmt, node)) != nullptr;
+ case T_TransactionStmt:
+ return ParseTransactionStmt(CAST_NODE(TransactionStmt, node));
+ case T_IndexStmt:
+ return ParseIndexStmt(CAST_NODE(IndexStmt, node)) != nullptr;
+ case T_CreateSeqStmt:
+ return ParseCreateSeqStmt(CAST_NODE(CreateSeqStmt, node)) != nullptr;
+ case T_AlterSeqStmt:
+ return ParseAlterSeqStmt(CAST_NODE(AlterSeqStmt, node)) != nullptr;
+ case T_AlterTableStmt:
+ return ParseAlterTableStmt(CAST_NODE(AlterTableStmt, node)) != nullptr;
+ default:
+ NodeNotImplemented(value, node);
+ return false;
+ }
+ }
+
+ [[nodiscard]]
+ bool ExtractPgConstsForAutoParam(List* rawValuesLists, TVector<TPgConst>& pgConsts) {
+ YQL_LOG_CTX_SCOPE(TStringBuf("PgSql Autoparametrize"), __FUNCTION__);
+ Y_ABORT_UNLESS(rawValuesLists);
+ size_t rows = ListLength(rawValuesLists);
+
+ if (rows == 0 || !Settings.AutoParametrizeEnabled || !Settings.AutoParametrizeValuesStmt) {
+ return false;
+ }
+
+ size_t cols = ListLength(CAST_NODE(List, ListNodeNth(rawValuesLists, 0)));
+ pgConsts.reserve(rows * cols);
+
+ for (int rowIdx = 0; rowIdx < ListLength(rawValuesLists); ++rowIdx) {
+ const auto rawRow = CAST_NODE(List, ListNodeNth(rawValuesLists, rowIdx));
+
+ for (int colIdx = 0; colIdx < ListLength(rawRow); ++colIdx) {
+ const auto rawCell = ListNodeNth(rawRow, colIdx);
+ if (NodeTag(rawCell) != T_A_Const) {
+ YQL_CLOG(INFO, Default) << "Auto parametrization of " << NodeTag(rawCell) << " is not supported";
+ return false;
+ }
+ auto pgConst = GetValueNType(CAST_NODE(A_Const, rawCell));
+ if (!pgConst) {
+ return false;
+ }
+ pgConsts.push_back(std::move(pgConst.GetRef()));
+ }
+ }
+ return true;
+ }
+
+ TMaybe<TVector<TPgConst::Type>> InferColumnTypesForValuesStmt(const TVector<TPgConst>& values, size_t cols) {
+ Y_ABORT_UNLESS((values.size() % cols == 0), "wrong amount of columns for auto param values vector");
+ TVector<TMaybe<TPgConst::Type>> maybeColumnTypes(cols);
+
+ for (size_t i = 0; i < values.size(); ++i) {
+ const auto& value = values[i];
+ size_t col = i % cols;
+ auto& columnType = maybeColumnTypes[col];
+
+ if (!columnType || columnType.GetRef() == TPgConst::Type::unknown || columnType.GetRef() == TPgConst::Type::nil) {
+ columnType = value.type;
+ continue;
+ }
+
+ // should we allow compatible types here?
+ if (columnType.GetRef() != value.type && columnType.GetRef() != TPgConst::Type::unknown && columnType.GetRef() != TPgConst::Type::nil) {
+ YQL_CLOG(INFO, Default)
+ << "Failed to auto parametrize: different types: "
+ << TPgConst::ToString(columnType.GetRef()) << " and " << TPgConst::ToString(value.type)
+ << " in col " << col;
+ return {};
+ }
+ }
+
+ TVector<TPgConst::Type> columnTypes;
+ for (auto& maybeColumnType: maybeColumnTypes) {
+ if (maybeColumnType.Empty()) {
+ YQL_CLOG(INFO, Default) << "Failed to auto parametrize: can't infer PgType for column";
+ return {};
+ }
+ columnTypes.emplace_back(maybeColumnType.GetRef());
+ }
+ return columnTypes;
+ }
+
+ TString AddSimpleAutoParam(TPgConst&& valueNType) {
+ if (!State.AutoParamValues) {
+ Y_ENSURE(Settings.AutoParamBuilderFactory);
+ State.AutoParamValues = Settings.AutoParamBuilderFactory->MakeBuilder();
+ }
+
+ auto nextName = TString(AUTO_PARAM_PREFIX) + ToString(State.AutoParamValues->Size());
+ auto& type = State.AutoParamValues->Add(nextName);
+ type.Pg(TPgConst::ToString(valueNType.type));
+ auto& data = type.FinishType();
+ data.Pg(valueNType.value);
+ data.FinishData();
+ return nextName;
+ }
+
+ TString AddValuesAutoParam(TVector<TPgConst>&& values, TVector<TPgConst::Type>&& columnTypes) {
+ if (!State.AutoParamValues) {
+ Y_ENSURE(Settings.AutoParamBuilderFactory);
+ State.AutoParamValues = Settings.AutoParamBuilderFactory->MakeBuilder();
+ }
+
+ auto nextName = TString(AUTO_PARAM_PREFIX) + ToString(State.AutoParamValues->Size());
+ auto& type = State.AutoParamValues->Add(nextName);
+ type.BeginList();
+ type.BeginTuple();
+ for (const auto& t : columnTypes) {
+ type.BeforeItem();
+ type.Pg(TPgConst::ToString(t));
+ type.AfterItem();
+ }
+
+ type.EndTuple();
+ type.EndList();
+ auto& data = type.FinishType();
+ data.BeginList();
+ size_t cols = columnTypes.size();
+ for (size_t idx = 0; idx < values.size(); idx += cols){
+ data.BeforeItem();
+ data.BeginTuple();
+ for (size_t delta = 0; delta < cols; ++delta) {
+ data.BeforeItem();
+ data.Pg(values[idx + delta].value);
+ data.AfterItem();
+ }
+
+ data.EndTuple();
+ data.AfterItem();
+ }
+
+ data.EndList();
+ data.FinishData();
+ return nextName;
+ }
+
+ TAstNode* MakeValuesStmtAutoParam(TVector<TPgConst>&& values, TVector<TPgConst::Type>&& columnTypes) {
+ TVector<TAstNode*> autoParamTupleType;
+ autoParamTupleType.reserve(columnTypes.size());
+ autoParamTupleType.push_back(A("TupleType"));
+ for (const auto& type : columnTypes) {
+ auto pgType = L(A("PgType"), QA(TPgConst::ToString(type)));
+ autoParamTupleType.push_back(pgType);
+ }
+ const auto paramType = L(A("ListType"), VL(autoParamTupleType));
+
+ const auto paramName = AddValuesAutoParam(std::move(values), std::move(columnTypes));
+ State.Statements.push_back(L(A("declare"), A(paramName), paramType));
+
+ YQL_CLOG(INFO, Default) << "Successfully autoparametrized VALUES at" << State.Positions.back();
+
+ return A(paramName);
+ }
+
+ [[nodiscard]]
+ TAstNode* ParseValuesList(List* valuesLists, bool buildCommonType) {
+ TVector<TAstNode*> valNames;
+ uint64 colIdx = 0;
+
+ TExprSettings settings;
+ settings.AllowColumns = false;
+ settings.Scope = "VALUES";
+
+ for (int valueIndex = 0; valueIndex < ListLength(valuesLists); ++valueIndex) {
+ auto node = ListNodeNth(valuesLists, valueIndex);
+ if (NodeTag(node) != T_List) {
+ NodeNotImplemented(node);
+ return nullptr;
+ }
+
+ auto lst = CAST_NODE(List, node);
+ if (valueIndex == 0) {
+ for (int item = 0; item < ListLength(lst); ++item) {
+ valNames.push_back(QA("column" + ToString(colIdx++)));
+ }
+ } else {
+ if (ListLength(lst) != (int)valNames.size()) {
+ AddError("VALUES lists must all be the same length");
+ return nullptr;
+ }
+ }
+ }
+
+ TVector<TPgConst> pgConsts;
+ bool canAutoparametrize = ExtractPgConstsForAutoParam(valuesLists, pgConsts);
+ if (canAutoparametrize) {
+ auto maybeColumnTypes = InferColumnTypesForValuesStmt(pgConsts, valNames.size());
+ if (maybeColumnTypes) {
+ auto valuesNode = MakeValuesStmtAutoParam(std::move(pgConsts), std::move(maybeColumnTypes.GetRef()));
+ return QL(QA("values"), QVL(valNames.data(), valNames.size()), valuesNode);
+ }
+ }
+
+ TVector<TAstNode*> valueRows;
+ valueRows.reserve(ListLength(valuesLists));
+ valueRows.push_back(A(buildCommonType ? "PgValuesList" : "AsList"));
+ for (int valueIndex = 0; valueIndex < ListLength(valuesLists); ++valueIndex) {
+ auto node = ListNodeNth(valuesLists, valueIndex);
+ if (NodeTag(node) != T_List) {
+ NodeNotImplemented(node);
+ return nullptr;
+ }
+
+ auto lst = CAST_NODE(List, node);
+ TVector<TAstNode*> row;
+
+ for (int item = 0; item < ListLength(lst); ++item) {
+ auto cell = ParseExpr(ListNodeNth(lst, item), settings);
+ if (!cell) {
+ return nullptr;
+ }
+
+ row.push_back(cell);
+ }
+
+ valueRows.push_back(QVL(row.data(), row.size()));
+ }
+
+ return QL(QA("values"), QVL(valNames.data(), valNames.size()), VL(valueRows));
+ }
+
+ TAstNode* ParseSetConfig(const FuncCall* value) {
+ auto length = ListLength(value->args);
+ if (length != 3) {
+ AddError(TStringBuilder() << "Expected 3 arguments, but got: " << length);
+ return nullptr;
+ }
+
+ VariableSetStmt config;
+ config.kind = VAR_SET_VALUE;
+ auto arg0 = ListNodeNth(value->args, 0);
+ auto arg1 = ListNodeNth(value->args, 1);
+ auto arg2 = ListNodeNth(value->args, 2);
+ if (NodeTag(arg2) != T_A_Const) {
+ AddError(TStringBuilder() << "Expected AConst node as is_local arg, but got node with tag: " << NodeTag(arg2));
+ return nullptr;
+ }
+ auto isLocalConst = CAST_NODE(A_Const, arg2);
+ if (isLocalConst->isnull) {
+ AddError(TStringBuilder() << "Expected t/f, but got null");
+ return nullptr;
+ }
+ if (NodeTag(isLocalConst->val) != T_Boolean) {
+ AddError(TStringBuilder() << "Expected bool in const, but got something wrong: " << NodeTag(isLocalConst->val));
+ return nullptr;
+ }
+ config.is_local = BoolVal(isLocalConst->val);
+
+ if (NodeTag(arg0) != T_A_Const || NodeTag(arg1) != T_A_Const) {
+ AddError(TStringBuilder() << "Expected const with string, but got something else: " << NodeTag(arg0));
+ return nullptr;
+ }
+
+ if (CAST_NODE(A_Const, arg0)->isnull || CAST_NODE(A_Const, arg1)->isnull) {
+ AddError(TStringBuilder() << "Expected string const as name arg, but got null");
+ return nullptr;
+ }
+
+ auto name = CAST_NODE(A_Const, arg0)->val;
+ auto val = CAST_NODE(A_Const, arg1)->val;
+ if (NodeTag(name) != T_String || NodeTag(val) != T_String) {
+ AddError(TStringBuilder() << "Expected string const as name arg, but got something else: " << NodeTag(name));
+ return nullptr;
+ }
+ config.name = (char*)StrVal(name);
+ config.args = list_make1((void*)arg1);
+ return ParseVariableSetStmt(&config, true);
+ }
+
+ using TTraverseSelectStack = TStack<std::pair<const SelectStmt*, bool>>;
+ using TTraverseNodeStack = TStack<std::pair<const Node*, bool>>;
+
+ struct TSelectStmtSettings {
+ bool Inner = true;
+ mutable TVector<TAstNode*> TargetColumns;
+ bool AllowEmptyResSet = false;
+ bool EmitPgStar = false;
+ bool FillTargetColumns = false;
+ bool UnknownsAllowed = false;
+ const TView* Recursive = nullptr;
+ };
+
+ [[nodiscard]]
+ TAstNode* ParseSelectStmt(
+ const SelectStmt* value,
+ const TSelectStmtSettings& selectSettings
+ ) {
+ if (Settings.Mode == NSQLTranslation::ESqlMode::LIMITED_VIEW) {
+ if (HasSelectInLimitedView) {
+ AddError("Expected exactly one SELECT in LIMITED_VIEW mode");
+ return nullptr;
+ }
+
+ HasSelectInLimitedView = true;
+ }
+
+ bool isValuesClauseOfInsertStmt = selectSettings.FillTargetColumns;
+
+ State.CTE.emplace_back();
+ auto prevRecursiveView = State.CurrentRecursiveView;
+ State.CurrentRecursiveView = selectSettings.Recursive;
+ Y_DEFER {
+ State.CTE.pop_back();
+ State.CurrentRecursiveView = prevRecursiveView;
+ };
+
+ if (value->withClause) {
+ if (!ParseWithClause(CAST_NODE(WithClause, value->withClause))) {
+ return nullptr;
+ }
+ }
+
+ TTraverseSelectStack traverseSelectStack;
+ traverseSelectStack.push({ value, false });
+
+ TVector<const SelectStmt*> setItems;
+ TVector<TAstNode*> setOpsNodes;
+
+ while (!traverseSelectStack.empty()) {
+ auto& top = traverseSelectStack.top();
+ if (top.first->op == SETOP_NONE) {
+ // leaf
+ setItems.push_back(top.first);
+ setOpsNodes.push_back(QA("push"));
+ traverseSelectStack.pop();
+ } else {
+ if (!top.first->larg || !top.first->rarg) {
+ AddError("SelectStmt: expected larg and rarg");
+ return nullptr;
+ }
+
+ if (!top.second) {
+ traverseSelectStack.push({ top.first->rarg, false });
+ traverseSelectStack.push({ top.first->larg, false });
+ top.second = true;
+ } else {
+ TString op;
+ switch (top.first->op) {
+ case SETOP_UNION:
+ op = "union"; break;
+ case SETOP_INTERSECT:
+ op = "intersect"; break;
+ case SETOP_EXCEPT:
+ op = "except"; break;
+ default:
+ AddError(TStringBuilder() << "SetOperation unsupported value: " << (int)top.first->op);
+ return nullptr;
+ }
+
+ if (top.first->all) {
+ op += "_all";
+ }
+
+ setOpsNodes.push_back(QA(op));
+ traverseSelectStack.pop();
+ }
+ }
+ }
+
+ bool hasCombiningQueries = (1 < setItems.size());
+
+ TAstNode* sort = nullptr;
+ if (ListLength(value->sortClause) > 0) {
+ TVector<TAstNode*> sortItems;
+ for (int i = 0; i < ListLength(value->sortClause); ++i) {
+ auto node = ListNodeNth(value->sortClause, i);
+ if (NodeTag(node) != T_SortBy) {
+ NodeNotImplemented(value, node);
+ return nullptr;
+ }
+
+ auto sort = ParseSortBy(CAST_NODE_EXT(PG_SortBy, T_SortBy, node), !hasCombiningQueries, true);
+ if (!sort) {
+ return nullptr;
+ }
+
+ sortItems.push_back(sort);
+ }
+
+ sort = QVL(sortItems.data(), sortItems.size());
+ }
+
+ TVector<TAstNode*> setItemNodes;
+ for (size_t id = 0; id < setItems.size(); ++id) {
+ const auto& x = setItems[id];
+ bool hasDistinctAll = false;
+ TVector<TAstNode*> distinctOnItems;
+ if (x->distinctClause) {
+ if (linitial(x->distinctClause) == NULL) {
+ hasDistinctAll = true;
+ } else {
+ for (int i = 0; i < ListLength(x->distinctClause); ++i) {
+ auto node = ListNodeNth(x->distinctClause, i);
+ TAstNode* expr;
+ if (NodeTag(node) == T_A_Const && (NodeTag(CAST_NODE(A_Const, node)->val) == T_Integer)) {
+ expr = MakeProjectionRef("DISTINCT ON", CAST_NODE(A_Const, node));
+ } else {
+ TExprSettings settings;
+ settings.AllowColumns = true;
+ settings.Scope = "DISTINCT ON";
+ expr = ParseExpr(node, settings);
+ }
+
+ if (!expr) {
+ return nullptr;
+ }
+
+
+ auto lambda = L(A("lambda"), QL(), expr);
+ distinctOnItems.push_back(L(A("PgGroup"), L(A("Void")), lambda));
+ }
+ }
+ }
+
+ if (x->intoClause) {
+ AddError("SelectStmt: not supported intoClause");
+ return nullptr;
+ }
+
+ TVector<TAstNode*> fromList;
+ TVector<TAstNode*> joinOps;
+ for (int i = 0; i < ListLength(x->fromClause); ++i) {
+ auto node = ListNodeNth(x->fromClause, i);
+ if (NodeTag(node) != T_JoinExpr) {
+ auto p = ParseFromClause(node);
+ if (!p) {
+ return nullptr;
+ }
+
+ AddFrom(*p, fromList);
+ joinOps.push_back(QL(QL(QA("push"))));
+ } else {
+ TTraverseNodeStack traverseNodeStack;
+ traverseNodeStack.push({ node, false });
+ TVector<TAstNode*> oneJoinGroup;
+
+ while (!traverseNodeStack.empty()) {
+ auto& top = traverseNodeStack.top();
+ if (NodeTag(top.first) != T_JoinExpr) {
+ // leaf
+ auto p = ParseFromClause(top.first);
+ if (!p) {
+ return nullptr;
+ }
+ AddFrom(*p, fromList);
+ traverseNodeStack.pop();
+ oneJoinGroup.push_back(QL(QA("push")));
+ } else {
+ auto join = CAST_NODE(JoinExpr, top.first);
+ if (!join->larg || !join->rarg) {
+ AddError("JoinExpr: expected larg and rarg");
+ return nullptr;
+ }
+
+ if (join->alias) {
+ AddError("JoinExpr: unsupported alias");
+ return nullptr;
+ }
+
+ if (join->isNatural) {
+ AddError("JoinExpr: unsupported isNatural");
+ return nullptr;
+ }
+
+ if (!top.second) {
+ traverseNodeStack.push({ join->rarg, false });
+ traverseNodeStack.push({ join->larg, false });
+ top.second = true;
+ } else {
+ TString op;
+ switch (join->jointype) {
+ case JOIN_INNER:
+ op = join->quals ? "inner" : "cross"; break;
+ case JOIN_LEFT:
+ op = "left"; break;
+ case JOIN_FULL:
+ op = "full"; break;
+ case JOIN_RIGHT:
+ op = "right"; break;
+ default:
+ AddError(TStringBuilder() << "jointype unsupported value: " << (int)join->jointype);
+ return nullptr;
+ }
+
+ if (ListLength(join->usingClause) > 0) {
+ if (join->join_using_alias) {
+ AddError(TStringBuilder() << "join USING: unsupported AS");
+ return nullptr;
+ }
+ if (op == "cross") {
+ op = "inner";
+ }
+ auto len = ListLength(join->usingClause);
+ TVector<TAstNode*> fields(len);
+ THashSet<TString> present;
+ for (decltype(len) i = 0; i < len; ++i) {
+ auto node = ListNodeNth(join->usingClause, i);
+ if (NodeTag(node) != T_String) {
+ AddError("JoinExpr: unexpected non-string constant");
+ return nullptr;
+ }
+ if (present.contains(StrVal(node))) {
+ AddError(TStringBuilder() << "USING clause: duplicated column " << StrVal(node));
+ return nullptr;
+ }
+ fields[i] = QAX(StrVal(node));
+ }
+ oneJoinGroup.push_back(QL(QA(op), QA("using"), QVL(fields)));
+ } else {
+
+ if (op != "cross" && !join->quals) {
+ AddError("join_expr: expected quals for non-cross join");
+ return nullptr;
+ }
+
+ if (op == "cross") {
+ oneJoinGroup.push_back(QL(QA(op)));
+ } else {
+ TExprSettings settings;
+ settings.AllowColumns = true;
+ settings.Scope = "JOIN ON";
+ auto quals = ParseExpr(join->quals, settings);
+ if (!quals) {
+ return nullptr;
+ }
+
+ auto lambda = L(A("lambda"), QL(), quals);
+ oneJoinGroup.push_back(QL(QA(op), L(A("PgWhere"), L(A("Void")), lambda)));
+ }
+ }
+ traverseNodeStack.pop();
+ }
+ }
+ }
+
+ joinOps.push_back(QVL(oneJoinGroup.data(), oneJoinGroup.size()));
+ }
+ }
+
+ TAstNode* whereFilter = nullptr;
+ if (x->whereClause) {
+ TExprSettings settings;
+ settings.AllowColumns = true;
+ settings.AllowSubLinks = true;
+ settings.Scope = "WHERE";
+ whereFilter = ParseExpr(x->whereClause, settings);
+ if (!whereFilter) {
+ return nullptr;
+ }
+ }
+
+ TAstNode* groupBy = nullptr;
+ if (ListLength(x->groupClause) > 0) {
+ TVector<TAstNode*> groupByItems;
+ for (int i = 0; i < ListLength(x->groupClause); ++i) {
+ auto node = ListNodeNth(x->groupClause, i);
+ TAstNode* expr;
+ if (NodeTag(node) == T_A_Const && (NodeTag(CAST_NODE(A_Const, node)->val) == T_Integer)) {
+ expr = MakeProjectionRef("GROUP BY", CAST_NODE(A_Const, node));
+ } else {
+ TExprSettings settings;
+ settings.AllowColumns = true;
+ settings.Scope = "GROUP BY";
+ if (NodeTag(node) == T_GroupingSet) {
+ expr = ParseGroupingSet(CAST_NODE(GroupingSet, node), settings);
+ } else {
+ expr = ParseExpr(node, settings);
+ }
+ }
+
+ if (!expr) {
+ return nullptr;
+ }
+
+ auto lambda = L(A("lambda"), QL(), expr);
+ groupByItems.push_back(L(A("PgGroup"), L(A("Void")), lambda));
+ }
+
+ groupBy = QVL(groupByItems.data(), groupByItems.size());
+ }
+
+ TAstNode* having = nullptr;
+ if (x->havingClause) {
+ TExprSettings settings;
+ settings.AllowColumns = true;
+ settings.Scope = "HAVING";
+ settings.AllowAggregates = true;
+ settings.AllowSubLinks = true;
+ having = ParseExpr(x->havingClause, settings);
+ if (!having) {
+ return nullptr;
+ }
+ }
+
+ TVector<TAstNode*> windowItems;
+ if (ListLength(x->windowClause) > 0) {
+ for (int i = 0; i < ListLength(x->windowClause); ++i) {
+ auto node = ListNodeNth(x->windowClause, i);
+ if (NodeTag(node) != T_WindowDef) {
+ NodeNotImplemented(x, node);
+ return nullptr;
+ }
+
+ auto win = ParseWindowDef(CAST_NODE(WindowDef, node));
+ if (!win) {
+ return nullptr;
+ }
+
+ windowItems.push_back(win);
+ }
+ }
+
+ if (ListLength(x->valuesLists) && ListLength(x->fromClause)) {
+ AddError("SelectStmt: values_lists isn't compatible to from_clause");
+ return nullptr;
+ }
+
+ if (!selectSettings.AllowEmptyResSet && (ListLength(x->valuesLists) == 0) && (ListLength(x->targetList) == 0)) {
+ AddError("SelectStmt: both values_list and target_list are not allowed to be empty");
+ return nullptr;
+ }
+
+ if (x != value && ListLength(x->sortClause) > 0) {
+ AddError("SelectStmt: sortClause should be used only on top");
+ return nullptr;
+ }
+
+ if (x != value) {
+ if (x->limitOption == LIMIT_OPTION_COUNT || x->limitOption == LIMIT_OPTION_DEFAULT) {
+ if (x->limitCount || x->limitOffset) {
+ AddError("SelectStmt: limit should be used only on top");
+ return nullptr;
+ }
+ } else {
+ AddError(TStringBuilder() << "LimitOption unsupported value: " << (int)x->limitOption);
+ return nullptr;
+ }
+
+ if (ListLength(x->lockingClause) > 0) {
+ AddWarning(TIssuesIds::PG_NO_LOCKING_SUPPORT, "SelectStmt: lockingClause is ignored");
+ }
+ }
+
+ TVector<TAstNode*> res;
+ ui32 i = 0;
+ if (selectSettings.EmitPgStar && id + 1 == setItems.size()) {
+ res.emplace_back(CreatePgStarResultItem());
+ i++;
+ }
+ bool maybeSelectWithJustSetConfig = !selectSettings.Inner && !sort && windowItems.empty() && !having && !groupBy && !whereFilter && !x->distinctClause && ListLength(x->targetList) == 1;
+ if (maybeSelectWithJustSetConfig) {
+ auto node = ListNodeNth(x->targetList, 0);
+ if (NodeTag(node) != T_ResTarget) {
+ NodeNotImplemented(x, node);
+ return nullptr;
+ }
+ auto r = CAST_NODE(ResTarget, node);
+ if (!r->val) {
+ AddError("SelectStmt: expected val");
+ return nullptr;
+ }
+ auto call = r->val;
+ if (NodeTag(call) == T_FuncCall) {
+ auto fn = CAST_NODE(FuncCall, call);
+ if (ListLength(fn->funcname) == 1) {
+ auto nameNode = ListNodeNth(fn->funcname, 0);
+ if (NodeTag(nameNode) != T_String) {
+ AddError("Function name must be string");
+ return nullptr;
+ }
+ auto name = to_lower(TString(StrVal(ListNodeNth(fn->funcname, 0))));
+ if (name == "set_config") {
+ return ParseSetConfig(fn);
+ }
+ }
+ }
+ }
+ for (int targetIndex = 0; targetIndex < ListLength(x->targetList); ++targetIndex) {
+ auto node = ListNodeNth(x->targetList, targetIndex);
+ if (NodeTag(node) != T_ResTarget) {
+ NodeNotImplemented(x, node);
+ return nullptr;
+ }
+
+ auto r = CAST_NODE(ResTarget, node);
+ if (!r->val) {
+ AddError("SelectStmt: expected val");
+ return nullptr;
+ }
+
+ TExprSettings settings;
+ settings.AllowColumns = true;
+ settings.AllowAggregates = true;
+ settings.AllowOver = true;
+ settings.AllowSubLinks = true;
+ settings.WindowItems = &windowItems;
+ settings.Scope = "SELECT";
+ auto x = ParseExpr(r->val, settings);
+ if (!x) {
+ return nullptr;
+ }
+ res.push_back(CreatePgResultItem(r, x, i));
+ }
+
+ TVector<TAstNode*> setItemOptions;
+ if (selectSettings.EmitPgStar) {
+ setItemOptions.push_back(QL(QA("emit_pg_star")));
+ }
+ if (!selectSettings.TargetColumns.empty()) {
+ setItemOptions.push_back(QL(QA("target_columns"), QVL(selectSettings.TargetColumns.data(), selectSettings.TargetColumns.size())));
+ }
+ if (selectSettings.FillTargetColumns) {
+ setItemOptions.push_back(QL(QA("fill_target_columns")));
+ }
+ if (ListLength(x->targetList) > 0) {
+ setItemOptions.push_back(QL(QA("result"), QVL(res.data(), res.size())));
+ } else {
+ auto valuesList = ParseValuesList(x->valuesLists, /*buildCommonType=*/!isValuesClauseOfInsertStmt);
+ if (!valuesList) {
+ return nullptr;
+ }
+ setItemOptions.push_back(valuesList);
+ }
+
+ if (!fromList.empty()) {
+ setItemOptions.push_back(QL(QA("from"), QVL(fromList.data(), fromList.size())));
+ setItemOptions.push_back(QL(QA("join_ops"), QVL(joinOps.data(), joinOps.size())));
+ }
+
+ if (whereFilter) {
+ auto lambda = L(A("lambda"), QL(), whereFilter);
+ setItemOptions.push_back(QL(QA("where"), L(A("PgWhere"), L(A("Void")), lambda)));
+ }
+
+ if (groupBy) {
+ setItemOptions.push_back(QL(QA("group_by"), groupBy));
+ }
+
+ if (windowItems.size()) {
+ auto window = QVL(windowItems.data(), windowItems.size());
+ setItemOptions.push_back(QL(QA("window"), window));
+ }
+
+ if (having) {
+ auto lambda = L(A("lambda"), QL(), having);
+ setItemOptions.push_back(QL(QA("having"), L(A("PgWhere"), L(A("Void")), lambda)));
+ }
+
+ if (hasDistinctAll) {
+ setItemOptions.push_back(QL(QA("distinct_all")));
+ } else if (!distinctOnItems.empty()) {
+ auto distinctOn = QVL(distinctOnItems.data(), distinctOnItems.size());
+ setItemOptions.push_back(QL(QA("distinct_on"), distinctOn));
+ }
+
+ if (!hasCombiningQueries && sort) {
+ setItemOptions.push_back(QL(QA("sort"), sort));
+ }
+
+ if (selectSettings.UnknownsAllowed || hasCombiningQueries) {
+ setItemOptions.push_back(QL(QA("unknowns_allowed")));
+ }
+
+ auto setItem = L(A("PgSetItem"), QVL(setItemOptions.data(), setItemOptions.size()));
+ setItemNodes.push_back(setItem);
+ }
+
+ if (value->intoClause) {
+ AddError("SelectStmt: not supported intoClause");
+ return nullptr;
+ }
+
+ if (ListLength(value->lockingClause) > 0) {
+ AddWarning(TIssuesIds::PG_NO_LOCKING_SUPPORT, "SelectStmt: lockingClause is ignored");
+ }
+
+ TAstNode* limit = nullptr;
+ TAstNode* offset = nullptr;
+ if (value->limitOption == LIMIT_OPTION_COUNT || value->limitOption == LIMIT_OPTION_DEFAULT) {
+ if (value->limitCount) {
+ TExprSettings settings;
+ settings.AllowColumns = false;
+ settings.AllowSubLinks = true;
+ settings.Scope = "LIMIT";
+ limit = ParseExpr(value->limitCount, settings);
+ if (!limit) {
+ return nullptr;
+ }
+ }
+
+ if (value->limitOffset) {
+ TExprSettings settings;
+ settings.AllowColumns = false;
+ settings.AllowSubLinks = true;
+ settings.Scope = "OFFSET";
+ offset = ParseExpr(value->limitOffset, settings);
+ if (!offset) {
+ return nullptr;
+ }
+ }
+ } else {
+ AddError(TStringBuilder() << "LimitOption unsupported value: " << (int)value->limitOption);
+ return nullptr;
+ }
+
+ TVector<TAstNode*> selectOptions;
+
+ selectOptions.push_back(QL(QA("set_items"), QVL(setItemNodes.data(), setItemNodes.size())));
+ selectOptions.push_back(QL(QA("set_ops"), QVL(setOpsNodes.data(), setOpsNodes.size())));
+
+ if (hasCombiningQueries && sort) {
+ selectOptions.push_back(QL(QA("sort"), sort));
+ }
+
+ if (limit) {
+ selectOptions.push_back(QL(QA("limit"), limit));
+ }
+
+ if (offset) {
+ selectOptions.push_back(QL(QA("offset"), offset));
+ }
+
+ auto output = L(A("PgSelect"), QVL(selectOptions.data(), selectOptions.size()));
+
+ if (selectSettings.Inner) {
+ return output;
+ }
+
+ if (Settings.Mode == NSQLTranslation::ESqlMode::LIMITED_VIEW) {
+ State.Statements.push_back(L(A("return"), L(A("Right!"), L(A("Cons!"), A("world"), output))));
+ return State.Statements.back();
+ }
+
+ auto resOptions = BuildResultOptions(!sort);
+ State.Statements.push_back(L(A("let"), A("output"), output));
+ State.Statements.push_back(L(A("let"), A("result_sink"), L(A("DataSink"), QA(TString(NYql::ResultProviderName)))));
+ State.Statements.push_back(L(A("let"), A("world"), L(A("Write!"),
+ A("world"), A("result_sink"), L(A("Key")), A("output"), resOptions)));
+ State.Statements.push_back(L(A("let"), A("world"), L(A("Commit!"),
+ A("world"), A("result_sink"))));
+ return State.Statements.back();
+ }
+
+ TAstNode* BuildResultOptions(bool unordered) {
+ TVector<TAstNode*> options;
+ options.push_back(QL(QA("type")));
+ options.push_back(QL(QA("autoref")));
+ if (unordered && UnorderedResult) {
+ options.push_back(QL(QA("unordered")));
+ }
+
+ return QVL(options.data(), options.size());
+ }
+
+ [[nodiscard]]
+ bool ParseWithClause(const WithClause* value) {
+ AT_LOCATION(value);
+ for (int i = 0; i < ListLength(value->ctes); ++i) {
+ auto object = ListNodeNth(value->ctes, i);
+ if (NodeTag(object) != T_CommonTableExpr) {
+ NodeNotImplemented(value, object);
+ return false;
+ }
+
+ if (!ParseCTE(CAST_NODE(CommonTableExpr, object), value->recursive)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ [[nodiscard]]
+ bool ParseCTE(const CommonTableExpr* value, bool recursive) {
+ AT_LOCATION(value);
+ TView view;
+ view.Name = value->ctename;
+
+ for (int i = 0; i < ListLength(value->aliascolnames); ++i) {
+ auto node = ListNodeNth(value->aliascolnames, i);
+ if (NodeTag(node) != T_String) {
+ NodeNotImplemented(value, node);
+ return false;
+ }
+
+ view.ColNames.push_back(StrVal(node));
+ }
+
+ if (NodeTag(value->ctequery) != T_SelectStmt) {
+ AddError("Expected Select statement as CTE query");
+ return false;
+ }
+
+ view.Source = ParseSelectStmt(CAST_NODE(SelectStmt, value->ctequery), {
+ .Inner = true,
+ .Recursive = recursive ? &view : nullptr
+ });
+
+ if (!view.Source) {
+ return false;
+ }
+
+ auto& currentCTEs = State.CTE.back();
+ if (currentCTEs.find(view.Name) != currentCTEs.end()) {
+ AddError(TStringBuilder() << "CTE already exists: '" << view.Name << "'");
+ return false;
+ }
+
+ currentCTEs[view.Name] = view;
+ return true;
+ }
+
+ [[nodiscard]]
+ TAstNode* AsScalarContext(TAstNode* subquery) {
+ return L(A("SingleMember"), L(A("Head"), L(A("Take"), subquery, L(A("Uint64"), QA("1")))));
+ }
+
+ [[nodiscard]]
+ TAstNode* MakeLambda(TVector<TAstNode*> args, TAstNode* body) {
+ return L(A("lambda"), QVL(args), body);
+ }
+
+ [[nodiscard]]
+ TAstNode* CreatePgStarResultItem() {
+ TAstNode* starLambda = L(A("lambda"), QL(), L(A("PgStar")));
+ return L(A("PgResultItem"), QAX(""), L(A("Void")), starLambda);
+ }
+
+ [[nodiscard]]
+ TAstNode* CreatePgResultItem(const ResTarget* r, TAstNode* x, ui32& columnIndex) {
+ bool isStar = false;
+ if (NodeTag(r->val) == T_ColumnRef) {
+ auto ref = CAST_NODE(ColumnRef, r->val);
+ for (int fieldNo = 0; fieldNo < ListLength(ref->fields); ++fieldNo) {
+ if (NodeTag(ListNodeNth(ref->fields, fieldNo)) == T_A_Star) {
+ isStar = true;
+ break;
+ }
+ }
+ }
+
+ TString name;
+ if (!isStar) {
+ name = r->name;
+ if (name.empty()) {
+ if (NodeTag(r->val) == T_ColumnRef) {
+ auto ref = CAST_NODE(ColumnRef, r->val);
+ auto field = ListNodeNth(ref->fields, ListLength(ref->fields) - 1);
+ if (NodeTag(field) == T_String) {
+ name = StrVal(field);
+ }
+ } else if (NodeTag(r->val) == T_FuncCall) {
+ auto func = CAST_NODE(FuncCall, r->val);
+ if (!ExtractFuncName(func, name, nullptr)) {
+ return nullptr;
+ }
+ }
+ }
+
+ if (name.empty()) {
+ name = "column" + ToString(columnIndex++);
+ }
+ }
+
+ const auto lambda = L(A("lambda"), QL(), x);
+ const auto columnName = QAX(name);
+ return L(A("PgResultItem"), columnName, L(A("Void")), lambda);
+ }
+
+ [[nodiscard]]
+ std::optional<TVector<TAstNode*>> ParseReturningList(const List* returningList) {
+ TVector <TAstNode*> list;
+ if (ListLength(returningList) == 0) {
+ return {};
+ }
+ ui32 index = 0;
+ for (int i = 0; i < ListLength(returningList); i++) {
+ auto node = ListNodeNth(returningList, i);
+ if (NodeTag(node) != T_ResTarget) {
+ NodeNotImplemented(returningList, node);
+ return std::nullopt;
+ }
+ auto r = CAST_NODE(ResTarget, node);
+ if (!r->val) {
+ AddError("SelectStmt: expected value");
+ return std::nullopt;
+ }
+ if (NodeTag(r->val) != T_ColumnRef) {
+ NodeNotImplemented(r, r->val);
+ return std::nullopt;
+ }
+ TExprSettings settings;
+ settings.AllowColumns = true;
+ auto columnRef = ParseColumnRef(CAST_NODE(ColumnRef, r->val), settings);
+ if (!columnRef) {
+ return std::nullopt;
+ }
+ list.emplace_back(CreatePgResultItem(r, columnRef, index));
+ }
+ return list;
+ }
+
+ [[nodiscard]]
+ TAstNode* ParseInsertStmt(const InsertStmt* value) {
+ if (value->onConflictClause) {
+ AddError("InsertStmt: not supported onConflictClause");
+ return nullptr;
+ }
+
+ TVector <TAstNode*> returningList;
+ if (value->returningList) {
+ auto list = ParseReturningList(value->returningList);
+ if (list.has_value()) {
+ returningList = list.value();
+ } else {
+ return nullptr;
+ }
+ }
+
+ if (value->withClause) {
+ AddError("InsertStmt: not supported withClause");
+ return nullptr;
+ }
+
+ const auto [sink, key] = ParseWriteRangeVar(value->relation);
+ if (!sink || !key) {
+ return nullptr;
+ }
+
+ TVector <TAstNode*> targetColumns;
+ if (value->cols) {
+ for (int i = 0; i < ListLength(value->cols); i++) {
+ auto node = ListNodeNth(value->cols, i);
+ if (NodeTag(node) != T_ResTarget) {
+ NodeNotImplemented(value, node);
+ return nullptr;
+ }
+ auto r = CAST_NODE(ResTarget, node);
+ if (!r->name) {
+ AddError("SelectStmt: expected name");
+ return nullptr;
+ }
+ targetColumns.push_back(QA(r->name));
+ }
+ }
+
+ const auto select = (value->selectStmt)
+ ? ParseSelectStmt(
+ CAST_NODE(SelectStmt, value->selectStmt),
+ {
+ .Inner = true,
+ .TargetColumns = targetColumns,
+ .AllowEmptyResSet = false,
+ .EmitPgStar = false,
+ .FillTargetColumns = true,
+ .UnknownsAllowed = true
+ })
+ : L(A("Void"));
+ if (!select) {
+ return nullptr;
+ }
+
+ const auto writeOptions = BuildWriteOptions(value, std::move(returningList));
+
+ State.Statements.push_back(L(
+ A("let"),
+ A("world"),
+ L(
+ A("Write!"),
+ A("world"),
+ sink,
+ key,
+ select,
+ writeOptions
+ )
+ ));
+
+ return State.Statements.back();
+ }
+
+ [[nodiscard]]
+ TAstNode* ParseUpdateStmt(const UpdateStmt* value) {
+ const auto fromClause = value->fromClause ? value->fromClause : ListMake1(value->relation).get();
+ SelectStmt selectStmt {
+ .type = T_SelectStmt,
+ .targetList = value->targetList,
+ .fromClause = fromClause,
+ .whereClause = value->whereClause,
+ .withClause = value->withClause,
+ };
+ const auto select = ParseSelectStmt(
+ &selectStmt,
+ {
+ .Inner = true,
+ .AllowEmptyResSet = true,
+ .EmitPgStar = true,
+ .FillTargetColumns = false,
+ .UnknownsAllowed = true
+ }
+ );
+ if (!select) {
+ return nullptr;
+ }
+
+ const auto [sink, key] = ParseWriteRangeVar(value->relation);
+ if (!sink || !key) {
+ return nullptr;
+ }
+
+ TVector<TAstNode*> returningList;
+ if (value->returningList) {
+ auto list = ParseReturningList(value->returningList);
+ if (list.has_value()) {
+ returningList = list.value();
+ } else {
+ return nullptr;
+ }
+ }
+
+ TVector<TAstNode*> options;
+ options.push_back(QL(QA("pg_update"), A("update_select")));
+ options.push_back(QL(QA("mode"), QA("update")));
+ if (!returningList.empty()) {
+ options.push_back(QL(QA("returning"), QVL(returningList.data(), returningList.size())));
+ }
+ const auto writeUpdate = L(A("block"), QL(
+ L(A("let"), A("update_select"), select),
+ L(A("let"), A("sink"), sink),
+ L(A("let"), A("key"), key),
+ L(A("return"), L(
+ A("Write!"),
+ A("world"),
+ A("sink"),
+ A("key"),
+ L(A("Void")),
+ QVL(options.data(), options.size())))
+ ));
+ State.Statements.push_back(L(
+ A("let"),
+ A("world"),
+ writeUpdate
+ ));
+
+ return State.Statements.back();
+ }
+
+ [[nodiscard]]
+ TAstNode* ParseViewStmt(const ViewStmt* value) {
+ if (ListLength(value->options) > 0) {
+ AddError("Create view: not supported options");
+ return nullptr;
+ }
+
+ TView view;
+ if (StrLength(value->view->catalogname) > 0) {
+ AddError("catalogname is not supported");
+ return nullptr;
+ }
+
+ if (StrLength(value->view->schemaname) > 0) {
+ AddError("schemaname is not supported");
+ return nullptr;
+ }
+
+ if (StrLength(value->view->relname) == 0) {
+ AddError("relname should be specified");
+ return nullptr;
+ }
+
+ view.Name = value->view->relname;
+ if (value->view->alias) {
+ AddError("alias is not supported");
+ return nullptr;
+ }
+
+ if (ListLength(value->aliases) == 0) {
+ AddError("expected at least one target column");
+ return nullptr;
+ }
+
+ for (int i = 0; i < ListLength(value->aliases); ++i) {
+ auto node = ListNodeNth(value->aliases, i);
+ if (NodeTag(node) != T_String) {
+ NodeNotImplemented(value, node);
+ return nullptr;
+ }
+
+ view.ColNames.push_back(StrVal(node));
+ }
+
+ if (value->withCheckOption != NO_CHECK_OPTION) {
+ AddError("Create view: not supported options");
+ return nullptr;
+ }
+
+
+ view.Source = ParseSelectStmt(CAST_NODE(SelectStmt, value->query), { .Inner = true });
+ if (!view.Source) {
+ return nullptr;
+ }
+
+ auto it = State.Views.find(view.Name);
+ if (it != State.Views.end() && !value->replace) {
+ AddError(TStringBuilder() << "View already exists: '" << view.Name << "'");
+ return nullptr;
+ }
+
+ State.Views[view.Name] = view;
+ return State.Statements.back();
+ }
+
+#pragma region CreateTable
+private:
+
+ struct TColumnInfo {
+ TString Name;
+ TString Type;
+ bool Serial = false;
+ bool NotNull = false;
+ TAstNode* Default = nullptr;
+ };
+
+ struct TCreateTableCtx {
+ std::unordered_map<TString, TColumnInfo> ColumnsSet;
+ std::vector<TString> ColumnOrder;
+ std::vector<TAstNode*> PrimaryKey;
+ std::vector<std::vector<TAstNode*>> UniqConstr;
+ bool isTemporary;
+ bool ifNotExists;
+ };
+
+ bool CheckConstraintSupported(const Constraint* pk) {
+ bool isSupported = true;
+
+ if (pk->deferrable) {
+ AddError("DEFERRABLE constraints not supported");
+ isSupported = false;
+ }
+
+ if (pk->initdeferred) {
+ AddError("INITIALLY DEFERRED constraints not supported");
+ isSupported = false;
+ }
+
+ if (0 < ListLength(pk->including)) {
+ AddError("INCLUDING columns not supported");
+ isSupported = false;
+ }
+
+ if (0 < ListLength(pk->options)) {
+ AddError("WITH options not supported");
+ isSupported = false;
+ }
+
+ if (pk->indexname) {
+ AddError("INDEX name not supported");
+ isSupported = false;
+ }
+
+ if (pk->indexspace) {
+ AddError("USING INDEX TABLESPACE not supported");
+ isSupported = false;
+ }
+
+ return isSupported;
+ }
+
+ bool FillPrimaryKeyColumns(TCreateTableCtx& ctx, const Constraint* pk) {
+ if (!CheckConstraintSupported(pk))
+ return false;
+
+ for (int i = 0; i < ListLength(pk->keys); ++i) {
+ auto node = ListNodeNth(pk->keys, i);
+ auto nodeName = StrVal(node);
+
+ auto it = ctx.ColumnsSet.find(nodeName);
+ if (it == ctx.ColumnsSet.end()) {
+ AddError("PK column does not belong to table");
+ return false;
+ }
+ it->second.NotNull = true;
+ ctx.PrimaryKey.push_back(QA(StrVal(node)));
+ }
+
+ Y_ENSURE(0 < ctx.PrimaryKey.size());
+
+ return true;
+ }
+
+ bool FillUniqueConstraint(TCreateTableCtx& ctx, const Constraint* constr) {
+ if (!CheckConstraintSupported(constr))
+ return false;
+
+ const auto length = ListLength(constr->keys);
+ std::vector<TAstNode*> uniq;
+ uniq.reserve(length);
+
+ for (auto i = 0; i < length; ++i) {
+ auto node = ListNodeNth(constr->keys, i);
+ auto nodeName = StrVal(node);
+
+ if (!ctx.ColumnsSet.contains(nodeName)) {
+ AddError("UNIQUE column does not belong to table");
+ return false;
+ }
+ uniq.push_back(QA(nodeName));
+ }
+
+ Y_ENSURE(0 < uniq.size());
+ ctx.UniqConstr.emplace_back(std::move(uniq));
+
+ return true;
+ }
+
+ const TString& FindColumnTypeAlias(const TString& colType, bool& isTypeSerial) {
+ const static std::unordered_map<TString, TString> aliasMap {
+ {"smallserial", "int2"},
+ {"serial2", "int2"},
+ {"serial", "int4"},
+ {"serial4", "int4"},
+ {"bigserial", "int8"},
+ {"serial8", "int8"},
+ };
+ const auto aliasIt = aliasMap.find(to_lower(colType));
+ if (aliasIt == aliasMap.end()) {
+ isTypeSerial = false;
+ return colType;
+ }
+ isTypeSerial = true;
+ return aliasIt->second;
+ }
+
+ bool AddColumn(TCreateTableCtx& ctx, const ColumnDef* node) {
+ TColumnInfo cinfo{.Name = node->colname};
+ if (SystemColumns.contains(to_lower(cinfo.Name))) {
+ AddError(TStringBuilder() << "system column can't be used: " << node->colname);
+ return false;
+ }
+
+ if (node->constraints) {
+ for (int i = 0; i < ListLength(node->constraints); ++i) {
+ auto constraintNode =
+ CAST_NODE(Constraint, ListNodeNth(node->constraints, i));
+
+ switch (constraintNode->contype) {
+ case CONSTR_NOTNULL:
+ cinfo.NotNull = true;
+ break;
+
+ case CONSTR_PRIMARY: {
+ if (!ctx.PrimaryKey.empty()) {
+ AddError("Only a single PK is allowed per table");
+ return false;
+ }
+ cinfo.NotNull = true;
+ ctx.PrimaryKey.push_back(QA(node->colname));
+ } break;
+
+ case CONSTR_UNIQUE: {
+ ctx.UniqConstr.push_back({QA(node->colname)});
+ } break;
+
+ case CONSTR_DEFAULT: {
+ TExprSettings settings;
+ settings.AllowColumns = false;
+ settings.Scope = "DEFAULT";
+ settings.AutoParametrizeEnabled = false;
+ cinfo.Default = ParseExpr(constraintNode->raw_expr, settings);
+ if (!cinfo.Default) {
+ return false;
+ }
+ } break;
+
+ default:
+ AddError("column constraint not supported");
+ return false;
+ }
+ }
+ }
+
+ // for now we pass just the last part of the type name
+ auto colTypeVal = StrVal( ListNodeNth(node->typeName->names,
+ ListLength(node->typeName->names) - 1));
+
+ cinfo.Type = FindColumnTypeAlias(colTypeVal, cinfo.Serial);
+ auto [it, inserted] = ctx.ColumnsSet.emplace(node->colname, cinfo);
+ if (!inserted) {
+ AddError("duplicated column names found");
+ return false;
+ }
+
+ ctx.ColumnOrder.push_back(node->colname);
+ return true;
+ }
+
+ bool AddConstraint(TCreateTableCtx& ctx, const Constraint* node) {
+ switch (node->contype) {
+ case CONSTR_PRIMARY: {
+ if (!ctx.PrimaryKey.empty()) {
+ AddError("Only a single PK is allowed per table");
+ return false;
+ }
+ if (!FillPrimaryKeyColumns(ctx, node)) {
+ return false;
+ }
+ } break;
+
+ case CONSTR_UNIQUE: {
+ if (!FillUniqueConstraint(ctx, node)) {
+ return false;
+ }
+ } break;
+
+ // TODO: support table-level not null constraints like:
+ // CHECK (col1 is not null [OR col2 is not null])
+
+ default:
+ AddError("table constraint not supported");
+ return false;
+ }
+ return true;
+ }
+
+ TAstNode* BuildColumnsOptions(TCreateTableCtx& ctx) {
+ std::vector<TAstNode*> columns;
+
+ for(const auto& name: ctx.ColumnOrder) {
+ auto it = ctx.ColumnsSet.find(name);
+ Y_ENSURE(it != ctx.ColumnsSet.end());
+
+ const auto& cinfo = it->second;
+
+ std::vector<TAstNode*> constraints;
+ if (cinfo.Serial) {
+ constraints.push_back(QL(QA("serial")));
+ }
+
+ if (cinfo.NotNull) {
+ constraints.push_back(QL(QA("not_null")));
+ }
+
+ if (cinfo.Default) {
+ constraints.push_back(QL(QA("default"), cinfo.Default));
+ }
+
+ columns.push_back(QL(QA(cinfo.Name), L(A("PgType"), QA(cinfo.Type)), QL(QA("columnConstraints"), QVL(constraints.data(), constraints.size()))));
+ }
+
+ return QVL(columns.data(), columns.size());
+ }
+
+ TAstNode* BuildCreateTableOptions(TCreateTableCtx& ctx) {
+ std::vector<TAstNode*> options;
+
+ TString mode = (ctx.ifNotExists) ? "create_if_not_exists" : "create";
+ options.push_back(QL(QA("mode"), QA(mode)));
+ options.push_back(QL(QA("columns"), BuildColumnsOptions(ctx)));
+ if (!ctx.PrimaryKey.empty()) {
+ options.push_back(QL(QA("primarykey"), QVL(ctx.PrimaryKey.data(), ctx.PrimaryKey.size())));
+ }
+ for (auto& uniq : ctx.UniqConstr) {
+ auto columns = QVL(uniq.data(), uniq.size());
+ options.push_back(QL(QA("index"), QL(
+ QL(QA("indexName")),
+ QL(QA("indexType"), QA("syncGlobalUnique")),
+ QL(QA("dataColumns"), QL()),
+ QL(QA("indexColumns"), columns))));
+ }
+ if (ctx.isTemporary) {
+ options.push_back(QL(QA("temporary")));
+ }
+ return QVL(options.data(), options.size());
+ }
+
+ TAstNode* BuildWriteOptions(const InsertStmt* value, TVector<TAstNode*> returningList = {}) {
+ std::vector<TAstNode*> options;
+
+ const auto insertMode = (ProviderToInsertModeMap.contains(Provider))
+ ? ProviderToInsertModeMap.at(Provider)
+ : "append";
+ options.push_back(QL(QA("mode"), QA(insertMode)));
+
+ if (!returningList.empty()) {
+ options.push_back(QL(QA("returning"), QVL(returningList.data(), returningList.size())));
+ }
+
+ if (!value->selectStmt) {
+ options.push_back(QL(QA("default_values")));
+ }
+
+ return QVL(options.data(), options.size());
+ }
+
+public:
+ [[nodiscard]]
+ TAstNode* ParseCreateStmt(const CreateStmt* value) {
+ // See also transformCreateStmt() in parse_utilcmd.c
+ if (0 < ListLength(value->inhRelations)) {
+ AddError("table inheritance not supported");
+ return nullptr;
+ }
+
+ if (value->partspec) {
+ AddError("PARTITION BY clause not supported");
+ return nullptr;
+ }
+
+ if (value->partbound) {
+ AddError("FOR VALUES clause not supported");
+ return nullptr;
+ }
+
+ // if we ever support typed tables, check transformOfType() in parse_utilcmd.c
+ if (value->ofTypename) {
+ AddError("typed tables not supported");
+ return nullptr;
+ }
+
+ if (0 < ListLength(value->options)) {
+ AddError("table options not supported");
+ return nullptr;
+ }
+
+ if (value->oncommit != ONCOMMIT_NOOP && value->oncommit != ONCOMMIT_PRESERVE_ROWS) {
+ AddError("ON COMMIT actions not supported");
+ return nullptr;
+ }
+
+ if (value->tablespacename) {
+ AddError("TABLESPACE not supported");
+ return nullptr;
+ }
+
+ if (value->accessMethod) {
+ AddError("USING not supported");
+ return nullptr;
+ }
+
+ TCreateTableCtx ctx {};
+
+ if (value->if_not_exists) {
+ ctx.ifNotExists = true;
+ }
+
+ const auto relPersistence = static_cast<NPg::ERelPersistence>(value->relation->relpersistence);
+ switch (relPersistence) {
+ case NPg::ERelPersistence::Temp:
+ ctx.isTemporary = true;
+ break;
+ case NPg::ERelPersistence::Unlogged:
+ AddError("UNLOGGED tables not supported");
+ return nullptr;
+ break;
+ case NPg::ERelPersistence::Permanent:
+ break;
+ }
+
+ auto [sink, key] = ParseWriteRangeVar(value->relation, true);
+
+ if (!sink || !key) {
+ return nullptr;
+ }
+
+ for (int i = 0; i < ListLength(value->tableElts); ++i) {
+ auto rawNode = ListNodeNth(value->tableElts, i);
+
+ switch (NodeTag(rawNode)) {
+ case T_ColumnDef:
+ if (!AddColumn(ctx, CAST_NODE(ColumnDef, rawNode))) {
+ return nullptr;
+ }
+ break;
+
+ case T_Constraint:
+ if (!AddConstraint(ctx, CAST_NODE(Constraint, rawNode))) {
+ return nullptr;
+ }
+ break;
+
+ default:
+ NodeNotImplemented(value, rawNode);
+ return nullptr;
+ }
+ }
+
+ State.Statements.push_back(
+ L(A("let"), A("world"),
+ L(A("Write!"), A("world"), sink, key, L(A("Void")),
+ BuildCreateTableOptions(ctx))));
+
+ return State.Statements.back();
+ }
+#pragma endregion CreateTable
+
+ [[nodiscard]]
+ TAstNode* ParseDropStmt(const DropStmt* value) {
+ TVector<const List*> nameListNodes;
+ for (int i = 0; i < ListLength(value->objects); ++i) {
+ auto object = ListNodeNth(value->objects, i);
+ if (NodeTag(object) != T_List) {
+ NodeNotImplemented(value, object);
+ return nullptr;
+ }
+ auto nameListNode = CAST_NODE(List, object);
+ nameListNodes.push_back(nameListNode);
+ }
+
+ switch (value->removeType) {
+ case OBJECT_VIEW: {
+ return ParseDropViewStmt(value, nameListNodes);
+ }
+ case OBJECT_TABLE: {
+ return ParseDropTableStmt(value, nameListNodes);
+ }
+ case OBJECT_INDEX: {
+ return ParseDropIndexStmt(value, nameListNodes);
+ }
+ case OBJECT_SEQUENCE: {
+ return ParseDropSequenceStmt(value, nameListNodes);
+ }
+ default: {
+ AddError("Not supported object type for DROP");
+ return nullptr;
+ }
+ }
+ }
+
+ TAstNode* ParseDropViewStmt(const DropStmt* value, const TVector<const List*>& names) {
+ // behavior and concurrent don't matter here
+
+ for (const auto& nameList : names) {
+ if (ListLength(nameList) != 1) {
+ AddError("Expected view name");
+ }
+ const auto nameNode = ListNodeNth(nameList, 0);
+
+ if (NodeTag(nameNode) != T_String) {
+ NodeNotImplemented(value, nameNode);
+ return nullptr;
+ }
+
+ const auto name = StrVal(nameNode);
+ auto it = State.Views.find(name);
+ if (!value->missing_ok && it == State.Views.end()) {
+ AddError(TStringBuilder() << "View not found: '" << name << "'");
+ return nullptr;
+ }
+
+ if (it != State.Views.end()) {
+ State.Views.erase(it);
+ }
+ }
+
+ return State.Statements.back();
+ }
+
+ TAstNode* ParseDropTableStmt(const DropStmt* value, const TVector<const List*>& names) {
+ if (value->behavior == DROP_CASCADE) {
+ AddError("CASCADE is not implemented");
+ return nullptr;
+ }
+
+ for (const auto& nameList : names) {
+ const auto [clusterName, tableName] = getSchemaAndObjectName(nameList);
+ const auto [sink, key] = ParseQualifiedRelationName(
+ /* catalogName */ "",
+ clusterName,
+ tableName,
+ /* isSink */ true,
+ /* isScheme */ true
+ );
+ if (sink == nullptr) {
+ return nullptr;
+ }
+
+ TString mode = (value->missing_ok) ? "drop_if_exists" : "drop";
+ State.Statements.push_back(L(
+ A("let"),
+ A("world"),
+ L(
+ A("Write!"),
+ A("world"),
+ sink,
+ key,
+ L(A("Void")),
+ QL(
+ QL(QA("mode"), QA(mode))
+ )
+ )
+ ));
+ }
+
+ return State.Statements.back();
+ }
+
+ TAstNode* ParseDropIndexStmt(const DropStmt* value, const TVector<const List*>& names) {
+ if (value->behavior == DROP_CASCADE) {
+ AddError("CASCADE is not implemented");
+ return nullptr;
+ }
+
+ if (names.size() != 1) {
+ AddError("DROP INDEX requires exactly one index");
+ return nullptr;
+ }
+
+ for (const auto& nameList : names) {
+ const auto [clusterName, indexName] = getSchemaAndObjectName(nameList);
+ const auto [sink, key] = ParseQualifiedPgObjectName(
+ /* catalogName */ "",
+ clusterName,
+ indexName,
+ "pgIndex"
+ );
+
+ TString missingOk = (value->missing_ok) ? "true" : "false";
+ State.Statements.push_back(L(
+ A("let"),
+ A("world"),
+ L(
+ A("Write!"),
+ A("world"),
+ sink,
+ key,
+ L(A("Void")),
+ QL(
+ QL(QA("mode"), QA("dropIndex")),
+ QL(QA("ifExists"), QA(missingOk))
+ )
+ )
+ ));
+ }
+
+ return State.Statements.back();
+ }
+
+ TAstNode* ParseDropSequenceStmt(const DropStmt* value, const TVector<const List*>& names) {
+ if (value->behavior == DROP_CASCADE) {
+ AddError("CASCADE is not implemented");
+ return nullptr;
+ }
+
+ if (names.size() != 1) {
+ AddError("DROP SEQUENCE requires exactly one sequence");
+ return nullptr;
+ }
+
+ for (const auto& nameList : names) {
+ const auto [clusterName, indexName] = getSchemaAndObjectName(nameList);
+ const auto [sink, key] = ParseQualifiedPgObjectName(
+ /* catalogName */ "",
+ clusterName,
+ indexName,
+ "pgSequence"
+ );
+
+ TString mode = (value->missing_ok) ? "drop_if_exists" : "drop";
+ State.Statements.push_back(L(
+ A("let"),
+ A("world"),
+ L(
+ A("Write!"),
+ A("world"),
+ sink,
+ key,
+ L(A("Void")),
+ QL(
+ QL(QA("mode"), QA(mode))
+ )
+ )
+ ));
+ }
+
+ return State.Statements.back();
+ }
+
+ [[nodiscard]]
+ TAstNode* ParseVariableSetStmt(const VariableSetStmt* value, bool isSetConfig = false) {
+ if (value->kind != VAR_SET_VALUE) {
+ AddError(TStringBuilder() << "VariableSetStmt, not supported kind: " << (int)value->kind);
+ return nullptr;
+ }
+
+ auto name = to_lower(TString(value->name));
+ if (name == "search_path") {
+ THashSet<TString> visitedValues;
+ TVector<TString> values;
+ for (int i = 0; i < ListLength(value->args); ++i) {
+ auto val = ListNodeNth(value->args, i);
+ if (NodeTag(val) != T_A_Const || CAST_NODE(A_Const, val)->isnull || NodeTag(CAST_NODE(A_Const, val)->val) != T_String) {
+ AddError(TStringBuilder() << "VariableSetStmt, expected string literal for " << value->name << " option");
+ return nullptr;
+ }
+ TString rawStr = to_lower(TString(StrVal(CAST_NODE(A_Const, val)->val)));
+ if (visitedValues.emplace(rawStr).second) {
+ values.emplace_back(rawStr);
+ }
+ }
+
+ if (values.size() != 1) {
+ AddError(TStringBuilder() << "VariableSetStmt, expected 1 unique scheme, but got: " << values.size());
+ return nullptr;
+ }
+ auto rawStr = values[0];
+ if (rawStr != "pg_catalog" && rawStr != "public" && rawStr != "" && rawStr != "information_schema") {
+ AddError(TStringBuilder() << "VariableSetStmt, search path supports only 'information_schema', 'public', 'pg_catalog', '' but got: '" << rawStr << "'");
+ return nullptr;
+ }
+ if (Settings.GUCSettings) {
+ Settings.GUCSettings->Set(name, rawStr, value->is_local);
+ if (StmtParseInfo) {
+ (*StmtParseInfo)[StatementId].KeepInCache = false;
+ }
+ }
+ return State.Statements.back();
+ }
+
+ if (isSetConfig) {
+ if (name != "search_path") {
+ AddError(TStringBuilder() << "VariableSetStmt, set_config doesn't support that option:" << name);
+ return nullptr;
+ }
+ }
+
+ if (name == "useblocks" || name == "emitaggapply" || name == "unorderedresult") {
+ if (ListLength(value->args) != 1) {
+ AddError(TStringBuilder() << "VariableSetStmt, expected 1 arg, but got: " << ListLength(value->args));
+ return nullptr;
+ }
+
+ auto arg = ListNodeNth(value->args, 0);
+ if (NodeTag(arg) == T_A_Const && (NodeTag(CAST_NODE(A_Const, arg)->val) == T_String)) {
+ TString rawStr = StrVal(CAST_NODE(A_Const, arg)->val);
+ if (name == "unorderedresult") {
+ UnorderedResult = (rawStr == "true");
+ } else {
+ auto configSource = L(A("DataSource"), QA(TString(NYql::ConfigProviderName)));
+ State.Statements.push_back(L(A("let"), A("world"), L(A(TString(NYql::ConfigureName)), A("world"), configSource,
+ QA(TString(rawStr == "true" ? "" : "Disable") + TString((name == "useblocks") ? "UseBlocks" : "PgEmitAggApply")))));
+ }
+ } else {
+ AddError(TStringBuilder() << "VariableSetStmt, expected string literal for " << value->name << " option");
+ return nullptr;
+ }
+ } else if (name == "dqengine" || name == "blockengine") {
+ if (ListLength(value->args) != 1) {
+ AddError(TStringBuilder() << "VariableSetStmt, expected 1 arg, but got: " << ListLength(value->args));
+ return nullptr;
+ }
+
+ auto arg = ListNodeNth(value->args, 0);
+ if (NodeTag(arg) == T_A_Const && (NodeTag(CAST_NODE(A_Const, arg)->val) == T_String)) {
+ auto rawStr = StrVal(CAST_NODE(A_Const, arg)->val);
+ auto str = to_lower(TString(rawStr));
+ const bool isDqEngine = name == "dqengine";
+ auto& enable = isDqEngine ? DqEngineEnabled : BlockEngineEnabled;
+ auto& force = isDqEngine ? DqEngineForce : BlockEngineForce;
+ if (str == "auto") {
+ enable = true;
+ force = false;
+ } else if (str == "force") {
+ enable = true;
+ force = true;
+ } else if (str == "disable") {
+ enable = false;
+ force = false;
+ } else {
+ AddError(TStringBuilder() << "VariableSetStmt, not supported " << value->name << " option value: " << rawStr);
+ return nullptr;
+ }
+ } else {
+ AddError(TStringBuilder() << "VariableSetStmt, expected string literal for " << value->name << " option");
+ return nullptr;
+ }
+ } else if (name.StartsWith("dq.") || name.StartsWith("yt.") || name.StartsWith("s3.") || name.StartsWith("ydb.")) {
+ if (ListLength(value->args) != 1) {
+ AddError(TStringBuilder() << "VariableSetStmt, expected 1 arg, but got: " << ListLength(value->args));
+ return nullptr;
+ }
+
+ auto arg = ListNodeNth(value->args, 0);
+ if (NodeTag(arg) == T_A_Const && (NodeTag(CAST_NODE(A_Const, arg)->val) == T_String)) {
+ auto dotPos = name.find('.');
+ auto provider = name.substr(0, dotPos);
+ TString providerName;
+ if (name.StartsWith("dq.")) {
+ providerName = NYql::DqProviderName;
+ } else if (name.StartsWith("yt.")) {
+ providerName = NYql::YtProviderName;
+ } else if (name.StartsWith("s3.")) {
+ providerName = NYql::S3ProviderName;
+ } else if (name.StartsWith("ydb.")) {
+ providerName = NYql::YdbProviderName;
+ } else {
+ Y_ASSERT(0);
+ }
+
+ auto providerSource = L(A("DataSource"), QA(providerName), QA("$all"));
+
+ auto rawStr = StrVal(CAST_NODE(A_Const, arg)->val);
+
+ State.Statements.push_back(L(A("let"), A("world"), L(A(TString(NYql::ConfigureName)), A("world"), providerSource,
+ QA("Attr"), QAX(name.substr(dotPos + 1)), QAX(rawStr))));
+ } else {
+ AddError(TStringBuilder() << "VariableSetStmt, expected string literal for " << value->name << " option");
+ return nullptr;
+ }
+ } else if (name == "tablepathprefix") {
+ if (ListLength(value->args) != 1) {
+ AddError(TStringBuilder() << "VariableSetStmt, expected 1 arg, but got: " << ListLength(value->args));
+ return nullptr;
+ }
+
+ auto arg = ListNodeNth(value->args, 0);
+ if (NodeTag(arg) == T_A_Const && (NodeTag(CAST_NODE(A_Const, arg)->val) == T_String)) {
+ auto rawStr = StrVal(CAST_NODE(A_Const, arg)->val);
+ TablePathPrefix = rawStr;
+ } else {
+ AddError(TStringBuilder() << "VariableSetStmt, expected string literal for " << value->name << " option");
+ return nullptr;
+ }
+ } else if (name == "costbasedoptimizer") {
+ if (ListLength(value->args) != 1) {
+ AddError(TStringBuilder() << "VariableSetStmt, expected 1 arg, but got: " << ListLength(value->args));
+ return nullptr;
+ }
+
+ auto arg = ListNodeNth(value->args, 0);
+ if (NodeTag(arg) == T_A_Const && (NodeTag(CAST_NODE(A_Const, arg)->val) == T_String)) {
+ auto rawStr = StrVal(CAST_NODE(A_Const, arg)->val);
+ auto str = to_lower(TString(rawStr));
+ if (!(str == "disable" || str == "pg" || str == "native")) {
+ AddError(TStringBuilder() << "VariableSetStmt, not supported CostBasedOptimizer option value: " << rawStr);
+ return nullptr;
+ }
+
+ State.CostBasedOptimizer = str;
+ } else {
+ AddError(TStringBuilder() << "VariableSetStmt, expected string literal for " << value->name << " option");
+ return nullptr;
+ }
+ } else if (name == "applicationname") {
+ if (ListLength(value->args) != 1) {
+ AddError(TStringBuilder() << "VariableSetStmt, expected 1 arg, but got: " << ListLength(value->args));
+ return nullptr;
+ }
+
+ auto arg = ListNodeNth(value->args, 0);
+ if (NodeTag(arg) == T_A_Const && (NodeTag(CAST_NODE(A_Const, arg)->val) == T_String)) {
+ auto rawStr = StrVal(CAST_NODE(A_Const, arg)->val);
+ State.ApplicationName = rawStr;
+ } else {
+ AddError(TStringBuilder() << "VariableSetStmt, expected string literal for " << value->name << " option");
+ return nullptr;
+ }
+ } else {
+ AddError(TStringBuilder() << "VariableSetStmt, not supported name: " << value->name);
+ return nullptr;
+ }
+
+ return State.Statements.back();
+ }
+
+ [[nodiscard]]
+ TAstNode* ParseDeleteStmt(const DeleteStmt* value) {
+ if (value->usingClause) {
+ AddError("using is not supported");
+ return nullptr;
+ }
+ TVector <TAstNode*> returningList;
+ if (value->returningList) {
+ auto list = ParseReturningList(value->returningList);
+ if (list.has_value()) {
+ returningList = list.value();
+ } else {
+ return nullptr;
+ }
+ }
+ if (value->withClause) {
+ AddError("with is not supported");
+ return nullptr;
+ }
+
+ if (!value->relation) {
+ AddError("DeleteStmt: expected relation");
+ return nullptr;
+ }
+
+ TVector<TAstNode*> fromList;
+ auto p = ParseRangeVar(value->relation);
+ if (!p) {
+ return nullptr;
+ }
+ AddFrom(*p, fromList);
+
+ TAstNode* whereFilter = nullptr;
+ if (value->whereClause) {
+ TExprSettings settings;
+ settings.AllowColumns = true;
+ settings.AllowSubLinks = true;
+ settings.Scope = "WHERE";
+ whereFilter = ParseExpr(value->whereClause, settings);
+ if (!whereFilter) {
+ return nullptr;
+ }
+ }
+
+ TVector<TAstNode*> setItemOptions;
+
+ setItemOptions.push_back(QL(QA("result"), QVL(CreatePgStarResultItem())));
+ setItemOptions.push_back(QL(QA("from"), QVL(fromList.data(), fromList.size())));
+ setItemOptions.push_back(QL(QA("join_ops"), QVL(QL(QL(QA("push"))))));
+
+ NYql::TAstNode* lambda = nullptr;
+ if (whereFilter) {
+ lambda = L(A("lambda"), QL(), whereFilter);
+ setItemOptions.push_back(QL(QA("where"), L(A("PgWhere"), L(A("Void")), lambda)));
+ }
+
+ auto setItemNode = L(A("PgSetItem"), QVL(setItemOptions.data(), setItemOptions.size()));
+
+ TVector<TAstNode*> selectOptions;
+ selectOptions.push_back(QL(QA("set_items"), QVL(setItemNode)));
+ selectOptions.push_back(QL(QA("set_ops"), QVL(QA("push"))));
+
+ auto select = L(A("PgSelect"), QVL(selectOptions.data(), selectOptions.size()));
+
+ auto [sink, key] = ParseWriteRangeVar(value->relation);
+
+ if (!sink || !key) {
+ return nullptr;
+ }
+
+ std::vector<TAstNode*> options;
+ options.push_back(QL(QA("pg_delete"), select));
+ options.push_back(QL(QA("mode"), QA("delete")));
+ if (!returningList.empty()) {
+ options.push_back(QL(QA("returning"), QVL(returningList.data(), returningList.size())));
+ }
+ State.Statements.push_back(L(
+ A("let"),
+ A("world"),
+ L(
+ A("Write!"),
+ A("world"),
+ sink,
+ key,
+ L(A("Void")),
+ QVL(options.data(), options.size())
+ )
+ ));
+ return State.Statements.back();
+ }
+
+ TMaybe<TString> GetConfigVariable(const TString& varName) {
+ if (varName == "server_version") {
+ return GetPostgresServerVersionStr();
+ }
+ if (varName == "server_version_num") {
+ return GetPostgresServerVersionNum();
+ }
+ if (varName == "standard_conforming_strings"){
+ return "on";
+ }
+ if (varName == "search_path"){
+ auto searchPath = Settings.GUCSettings->Get("search_path");
+ return searchPath ? *searchPath : "public";
+ }
+ if (varName == "default_transaction_read_only"){
+ return "off"; // mediawiki
+ }
+ if (varName == "transaction_isolation"){
+ return "serializable";
+ }
+ return {};
+ }
+
+ TMaybe<std::vector<TAstNode*>> ParseIndexElements(List* list) {
+ const auto length = ListLength(list);
+ std::vector<TAstNode*> columns;
+ columns.reserve(length);
+
+ for (auto i = 0; i < length; ++i) {
+ auto node = ListNodeNth(list, i);
+ auto indexElem = IndexElement(node);
+ if (indexElem->expr || indexElem->indexcolname) {
+ AddError("index expression is not supported yet");
+ return {};
+ }
+
+ columns.push_back(QA(indexElem->name));
+ }
+
+ return columns;
+ }
+
+
+ [[nodiscard]]
+ TAstNode* ParseVariableShowStmt(const VariableShowStmt* value) {
+ const auto varName = to_lower(TString(value->name));
+
+ const auto varValue = GetConfigVariable(varName);
+ if (!varValue) {
+ AddError("unrecognized configuration parameter \"" + varName + "\"");
+ return nullptr;
+ }
+
+ const auto columnName = QAX(varName);
+ const auto varValueNode =
+ L(A("PgConst"), QAX(*varValue), L(A("PgType"), QA("text")));
+
+ const auto lambda = L(A("lambda"), QL(), varValueNode);
+ const auto res = QL(L(A("PgResultItem"), columnName, L(A("Void")), lambda));
+
+ const auto setItem = L(A("PgSetItem"), QL(QL(QA("result"), res)));
+ const auto setItems = QL(QA("set_items"), QL(setItem));
+ const auto setOps = QL(QA("set_ops"), QVL(QA("push")));
+ const auto selectOptions = QL(setItems, setOps);
+
+ const auto output = L(A("PgSelect"), selectOptions);
+ State.Statements.push_back(L(A("let"), A("output"), output));
+ State.Statements.push_back(L(A("let"), A("result_sink"), L(A("DataSink"), QA(TString(NYql::ResultProviderName)))));
+
+ const auto resOptions = BuildResultOptions(true);
+ State.Statements.push_back(L(A("let"), A("world"), L(A("Write!"),
+ A("world"), A("result_sink"), L(A("Key")), A("output"), resOptions)));
+ State.Statements.push_back(L(A("let"), A("world"), L(A("Commit!"),
+ A("world"), A("result_sink"))));
+ return State.Statements.back();
+ }
+
+ [[nodiscard]]
+ bool ParseTransactionStmt(const TransactionStmt* value) {
+ switch (value->kind) {
+ case TRANS_STMT_BEGIN:
+ case TRANS_STMT_START:
+ case TRANS_STMT_SAVEPOINT:
+ case TRANS_STMT_RELEASE:
+ case TRANS_STMT_ROLLBACK_TO:
+ return true;
+ case TRANS_STMT_COMMIT:
+ State.Statements.push_back(L(A("let"), A("world"), L(A("CommitAll!"),
+ A("world"))));
+ if (Settings.GUCSettings) {
+ Settings.GUCSettings->Commit();
+ }
+ return true;
+ case TRANS_STMT_ROLLBACK:
+ State.Statements.push_back(L(A("let"), A("world"), L(A("CommitAll!"),
+ A("world"), QL(QL(QA("mode"), QA("rollback"))))));
+ if (Settings.GUCSettings) {
+ Settings.GUCSettings->RollBack();
+ }
+ return true;
+ default:
+ AddError(TStringBuilder() << "TransactionStmt: kind is not supported: " << (int)value->kind);
+ return false;
+ }
+ }
+
+ [[nodiscard]]
+ TAstNode* ParseIndexStmt(const IndexStmt* value) {
+ if (value->unique) {
+ AddError("unique index creation is not supported yet");
+ return nullptr;
+ }
+
+ if (value->primary) {
+ AddError("primary key creation is not supported yet");
+ return nullptr;
+ }
+
+ if (value->isconstraint || value->deferrable || value->initdeferred) {
+ AddError("constraint modification is not supported yet");
+ return nullptr;
+ }
+
+ if (value->whereClause) {
+ AddError("partial index is not supported yet");
+ return nullptr;
+ }
+
+ if (value->options) {
+ AddError("storage parameters for index is not supported yet");
+ return nullptr;
+ }
+
+ auto columns = ParseIndexElements(value->indexParams);
+ if (!columns)
+ return nullptr;
+
+ auto coverColumns = ParseIndexElements(value->indexIncludingParams);
+ if (!coverColumns)
+ return nullptr;
+
+ const auto [sink, key] = ParseWriteRangeVar(value->relation, true);
+ if (!sink || !key) {
+ return nullptr;
+ }
+
+ std::vector<TAstNode*> flags;
+ flags.emplace_back(QA("pg"));
+ if (value->if_not_exists) {
+ flags.emplace_back(QA("ifNotExists"));
+ }
+
+ std::vector<TAstNode*> desc;
+ auto indexNameAtom = QA("indexName");
+ if (value->idxname) {
+ desc.emplace_back(QL(indexNameAtom, QA(value->idxname)));
+ } else {
+ desc.emplace_back(QL(indexNameAtom));
+ }
+ desc.emplace_back(QL(QA("indexType"), QA(value->unique ? "syncGlobalUnique" : "syncGlobal")));
+ desc.emplace_back(QL(QA("indexColumns"), QVL(columns->data(), columns->size())));
+ desc.emplace_back(QL(QA("dataColumns"), QVL(coverColumns->data(), coverColumns->size())));
+ desc.emplace_back(QL(QA("flags"), QVL(flags.data(), flags.size())));
+
+ State.Statements.push_back(L(
+ A("let"),
+ A("world"),
+ L(
+ A("Write!"),
+ A("world"),
+ sink,
+ key,
+ L(A("Void")),
+ QL(
+ QL(QA("mode"), QA("alter")),
+ QL(QA("actions"), QL(QL(QA("addIndex"), QVL(desc.data(), desc.size()))))
+ )
+ )
+ ));
+
+ return State.Statements.back();
+ }
+
+ [[nodiscard]]
+ TAstNode* ParseCreateSeqStmt(const CreateSeqStmt* value) {
+
+ std::vector<TAstNode*> options;
+
+ TString mode = (value->if_not_exists) ? "create_if_not_exists" : "create";
+ options.push_back(QL(QA("mode"), QA(mode)));
+
+ auto [sink, key] = ParseQualifiedPgObjectName(
+ value->sequence->catalogname,
+ value->sequence->schemaname,
+ value->sequence->relname,
+ "pgSequence"
+ );
+
+ if (!sink || !key) {
+ return nullptr;
+ }
+
+ const auto relPersistence = static_cast<NPg::ERelPersistence>(value->sequence->relpersistence);
+ switch (relPersistence) {
+ case NPg::ERelPersistence::Temp:
+ options.push_back(QL(QA("temporary")));
+ break;
+ case NPg::ERelPersistence::Unlogged:
+ AddError("UNLOGGED sequence not supported");
+ return nullptr;
+ break;
+ case NPg::ERelPersistence::Permanent:
+ break;
+ }
+
+ for (int i = 0; i < ListLength(value->options); ++i) {
+ auto rawNode = ListNodeNth(value->options, i);
+
+ switch (NodeTag(rawNode)) {
+ case T_DefElem: {
+ const auto* defElem = CAST_NODE(DefElem, rawNode);
+ TString nameElem = defElem->defname;
+ if (defElem->arg) {
+ switch (NodeTag(defElem->arg))
+ {
+ case T_Boolean:
+ options.emplace_back(QL(QAX(nameElem), QA(ToString(boolVal(defElem->arg)))));
+ break;
+ case T_Integer:
+ options.emplace_back(QL(QAX(nameElem), QA(ToString(intVal(defElem->arg)))));
+ break;
+ case T_Float:
+ options.emplace_back(QL(QAX(nameElem), QA(strVal(defElem->arg))));
+ break;
+ case T_TypeName: {
+ const auto* typeName = CAST_NODE_EXT(PG_TypeName, T_TypeName, defElem->arg);
+ if (ListLength(typeName->names) > 0) {
+ options.emplace_back(QL(QAX(nameElem),
+ QAX(StrVal(ListNodeNth(typeName->names, ListLength(typeName->names) - 1)))));
+ }
+ break;
+ }
+ default:
+ NodeNotImplemented(defElem->arg);
+ return nullptr;
+ }
+ }
+ break;
+ }
+ default:
+ NodeNotImplemented(rawNode);
+ return nullptr;
+ }
+ }
+
+ if (value->for_identity) {
+ options.push_back(QL(QA("for_identity")));
+ }
+
+ if (value->ownerId != InvalidOid) {
+ options.push_back(QL(QA("owner_id"), QA(ToString(value->ownerId))));
+ }
+
+ State.Statements.push_back(
+ L(A("let"), A("world"),
+ L(A("Write!"), A("world"), sink, key, L(A("Void")),
+ QVL(options.data(), options.size()))));
+
+ return State.Statements.back();
+ }
+
+ [[nodiscard]]
+ TAstNode* ParseAlterSeqStmt(const AlterSeqStmt* value) {
+
+ std::vector<TAstNode*> options;
+ TString mode = (value->missing_ok) ? "alter_if_exists" : "alter";
+
+ options.push_back(QL(QA("mode"), QA(mode)));
+
+ auto [sink, key] = ParseQualifiedPgObjectName(
+ value->sequence->catalogname,
+ value->sequence->schemaname,
+ value->sequence->relname,
+ "pgSequence"
+ );
+
+ if (!sink || !key) {
+ return nullptr;
+ }
+
+ for (int i = 0; i < ListLength(value->options); ++i) {
+ auto rawNode = ListNodeNth(value->options, i);
+ switch (NodeTag(rawNode)) {
+ case T_DefElem: {
+ const auto* defElem = CAST_NODE(DefElem, rawNode);
+ TString nameElem = defElem->defname;
+ if (defElem->arg) {
+ switch (NodeTag(defElem->arg))
+ {
+ case T_Boolean:
+ options.emplace_back(QL(QAX(nameElem), QA(ToString(boolVal(defElem->arg)))));
+ break;
+ case T_Integer:
+ options.emplace_back(QL(QAX(nameElem), QA(ToString(intVal(defElem->arg)))));
+ break;
+ case T_Float:
+ options.emplace_back(QL(QAX(nameElem), QA(strVal(defElem->arg))));
+ break;
+ case T_TypeName: {
+ const auto* typeName = CAST_NODE_EXT(PG_TypeName, T_TypeName, defElem->arg);
+ if (ListLength(typeName->names) > 0) {
+ options.emplace_back(QL(QAX(nameElem),
+ QAX(StrVal(ListNodeNth(typeName->names, ListLength(typeName->names) - 1)))));
+ }
+ break;
+ }
+ default:
+ NodeNotImplemented(defElem->arg);
+ return nullptr;
+ }
+ }
+ break;
+ }
+ default:
+ NodeNotImplemented(rawNode);
+ return nullptr;
+ }
+ }
+
+ if (value->for_identity) {
+ options.push_back(QL(QA("for_identity")));
+ }
+
+ State.Statements.push_back(
+ L(A("let"), A("world"),
+ L(A("Write!"), A("world"), sink, key, L(A("Void")),
+ QVL(options.data(), options.size()))));
+
+ return State.Statements.back();
+ }
+
+ [[nodiscard]]
+ TAstNode* ParseAlterTableStmt(const AlterTableStmt* value) {
+ std::vector<TAstNode*> options;
+ TString mode = (value->missing_ok) ? "alter_if_exists" : "alter";
+
+ options.push_back(QL(QA("mode"), QA(mode)));
+
+ const auto [sink, key] = ParseWriteRangeVar(value->relation, true);
+ if (!sink || !key) {
+ return nullptr;
+ }
+
+ std::vector<TAstNode*> alterColumns;
+ for (int i = 0; i < ListLength(value->cmds); ++i) {
+ auto rawNode = ListNodeNth(value->cmds, i);
+
+ const auto* cmd = CAST_NODE(AlterTableCmd, rawNode);
+ switch (cmd->subtype) {
+ case AT_ColumnDefault: { /* ALTER COLUMN DEFAULT */
+ const auto* def = cmd->def;
+ const auto* colName = cmd->name;
+ if (def == nullptr) {
+ alterColumns.push_back(QL(QAX(colName), QL(QA("setDefault"), QL(QA("Null")))));
+ break;
+ }
+ switch (NodeTag(def)) {
+ case T_FuncCall: {
+ const auto* newDefault = CAST_NODE(FuncCall, def);
+ const auto* funcName = ListNodeNth(newDefault->funcname, 0);
+ if (NodeTag(funcName) != T_String) {
+ NodeNotImplemented(newDefault, funcName);
+ return nullptr;
+ }
+ auto strFuncName = StrVal(funcName);
+ if (strcmp(strFuncName, "nextval") != 0) {
+ NodeNotImplemented(newDefault, funcName);
+ return nullptr;
+ }
+ const auto* rawArg = ListNodeNth(newDefault->args, 0);
+ if (NodeTag(rawArg) != T_TypeCast && NodeTag(rawArg) != T_A_Const) {
+ AddError(TStringBuilder() << "Expected type cast node or a_const, but got something wrong: " << NodeTag(rawArg));
+ return nullptr;
+ }
+ const A_Const* localConst = nullptr;
+ if (NodeTag(rawArg) == T_TypeCast) {
+ auto localCast = CAST_NODE(TypeCast, rawArg)->arg;
+ if (NodeTag(localCast) != T_A_Const) {
+ AddError(TStringBuilder() << "Expected a_const in cast, but got something wrong: " << NodeTag(localCast));
+ return nullptr;
+ }
+ localConst = CAST_NODE(A_Const, localCast);
+ } else {
+ localConst = CAST_NODE(A_Const, rawArg);
+ }
+ if (NodeTag(localConst->val) != T_String) {
+ AddError(TStringBuilder() << "Expected string in const, but got something wrong: " << NodeTag(localConst->val));
+ return nullptr;
+ }
+ auto seqName = StrVal(localConst->val);
+ TVector<TString> seqNameList;
+ Split(seqName, ".", seqNameList);
+ if (seqNameList.size() != 2 && seqNameList.size() != 1) {
+ AddError(TStringBuilder() << "Expected list size is 1 or 2, but there are " << seqNameList.size());
+ return nullptr;
+ }
+ alterColumns.push_back(QL(QAX(colName), QL(QA("setDefault"), QL(QA("nextval"), QA(seqNameList.back())))));
+ break;
+ }
+ default:
+ NodeNotImplemented(def);
+ return nullptr;
+ }
+ break;
+ }
+ default:
+ NodeNotImplemented(rawNode);
+ return nullptr;
+ }
+ }
+
+ std::vector<TAstNode*> actions { QL(QA("alterColumns"), QVL(alterColumns.data(), alterColumns.size())) };
+
+ options.push_back(
+ QL(QA("actions"),
+ QVL(actions.data(), actions.size())
+ )
+ );
+
+ State.Statements.push_back(
+ L(A("let"), A("world"),
+ L(A("Write!"), A("world"), sink, key, L(A("Void")),
+ QVL(options.data(), options.size()))));
+
+ return State.Statements.back();
+ }
+
+ TMaybe<TFromDesc> ParseFromClause(const Node* node) {
+ switch (NodeTag(node)) {
+ case T_RangeVar:
+ return ParseRangeVar(CAST_NODE(RangeVar, node));
+ case T_RangeSubselect:
+ return ParseRangeSubselect(CAST_NODE(RangeSubselect, node));
+ case T_RangeFunction:
+ return ParseRangeFunction(CAST_NODE(RangeFunction, node));
+ default:
+ NodeNotImplementedImpl<SelectStmt>(node);
+ return {};
+ }
+ }
+
+ void AddFrom(const TFromDesc& p, TVector<TAstNode*>& fromList) {
+ auto aliasNode = QAX(p.Alias);
+ TVector<TAstNode*> colNamesNodes;
+ for (const auto& c : p.ColNames) {
+ colNamesNodes.push_back(QAX(c));
+ }
+
+ auto colNamesTuple = QVL(colNamesNodes.data(), colNamesNodes.size());
+ if (p.InjectRead) {
+ auto label = "read" + ToString(State.ReadIndex);
+ State.Statements.push_back(L(A("let"), A(label), p.Source));
+ State.Statements.push_back(L(A("let"), A("world"), L(A("Left!"), A(label))));
+ fromList.push_back(QL(L(A("Right!"), A(label)), aliasNode, colNamesTuple));
+ ++State.ReadIndex;
+ } else {
+ auto source = p.Source;
+ if (!source) {
+ source = L(A("PgSelf"));
+ }
+
+ fromList.push_back(QL(source, aliasNode, colNamesTuple));
+ }
+ }
+
+ bool ParseAlias(const Alias* alias, TString& res, TVector<TString>& colnames) {
+ for (int i = 0; i < ListLength(alias->colnames); ++i) {
+ auto node = ListNodeNth(alias->colnames, i);
+ if (NodeTag(node) != T_String) {
+ NodeNotImplemented(alias, node);
+ return false;
+ }
+
+ colnames.push_back(StrVal(node));
+ }
+
+ res = alias->aliasname;
+ return true;
+ }
+
+ TString ResolveCluster(const TStringBuf schemaname, TString name) {
+ if (NYql::NPg::GetStaticColumns().contains(NPg::TTableInfoKey{"pg_catalog", name})) {
+ return "pg_catalog";
+ }
+
+ if (schemaname == "public") {
+ return Settings.DefaultCluster;;
+ }
+ if (schemaname == "" && Settings.GUCSettings) {
+ auto search_path = Settings.GUCSettings->Get("search_path");
+ if (!search_path || *search_path == "public" || search_path->empty()) {
+ return Settings.DefaultCluster;
+ }
+ return TString(*search_path);
+ }
+ return TString(schemaname);
+ }
+
+ TAstNode* BuildClusterSinkOrSourceExpression(
+ bool isSink, const TStringBuf schemaname) {
+ TString usedCluster(schemaname);
+ auto p = Settings.ClusterMapping.FindPtr(usedCluster);
+ if (!p) {
+ usedCluster = to_lower(usedCluster);
+ p = Settings.ClusterMapping.FindPtr(usedCluster);
+ }
+
+ if (!p) {
+ AddError(TStringBuilder() << "Unknown cluster: " << schemaname);
+ return nullptr;
+ }
+
+ return L(isSink ? A("DataSink") : A("DataSource"), QAX(*p), QAX(usedCluster));
+ }
+
+ TAstNode* BuildTableKeyExpression(const TStringBuf relname,
+ const TStringBuf cluster, bool isScheme = false
+ ) {
+ auto lowerCluster = to_lower(TString(cluster));
+ bool noPrefix = (lowerCluster == "pg_catalog" || lowerCluster == "information_schema");
+ TString tableName = noPrefix ? to_lower(TString(relname)) : TablePathPrefix + relname;
+ return L(A("Key"), QL(QA(isScheme ? "tablescheme" : "table"),
+ L(A("String"), QAX(std::move(tableName)))));
+ }
+
+ TReadWriteKeyExprs ParseQualifiedRelationName(const TStringBuf catalogname,
+ const TStringBuf schemaname,
+ const TStringBuf relname,
+ bool isSink, bool isScheme) {
+ if (!catalogname.empty()) {
+ AddError("catalogname is not supported");
+ return {};
+ }
+ if (relname.empty()) {
+ AddError("relname should be specified");
+ return {};
+ }
+
+ const auto cluster = ResolveCluster(schemaname, TString(relname));
+ const auto sinkOrSource = BuildClusterSinkOrSourceExpression(isSink, cluster);
+ const auto key = BuildTableKeyExpression(relname, cluster, isScheme);
+ return {sinkOrSource, key};
+ }
+
+
+ TAstNode* BuildPgObjectExpression(const TStringBuf objectName, const TStringBuf objectType) {
+ bool noPrefix = (objectType == "pgIndex");
+ TString name = noPrefix ? TString(objectName) : TablePathPrefix + TString(objectName);
+ return L(A("Key"), QL(QA("pgObject"),
+ L(A("String"), QAX(std::move(name))),
+ L(A("String"), QA(objectType))
+ ));
+ }
+
+ TReadWriteKeyExprs ParseQualifiedPgObjectName(const TStringBuf catalogname,
+ const TStringBuf schemaname,
+ const TStringBuf objectName,
+ const TStringBuf pgObjectType) {
+ if (!catalogname.empty()) {
+ AddError("catalogname is not supported");
+ return {};
+ }
+ if (objectName.empty()) {
+ AddError("objectName should be specified");
+ return {};
+ }
+
+ const auto cluster = ResolveCluster(schemaname, TString(objectName));
+ const auto sinkOrSource = BuildClusterSinkOrSourceExpression(true, cluster);
+ const auto key = BuildPgObjectExpression(objectName, pgObjectType);
+ return {sinkOrSource, key};
+ }
+
+ TReadWriteKeyExprs ParseWriteRangeVar(const RangeVar *value,
+ bool isScheme = false) {
+ if (value->alias) {
+ AddError("alias is not supported");
+ return {};
+ }
+
+ return ParseQualifiedRelationName(value->catalogname, value->schemaname,
+ value->relname,
+ /* isSink */ true, isScheme);
+ }
+
+ TMaybe<TFromDesc> ParseRangeVar(const RangeVar* value) {
+ AT_LOCATION(value);
+
+ const TView* view = nullptr;
+ if (StrLength(value->schemaname) == 0) {
+ for (auto rit = State.CTE.rbegin(); rit != State.CTE.rend(); ++rit) {
+ auto cteIt = rit->find(value->relname);
+ if (cteIt != rit->end()) {
+ view = &cteIt->second;
+ break;
+ }
+ }
+ if (!view && State.CurrentRecursiveView && State.CurrentRecursiveView->Name == value->relname) {
+ view = State.CurrentRecursiveView;
+ }
+
+ if (!view) {
+ auto viewIt = State.Views.find(value->relname);
+ if (viewIt != State.Views.end()) {
+ view = &viewIt->second;
+ }
+ }
+ }
+
+ TString alias;
+ TVector<TString> colnames;
+ if (value->alias) {
+ if (!ParseAlias(value->alias, alias, colnames)) {
+ return {};
+ }
+ } else {
+ alias = value->relname;
+ }
+
+ if (view) {
+ return TFromDesc{view->Source, alias, colnames.empty() ? view->ColNames : colnames, false };
+ }
+
+ TString schemaname = value->schemaname;
+ if (!StrCompare(value->schemaname, "bindings")) {
+ bool isBinding = false;
+ switch (Settings.BindingsMode) {
+ case NSQLTranslation::EBindingsMode::DISABLED:
+ AddError("Please remove 'bindings.' from your query, the support for this syntax has ended");
+ return {};
+ case NSQLTranslation::EBindingsMode::ENABLED:
+ isBinding = true;
+ break;
+ case NSQLTranslation::EBindingsMode::DROP_WITH_WARNING:
+ AddWarning(TIssuesIds::YQL_DEPRECATED_BINDINGS, "Please remove 'bindings.' from your query, the support for this syntax will be dropped soon");
+ [[fallthrough]];
+ case NSQLTranslation::EBindingsMode::DROP:
+ schemaname = Settings.DefaultCluster;
+ break;
+ }
+
+ if (isBinding) {
+ auto s = BuildBindingSource(value);
+ if (!s) {
+ return {};
+ }
+ return TFromDesc{ s, alias, colnames, true };
+ }
+ }
+
+
+ const auto [source, key] = ParseQualifiedRelationName(
+ value->catalogname, schemaname, value->relname,
+ /* isSink */ false,
+ /* isScheme */ false);
+ if (source == nullptr || key == nullptr) {
+ return {};
+ }
+ const auto readExpr = this->SqlProcArgsCount ?
+ L(A("Cons!"),
+ A("world"),
+ L(
+ A("PgTableContent"),
+ QA("pg_catalog"),
+ QAX(value->relname),
+ L(A("Void")),
+ QL()
+ )
+ ) :
+ L(
+ A("Read!"),
+ A("world"),
+ source,
+ key,
+ L(A("Void")),
+ QL()
+ );
+ return TFromDesc {
+ readExpr,
+ alias,
+ colnames,
+ /* injectRead */ true,
+ };
+ }
+
+ TAstNode* BuildBindingSource(const RangeVar* value) {
+ if (StrLength(value->relname) == 0) {
+ AddError("relname should be specified");
+ }
+
+ const TString binding = value->relname;
+ NSQLTranslation::TBindingInfo bindingInfo;
+ if (const auto& error = ExtractBindingInfo(Settings, binding, bindingInfo)) {
+ AddError(error);
+ return nullptr;
+ }
+ TVector<TAstNode*> hints;
+ if (bindingInfo.Schema) {
+ auto schema = QA(bindingInfo.Schema);
+
+ auto type = L(A("SqlTypeFromYson"), schema);
+ auto columns = L(A("SqlColumnOrderFromYson"), schema);
+ hints.emplace_back(QL(QA("userschema"), type, columns));
+ }
+
+ for (auto& [key, value] : bindingInfo.Attributes) {
+ TVector<TAstNode*> hintValues;
+ hintValues.push_back(QA(NormalizeName(key)));
+ for (auto& v : value) {
+ hintValues.push_back(QA(v));
+ }
+ hints.emplace_back(QVL(hintValues.data(), hintValues.size()));
+ }
+
+ auto source = L(A("DataSource"), QAX(bindingInfo.ClusterType), QAX(bindingInfo.Cluster));
+ return L(
+ A("Read!"),
+ A("world"),
+ source,
+ L(
+ A("MrTableConcat"),
+ L(
+ A("Key"),
+ QL(
+ QA("table"),
+ L(
+ A("String"),
+ QAX(bindingInfo.Path)
+ )
+ )
+ )
+ ),
+ L(A("Void")),
+ QVL(hints.data(), hints.size())
+ );
+ }
+
+ TMaybe<TFromDesc> ParseRangeFunction(const RangeFunction* value) {
+ if (value->lateral) {
+ AddError("RangeFunction: unsupported lateral");
+ return {};
+ }
+
+ if (value->ordinality) {
+ AddError("RangeFunction: unsupported ordinality");
+ return {};
+ }
+
+ if (value->is_rowsfrom) {
+ AddError("RangeFunction: unsupported is_rowsfrom");
+ return {};
+ }
+
+ if (ListLength(value->coldeflist) > 0) {
+ AddError("RangeFunction: unsupported coldeflist");
+ return {};
+ }
+
+ if (ListLength(value->functions) != 1) {
+ AddError("RangeFunction: only one function is supported");
+ return {};
+ }
+
+ TString alias;
+ TVector<TString> colnames;
+ if (value->alias) {
+ if (!ParseAlias(value->alias, alias, colnames)) {
+ return {};
+ }
+ }
+
+ auto funcNode = ListNodeNth(value->functions, 0);
+ if (NodeTag(funcNode) != T_List) {
+ AddError("RangeFunction: expected pair");
+ return {};
+ }
+
+ auto lst = CAST_NODE(List, funcNode);
+ if (ListLength(lst) != 2) {
+ AddError("RangeFunction: expected pair");
+ return {};
+ }
+
+ TExprSettings settings;
+ settings.AllowColumns = false;
+ settings.AllowReturnSet = true;
+ settings.Scope = "RANGE FUNCTION";
+ auto node = ListNodeNth(lst, 0);
+ if (NodeTag(node) != T_FuncCall) {
+ AddError("RangeFunction: extected FuncCall");
+ return {};
+ }
+
+ bool injectRead = false;
+ auto func = ParseFuncCall(CAST_NODE(FuncCall, node), settings, true, injectRead);
+ if (!func) {
+ return {};
+ }
+
+ return TFromDesc{ func, alias, colnames, injectRead };
+ }
+
+ TMaybe<TFromDesc> ParseRangeSubselect(const RangeSubselect* value) {
+ if (value->lateral) {
+ AddError("RangeSubselect: unsupported lateral");
+ return {};
+ }
+
+ if (!value->alias) {
+ AddError("RangeSubselect: expected alias");
+ return {};
+ }
+
+ TString alias;
+ TVector<TString> colnames;
+ if (!ParseAlias(value->alias, alias, colnames)) {
+ return {};
+ }
+
+ if (!value->subquery) {
+ AddError("RangeSubselect: expected subquery");
+ return {};
+ }
+
+ if (NodeTag(value->subquery) != T_SelectStmt) {
+ NodeNotImplemented(value, value->subquery);
+ return {};
+ }
+
+ return TFromDesc{ ParseSelectStmt(CAST_NODE(SelectStmt, value->subquery), { .Inner = true }), alias, colnames, false };
+ }
+
+ TAstNode* ParseNullTestExpr(const NullTest* value, const TExprSettings& settings) {
+ AT_LOCATION(value);
+ if (value->argisrow) {
+ AddError("NullTest: unsupported argisrow");
+ return nullptr;
+ }
+ auto arg = ParseExpr(Expr2Node(value->arg), settings);
+ if (!arg) {
+ return nullptr;
+ }
+ auto result = L(A("Exists"), arg);
+ if (value->nulltesttype == IS_NULL) {
+ result = L(A("Not"), result);
+ }
+ return L(A("ToPg"), result);
+ }
+
+ struct TCaseBranch {
+ TAstNode* Pred;
+ TAstNode* Value;
+ };
+
+ TCaseBranch ReduceCaseBranches(std::vector<TCaseBranch>::const_iterator begin, std::vector<TCaseBranch>::const_iterator end) {
+ Y_ENSURE(begin < end);
+ const size_t branchCount = end - begin;
+ if (branchCount == 1) {
+ return *begin;
+ }
+
+ auto mid = begin + branchCount / 2;
+ auto left = ReduceCaseBranches(begin, mid);
+ auto right = ReduceCaseBranches(mid, end);
+
+ TVector<TAstNode*> preds;
+ preds.reserve(branchCount + 1);
+ preds.push_back(A("Or"));
+ for (auto it = begin; it != end; ++it) {
+ preds.push_back(it->Pred);
+ }
+
+ TCaseBranch result;
+ result.Pred = VL(&preds[0], preds.size());
+ result.Value = L(A("If"), left.Pred, left.Value, right.Value);
+ return result;
+
+ }
+
+ TAstNode* ParseCaseExpr(const CaseExpr* value, const TExprSettings& settings) {
+ AT_LOCATION(value);
+ TAstNode* testExpr = nullptr;
+ if (value->arg) {
+ testExpr = ParseExpr(Expr2Node(value->arg), settings);
+ if (!testExpr) {
+ return nullptr;
+ }
+ }
+ std::vector<TCaseBranch> branches;
+ for (int i = 0; i < ListLength(value->args); ++i) {
+ auto node = ListNodeNth(value->args, i);
+ auto whenNode = CAST_NODE(CaseWhen, node);
+ auto whenExpr = ParseExpr(Expr2Node(whenNode->expr), settings);
+ if (!whenExpr) {
+ return nullptr;
+ }
+ if (testExpr) {
+ whenExpr = L(A("PgOp"), QA("="), testExpr, whenExpr);
+ }
+
+ whenExpr = L(A("Coalesce"),
+ L(A("FromPg"), whenExpr),
+ L(A("Bool"), QA("false"))
+ );
+
+ auto whenResult = ParseExpr(Expr2Node(whenNode->result), settings);
+ if (!whenResult) {
+ return nullptr;
+ }
+ branches.emplace_back(TCaseBranch{ .Pred = whenExpr,.Value = whenResult });
+ }
+ TAstNode* defaultResult = nullptr;
+ if (value->defresult) {
+ defaultResult = ParseExpr(Expr2Node(value->defresult), settings);
+ if (!defaultResult) {
+ return nullptr;
+ }
+ } else {
+ defaultResult = L(A("Null"));
+ }
+ auto final = ReduceCaseBranches(branches.begin(), branches.end());
+ return L(A("If"), final.Pred, final.Value, defaultResult);
+ }
+
+ TAstNode* ParseParamRefExpr(const ParamRef* value) {
+ if (SqlProcArgsCount && (value->number < 1 || (ui32)value->number > *SqlProcArgsCount)) {
+ AddError(TStringBuilder() << "Unexpected parameter number: " << value->number);
+ return nullptr;
+ }
+
+ const auto varName = PREPARED_PARAM_PREFIX + ToString(value->number);
+ if (!State.ParamNameToPgTypeName.contains(varName)) {
+ State.ParamNameToPgTypeName[varName] = DEFAULT_PARAM_TYPE;
+ }
+ return A(varName);
+ }
+
+ TAstNode* ParseReturnStmt(const ReturnStmt* value) {
+ TExprSettings settings;
+ settings.AllowColumns = false;
+ settings.Scope = "RETURN";
+ auto expr = ParseExpr(value->returnval, settings);
+ if (!expr) {
+ return nullptr;
+ }
+
+ State.Statements.push_back(L(A("return"), expr));
+ return State.Statements.back();
+ }
+
+ TAstNode* ParseSQLValueFunction(const SQLValueFunction* value) {
+ AT_LOCATION(value);
+ switch (value->op) {
+ case SVFOP_CURRENT_DATE:
+ return L(A("PgCast"),
+ L(A("PgCall"), QA("now"), QL()),
+ L(A("PgType"), QA("date"))
+ );
+ case SVFOP_CURRENT_TIME:
+ return L(A("PgCast"),
+ L(A("PgCall"), QA("now"), QL()),
+ L(A("PgType"), QA("timetz"))
+ );
+ case SVFOP_CURRENT_TIME_N:
+ return L(A("PgCast"),
+ L(A("PgCall"), QA("now"), QL()),
+ L(A("PgType"), QA("timetz")),
+ L(A("PgConst"), QA(ToString(value->typmod)), L(A("PgType"), QA("int4")))
+ );
+ case SVFOP_CURRENT_TIMESTAMP:
+ return L(A("PgCall"), QA("now"), QL());
+ case SVFOP_CURRENT_TIMESTAMP_N:
+ return L(A("PgCast"),
+ L(A("PgCall"), QA("now"), QL()),
+ L(A("PgType"), QA("timestamptz")),
+ L(A("PgConst"), QA(ToString(value->typmod)), L(A("PgType"), QA("int4")))
+ );
+ case SVFOP_CURRENT_USER:
+ case SVFOP_CURRENT_ROLE:
+ case SVFOP_USER: {
+ auto user = Settings.GUCSettings->Get("ydb_user");
+ return L(A("PgConst"), user ? QAX(TString(*user)) : QA("postgres"), L(A("PgType"), QA("name")));
+ }
+ case SVFOP_CURRENT_CATALOG: {
+ std::optional<TString> database;
+ if (Settings.GUCSettings) {
+ database = Settings.GUCSettings->Get("ydb_database");
+ }
+
+ return L(A("PgConst"), QA(database ? *database : "postgres"), L(A("PgType"), QA("name")));
+ }
+ case SVFOP_CURRENT_SCHEMA:
+ return GetCurrentSchema();
+ default:
+ AddError(TStringBuilder() << "Usupported SQLValueFunction: " << (int)value->op);
+ return nullptr;
+ }
+ }
+
+ TAstNode* GetCurrentSchema() {
+ std::optional<TString> searchPath;
+ if (Settings.GUCSettings) {
+ searchPath = Settings.GUCSettings->Get("search_path");
+ }
+
+ return L(A("PgConst"), QA(searchPath ? *searchPath : "public"), L(A("PgType"), QA("name")));
+ }
+
+ TAstNode* ParseBooleanTest(const BooleanTest* value, const TExprSettings& settings) {
+ AT_LOCATION(value);
+
+ auto arg = ParseExpr(Expr2Node(value->arg), settings);
+ if (!arg) {
+ return nullptr;
+ }
+
+ TString op;
+ bool isNot = false;
+
+ switch (value->booltesttype) {
+ case IS_TRUE: {
+ op = "PgIsTrue";
+ break;
+ }
+ case IS_NOT_TRUE: {
+ op = "PgIsTrue";
+ isNot = true;
+ break;
+ }
+
+ case IS_FALSE: {
+ op = "PgIsFalse";
+ break;
+ }
+
+ case IS_NOT_FALSE: {
+ op = "PgIsFalse";
+ isNot = true;
+ break;
+ }
+
+ case IS_UNKNOWN: {
+ op = "PgIsUnknown";
+ break;
+ }
+
+ case IS_NOT_UNKNOWN: {
+ op = "PgIsUnknown";
+ isNot = true;
+ break;
+ }
+
+ default: {
+ TStringBuilder b;
+ b << "Unsupported booltesttype " << static_cast<int>(value->booltesttype);
+ AddError(b);
+ return nullptr;
+ }
+ }
+ auto result = L(A(op), arg);
+ if (isNot) {
+ result = L(A("PgNot"), result);
+ }
+ return result;
+ }
+
+ TAstNode* ParseExpr(const Node* node, const TExprSettings& settings) {
+ switch (NodeTag(node)) {
+ case T_A_Const: {
+ return ParseAConst(CAST_NODE(A_Const, node), settings);
+ }
+ case T_A_Expr: {
+ return ParseAExpr(CAST_NODE(A_Expr, node), settings);
+ }
+ case T_CaseExpr: {
+ return ParseCaseExpr(CAST_NODE(CaseExpr, node), settings);
+ }
+ case T_ColumnRef: {
+ return ParseColumnRef(CAST_NODE(ColumnRef, node), settings);
+ }
+ case T_TypeCast: {
+ return ParseTypeCast(CAST_NODE(TypeCast, node), settings);
+ }
+ case T_BoolExpr: {
+ return ParseBoolExpr(CAST_NODE(BoolExpr, node), settings);
+ }
+ case T_NullTest: {
+ return ParseNullTestExpr(CAST_NODE(NullTest, node), settings);
+ }
+ case T_FuncCall: {
+ bool injectRead;
+ return ParseFuncCall(CAST_NODE(FuncCall, node), settings, false, injectRead);
+ }
+ case T_A_ArrayExpr: {
+ return ParseAArrayExpr(CAST_NODE(A_ArrayExpr, node), settings);
+ }
+ case T_SubLink: {
+ return ParseSubLinkExpr(CAST_NODE(SubLink, node), settings);
+ }
+ case T_CoalesceExpr: {
+ return ParseCoalesceExpr(CAST_NODE(CoalesceExpr, node), settings);
+ }
+ case T_GroupingFunc: {
+ return ParseGroupingFunc(CAST_NODE(GroupingFunc, node));
+ }
+ case T_ParamRef: {
+ return ParseParamRefExpr(CAST_NODE(ParamRef, node));
+ }
+ case T_SQLValueFunction: {
+ return ParseSQLValueFunction(CAST_NODE(SQLValueFunction, node));
+ }
+ case T_BooleanTest: {
+ return ParseBooleanTest(CAST_NODE(BooleanTest, node), settings);
+ }
+ default:
+ NodeNotImplemented(node);
+ return nullptr;
+ }
+ }
+
+ TAstNode* AutoParametrizeConst(TPgConst&& valueNType, TAstNode* pgType) {
+ const auto& paramName = AddSimpleAutoParam(std::move(valueNType));
+ State.Statements.push_back(L(A("declare"), A(paramName), pgType));
+
+ YQL_CLOG(INFO, Default) << "Autoparametrized " << paramName << " at " << State.Positions.back();
+
+ return A(paramName);
+ }
+
+ TAstNode* ParseAConst(const A_Const* value, const TExprSettings& settings) {
+ AT_LOCATION(value);
+ const auto& val = value->val;
+ auto valueNType = GetValueNType(value);
+ if (!valueNType) {
+ return nullptr;
+ }
+
+ TAstNode* pgTypeNode = !value->isnull
+ ? L(A("PgType"), QA(TPgConst::ToString(valueNType->type)))
+ : L(A("PgType"), QA("unknown"));
+
+ if (Settings.AutoParametrizeEnabled && settings.AutoParametrizeEnabled) {
+ return AutoParametrizeConst(std::move(valueNType.GetRef()), pgTypeNode);
+ }
+
+ if (value->isnull) {
+ return L(A("PgCast"), L(A("Null")), pgTypeNode);
+ }
+
+ switch (NodeTag(val)) {
+ case T_Integer:
+ case T_Float: {
+ return L(A("PgConst"), QA(valueNType->value.GetRef()), pgTypeNode);
+ }
+ case T_Boolean:
+ case T_String:
+ case T_BitString: {
+ return L(A("PgConst"), QAX(valueNType->value.GetRef()), pgTypeNode);
+ }
+ default: {
+ NodeNotImplemented((const Node*)value);
+ return nullptr;
+ }
+ }
+ }
+
+ TAstNode* ParseAArrayExpr(const A_ArrayExpr* value, const TExprSettings& settings) {
+ AT_LOCATION(value);
+ TVector<TAstNode*> args;
+ args.push_back(A("PgArray"));
+ for (int i = 0; i < ListLength(value->elements); ++i) {
+ auto elem = ParseExpr(ListNodeNth(value->elements, i), settings);
+ if (!elem) {
+ return nullptr;
+ }
+
+ args.push_back(elem);
+ }
+
+ return VL(args.data(), args.size());
+ }
+
+ TAstNode* ParseCoalesceExpr(const CoalesceExpr* value, const TExprSettings& settings) {
+ AT_LOCATION(value);
+ TVector<TAstNode*> args;
+ args.push_back(A("Coalesce"));
+ for (int i = 0; i < ListLength(value->args); ++i) {
+ auto elem = ParseExpr(ListNodeNth(value->args, i), settings);
+ if (!elem) {
+ return nullptr;
+ }
+
+ args.push_back(elem);
+ }
+
+ return VL(args.data(), args.size());
+ }
+
+ TAstNode* ParseGroupingFunc(const GroupingFunc* value) {
+ AT_LOCATION(value);
+ TVector<TAstNode*> args;
+ args.push_back(A("PgGrouping"));
+ TExprSettings settings;
+ settings.Scope = "GROUPING";
+ settings.AllowColumns = true;
+ for (int i = 0; i < ListLength(value->args); ++i) {
+ auto elem = ParseExpr(ListNodeNth(value->args, i), settings);
+ if (!elem) {
+ return nullptr;
+ }
+
+ args.push_back(elem);
+ }
+
+ return VL(args.data(), args.size());
+ }
+
+ TAstNode* ParseGroupingSet(const GroupingSet* value, const TExprSettings& settings) {
+ AT_LOCATION(value);
+ TString mode;
+ switch (value->kind) {
+ case GROUPING_SET_ROLLUP:
+ mode = "rollup";
+ break;
+ case GROUPING_SET_CUBE:
+ mode = "cube";
+ break;
+ case GROUPING_SET_SETS:
+ mode = "sets";
+ break;
+ default:
+ AddError(TStringBuilder() << "Unexpected grouping set kind: " << (int)value->kind);
+ return nullptr;
+ }
+
+ auto innerSettings = settings;
+ innerSettings.Scope = to_title(mode);
+
+ TVector<TAstNode*> args;
+ args.push_back(A("PgGroupingSet"));
+ args.push_back(QA(mode));
+ if (value->kind == GROUPING_SET_SETS) {
+ // tuple for each set
+ for (int i = 0; i < ListLength(value->content); ++i) {
+ auto child = ListNodeNth(value->content, i);
+ if (NodeTag(child) == T_GroupingSet) {
+ auto kind = CAST_NODE(GroupingSet, child)->kind;
+ if (kind != GROUPING_SET_EMPTY) {
+ AddError(TStringBuilder() << "Unexpected inner grouping set kind: " << (int)kind);
+ return nullptr;
+ }
+
+ args.push_back(QL());
+ continue;
+ }
+
+ if (NodeTag(child) == T_RowExpr) {
+ auto row = CAST_NODE(RowExpr, child);
+ TVector<TAstNode*> tupleItems;
+ for (int j = 0; j < ListLength(row->args); ++j) {
+ auto elem = ParseExpr(ListNodeNth(row->args, j), innerSettings);
+ if (!elem) {
+ return nullptr;
+ }
+
+ tupleItems.push_back(elem);
+ }
+
+ args.push_back(QVL(tupleItems.data(), tupleItems.size()));
+ continue;
+ }
+
+ auto elem = ParseExpr(ListNodeNth(value->content, i), innerSettings);
+ if (!elem) {
+ return nullptr;
+ }
+
+ args.push_back(QL(elem));
+ }
+ } else {
+ // one tuple
+ TVector<TAstNode*> tupleItems;
+ for (int i = 0; i < ListLength(value->content); ++i) {
+ auto elem = ParseExpr(ListNodeNth(value->content, i), innerSettings);
+ if (!elem) {
+ return nullptr;
+ }
+
+ tupleItems.push_back(elem);
+ }
+
+ args.push_back(QVL(tupleItems.data(), tupleItems.size()));
+ }
+
+ return VL(args.data(), args.size());
+ }
+
+
+ TAstNode* ParseSubLinkExpr(const SubLink* value, const TExprSettings& settings) {
+ AT_LOCATION(value);
+ if (!settings.AllowSubLinks) {
+ AddError(TStringBuilder() << "SubLinks are not allowed in: " << settings.Scope);
+ return nullptr;
+ }
+
+ TString linkType;
+ TString operName;
+ switch (value->subLinkType) {
+ case EXISTS_SUBLINK:
+ linkType = "exists";
+ break;
+ case ALL_SUBLINK:
+ linkType = "all";
+ operName = "=";
+ break;
+ case ANY_SUBLINK:
+ linkType = "any";
+ operName = "=";
+ break;
+ case EXPR_SUBLINK:
+ linkType = "expr";
+ break;
+ case ARRAY_SUBLINK:
+ linkType = "array";
+ break;
+ default:
+ AddError(TStringBuilder() << "SublinkExpr: unsupported link type: " << (int)value->subLinkType);
+ return nullptr;
+ }
+
+ if (ListLength(value->operName) > 1) {
+ AddError("SubLink: unsuppoted opername");
+ return nullptr;
+ } else if (ListLength(value->operName) == 1) {
+ auto nameNode = ListNodeNth(value->operName, 0);
+ if (NodeTag(nameNode) != T_String) {
+ NodeNotImplemented(value, nameNode);
+ return nullptr;
+ }
+
+ operName = StrVal(nameNode);
+ }
+
+ TAstNode* rowTest;
+ if (value->testexpr) {
+ TExprSettings localSettings = settings;
+ localSettings.Scope = "SUBLINK TEST";
+ auto test = ParseExpr(value->testexpr, localSettings);
+ if (!test) {
+ return nullptr;
+ }
+
+ rowTest = L(A("lambda"), QL(A("value")), L(A("PgOp"), QAX(operName), test, A("value")));
+ } else {
+ rowTest = L(A("Void"));
+ }
+
+ auto select = ParseSelectStmt(CAST_NODE(SelectStmt, value->subselect), {.Inner = true});
+ if (!select) {
+ return nullptr;
+ }
+
+ return L(A("PgSubLink"), QA(linkType), L(A("Void")), L(A("Void")), rowTest, L(A("lambda"), QL(), select));
+ }
+
+ TAstNode* ParseTableRangeFunction(const TString& name, const TString& schema, List* args) {
+ auto source = BuildClusterSinkOrSourceExpression(false, schema);
+ if (!source) {
+ return nullptr;
+ }
+
+ TVector<TString> argStrs;
+ for (int i = 0; i < ListLength(args); ++i) {
+ auto arg = ListNodeNth(args, i);
+ if (NodeTag(arg) == T_A_Const && (NodeTag(CAST_NODE(A_Const, arg)->val) == T_String)) {
+ TString rawStr = StrVal(CAST_NODE(A_Const, arg)->val);
+ argStrs.push_back(rawStr);
+ } else {
+ AddError("Expected String argument for table function");
+ return nullptr;
+ }
+ }
+
+ if (argStrs.empty()) {
+ AddError("Expected at least one argument for table function");
+ return nullptr;
+ }
+
+ TAstNode* key;
+ auto lowerName = to_lower(name);
+ auto options = QL();
+ if (lowerName == "concat") {
+ TVector<TAstNode*> concatArgs;
+ concatArgs.push_back(A("MrTableConcat"));
+ for (const auto& s : argStrs) {
+ concatArgs.push_back(L(A("Key"), QL(QA("table"),L(A("String"), QAX(s)))));
+ }
+
+ key = VL(concatArgs);
+ } else if (lowerName == "concat_view") {
+ if (argStrs.size() % 2 != 0) {
+ AddError("Expected sequence of pairs of table and view for concat_view");
+ return nullptr;
+ }
+
+ TVector<TAstNode*> concatArgs;
+ concatArgs.push_back(A("MrTableConcat"));
+ for (ui32 i = 0; i < argStrs.size(); i += 2) {
+ concatArgs.push_back(L(A("Key"),
+ QL(QA("table"),L(A("String"), QAX(argStrs[i]))),
+ QL(QA("view"),L(A("String"), QAX(argStrs[i + 1])))));
+ }
+
+ key = VL(concatArgs);
+ } else if (lowerName == "range") {
+ if (argStrs.size() > 5) {
+ AddError("Too many arguments");
+ return nullptr;
+ }
+
+ options = QL(QL(QA("ignorenonexisting")));
+ TAstNode* expr;
+ if (argStrs.size() == 1) {
+ expr = L(A("Bool"),QA("true"));
+ } else if (argStrs.size() == 2) {
+ expr = L(A(">="),A("item"),L(A("String"),QAX(argStrs[1])));
+ } else {
+ expr = L(A("And"),
+ L(A(">="),A("item"),L(A("String"),QAX(argStrs[1]))),
+ L(A("<="),A("item"),L(A("String"),QAX(argStrs[2])))
+ );
+ }
+
+ auto lambda = L(A("lambda"), QL(A("item")), expr);
+ auto range = L(A("MrTableRange"), QAX(argStrs[0]), lambda, QAX(argStrs.size() < 4 ? "" : argStrs[3]));
+ if (argStrs.size() < 5) {
+ key = L(A("Key"), QL(QA("table"),range));
+ } else {
+ key = L(A("Key"), QL(QA("table"),range), QL(QA("view"),L(A("String"), QAX(argStrs[4]))));
+ }
+ } else if (lowerName == "regexp" || lowerName == "like") {
+ if (argStrs.size() < 2 || argStrs.size() > 4) {
+ AddError("Expected from 2 to 4 arguments");
+ return nullptr;
+ }
+
+ options = QL(QL(QA("ignorenonexisting")));
+ TAstNode* expr;
+ if (lowerName == "regexp") {
+ expr = L(A("Apply"),L(A("Udf"),QA("Re2.Grep"),
+ QL(L(A("String"),QAX(argStrs[1])),L(A("Null")))),
+ A("item"));
+ } else {
+ expr = L(A("Apply"),L(A("Udf"),QA("Re2.Match"),
+ QL(L(A("Apply"),
+ L(A("Udf"), QA("Re2.PatternFromLike")),
+ L(A("String"),QAX(argStrs[1]))),L(A("Null")))),
+ A("item"));
+ }
+
+ auto lambda = L(A("lambda"), QL(A("item")), expr);
+ auto range = L(A("MrTableRange"), QAX(argStrs[0]), lambda, QAX(argStrs.size() < 3 ? "" : argStrs[2]));
+ if (argStrs.size() < 4) {
+ key = L(A("Key"), QL(QA("table"),range));
+ } else {
+ key = L(A("Key"), QL(QA("table"),range), QL(QA("view"),L(A("String"), QAX(argStrs[3]))));
+ }
+ } else {
+ AddError(TStringBuilder() << "Unknown table function: " << name);
+ return nullptr;
+ }
+
+ return L(
+ A("Read!"),
+ A("world"),
+ source,
+ key,
+ L(A("Void")),
+ options
+ );
+ }
+
+ TAstNode* ParseFuncCall(const FuncCall* value, const TExprSettings& settings, bool rangeFunction, bool& injectRead) {
+ AT_LOCATION(value);
+ if (ListLength(value->agg_order) > 0) {
+ AddError("FuncCall: unsupported agg_order");
+ return nullptr;
+ }
+
+ if (value->agg_filter) {
+ AddError("FuncCall: unsupported agg_filter");
+ return nullptr;
+ }
+
+ if (value->agg_within_group) {
+ AddError("FuncCall: unsupported agg_within_group");
+ return nullptr;
+ }
+
+ if (value->func_variadic) {
+ AddError("FuncCall: unsupported func_variadic");
+ return nullptr;
+ }
+
+ TAstNode* window = nullptr;
+ if (value->over) {
+ if (!settings.AllowOver) {
+ AddError(TStringBuilder() << "Over is not allowed in: " << settings.Scope);
+ return nullptr;
+ }
+
+ if (StrLength(value->over->name)) {
+ window = QAX(value->over->name);
+ } else {
+ auto index = settings.WindowItems->size();
+ auto def = ParseWindowDef(value->over);
+ if (!def) {
+ return nullptr;
+ }
+
+ window = L(A("PgAnonWindow"), QA(ToString(index)));
+ settings.WindowItems->push_back(def);
+ }
+ }
+
+ TString name;
+ TString schema;
+ if (!ExtractFuncName(value, name, rangeFunction ? &schema : nullptr)) {
+ return nullptr;
+ }
+
+ if (rangeFunction && !schema.empty() && schema != "pg_catalog") {
+ injectRead = true;
+ return ParseTableRangeFunction(name, schema, value->args);
+ }
+
+ if (name == "shobj_description" || name == "obj_description") {
+ AddWarning(TIssuesIds::PG_COMPAT, name + " function forced to NULL");
+ return L(A("Null"));
+ }
+
+ if (name == "current_schema") {
+ return GetCurrentSchema();
+ }
+
+ // for zabbix https://github.com/ydb-platform/ydb/issues/2904
+ if (name == "pg_try_advisory_lock" || name == "pg_try_advisory_lock_shared" || name == "pg_advisory_unlock" || name == "pg_try_advisory_xact_lock" || name == "pg_try_advisory_xact_lock_shared"){
+ AddWarning(TIssuesIds::PG_COMPAT, name + " function forced to return OK without waiting and without really lock/unlock");
+ return L(A("PgConst"), QA("true"), L(A("PgType"), QA("bool")));
+ }
+
+ if (name == "pg_advisory_lock" || name == "pg_advisory_lock_shared" || name == "pg_advisory_unlock_all" || name == "pg_advisory_xact_lock" || name == "pg_advisory_xact_lock_shared"){
+ AddWarning(TIssuesIds::PG_COMPAT, name + " function forced to return OK without waiting and without really lock/unlock");
+ return L(A("Null"));
+ }
+
+ const bool isAggregateFunc = NYql::NPg::HasAggregation(name, NYql::NPg::EAggKind::Normal);
+ const bool hasReturnSet = NYql::NPg::HasReturnSetProc(name);
+
+ if (isAggregateFunc && !settings.AllowAggregates) {
+ AddError(TStringBuilder() << "Aggregate functions are not allowed in: " << settings.Scope);
+ return nullptr;
+ }
+
+ if (hasReturnSet && !settings.AllowReturnSet) {
+ AddError(TStringBuilder() << "Generator functions are not allowed in: " << settings.Scope);
+ return nullptr;
+ }
+
+ TVector<TAstNode*> args;
+ TString callable;
+ if (window) {
+ if (isAggregateFunc) {
+ callable = "PgAggWindowCall";
+ } else {
+ callable = "PgWindowCall";
+ }
+ } else {
+ if (isAggregateFunc) {
+ callable = "PgAgg";
+ } else {
+ callable = "PgCall";
+ }
+ }
+
+ args.push_back(A(callable));
+ args.push_back(QAX(name));
+ if (window) {
+ args.push_back(window);
+ }
+
+ TVector<TAstNode*> callSettings;
+ if (value->agg_distinct) {
+ if (!isAggregateFunc) {
+ AddError("FuncCall: agg_distinct must be set only for aggregate functions");
+ return nullptr;
+ }
+
+ callSettings.push_back(QL(QA("distinct")));
+ }
+
+ if (rangeFunction) {
+ callSettings.push_back(QL(QA("range")));
+ }
+
+ args.push_back(QVL(callSettings.data(), callSettings.size()));
+ if (value->agg_star) {
+ if (name != "count") {
+ AddError("FuncCall: * is expected only in count function");
+ return nullptr;
+ }
+ } else {
+ if (name == "count" && ListLength(value->args) == 0) {
+ AddError("FuncCall: count(*) must be used to call a parameterless aggregate function");
+ return nullptr;
+ }
+
+ bool hasError = false;
+ for (int i = 0; i < ListLength(value->args); ++i) {
+ auto x = ListNodeNth(value->args, i);
+ auto arg = ParseExpr(x, settings);
+ if (!arg) {
+ hasError = true;
+ continue;
+ }
+
+ args.push_back(arg);
+ }
+
+ if (hasError) {
+ return nullptr;
+ }
+ }
+
+ return VL(args.data(), args.size());
+ }
+
+ bool ExtractFuncName(const FuncCall* value, TString& name, TString* schemaName) {
+ TVector<TString> names;
+ for (int i = 0; i < ListLength(value->funcname); ++i) {
+ auto x = ListNodeNth(value->funcname, i);
+ if (NodeTag(x) != T_String) {
+ NodeNotImplemented(value, x);
+ return false;
+ }
+
+ names.push_back(to_lower(TString(StrVal(x))));
+ }
+
+ if (names.empty()) {
+ AddError("FuncCall: missing function name");
+ return false;
+ }
+
+ if (names.size() > 2) {
+ AddError(TStringBuilder() << "FuncCall: too many name components:: " << names.size());
+ return false;
+ }
+
+ if (names.size() == 2) {
+ if (!schemaName && names[0] != "pg_catalog") {
+ AddError(TStringBuilder() << "FuncCall: expected pg_catalog, but got: " << names[0]);
+ return false;
+ }
+
+ if (schemaName) {
+ *schemaName = names[0];
+ }
+ }
+
+ name = names.back();
+ return true;
+ }
+
+ TAstNode* ParseTypeCast(const TypeCast* value, const TExprSettings& settings) {
+ AT_LOCATION(value);
+ if (!value->arg) {
+ AddError("Expected arg");
+ return nullptr;
+ }
+
+ if (!value->typeName) {
+ AddError("Expected type_name");
+ return nullptr;
+ }
+
+ auto arg = value->arg;
+ auto typeName = value->typeName;
+ auto supportedTypeName = typeName->typeOid == 0 &&
+ !typeName->setof &&
+ !typeName->pct_type &&
+ (ListLength(typeName->names) == 2 &&
+ NodeTag(ListNodeNth(typeName->names, 0)) == T_String &&
+ !StrICompare(StrVal(ListNodeNth(typeName->names, 0)), "pg_catalog") || ListLength(typeName->names) == 1) &&
+ NodeTag(ListNodeNth(typeName->names, ListLength(typeName->names) - 1)) == T_String;
+
+ if (NodeTag(arg) == T_A_Const &&
+ (NodeTag(CAST_NODE(A_Const, arg)->val) == T_String ||
+ CAST_NODE(A_Const, arg)->isnull) &&
+ supportedTypeName &&
+ typeName->typemod == -1 &&
+ ListLength(typeName->typmods) == 0 &&
+ ListLength(typeName->arrayBounds) == 0) {
+ TStringBuf targetType = StrVal(ListNodeNth(typeName->names, ListLength(typeName->names) - 1));
+ if (NodeTag(CAST_NODE(A_Const, arg)->val) == T_String && targetType == "bool") {
+ auto str = StrVal(CAST_NODE(A_Const, arg)->val);
+ return L(A("PgConst"), QAX(str), L(A("PgType"), QA("bool")));
+ }
+ }
+
+ if (supportedTypeName) {
+ AT_LOCATION(typeName);
+ TStringBuf targetType = StrVal(ListNodeNth(typeName->names, ListLength(typeName->names) - 1));
+ auto input = ParseExpr(arg, settings);
+ if (!input) {
+ return nullptr;
+ }
+
+ auto finalType = TString(targetType);
+ if (ListLength(typeName->arrayBounds) && !finalType.StartsWith('_')) {
+ finalType = "_" + finalType;
+ }
+
+ if (!NPg::HasType(finalType)) {
+ AddError(TStringBuilder() << "Unknown type: " << finalType);
+ return nullptr;
+ }
+
+ if (ListLength(typeName->typmods) == 0 && typeName->typemod == -1) {
+ return L(A("PgCast"), input, L(A("PgType"), QAX(finalType)));
+ } else {
+ const auto& typeDesc = NPg::LookupType(finalType);
+ ui32 typeModInFuncId;
+ if (typeDesc.ArrayTypeId == typeDesc.TypeId) {
+ const auto& typeDescElem = NPg::LookupType(typeDesc.ElementTypeId);
+ typeModInFuncId = typeDescElem.TypeModInFuncId;
+ } else {
+ typeModInFuncId = typeDesc.TypeModInFuncId;
+ }
+
+ if (!typeModInFuncId) {
+ AddError(TStringBuilder() << "Type " << finalType << " doesn't support modifiers");
+ return nullptr;
+ }
+
+ const auto& procDesc = NPg::LookupProc(typeModInFuncId);
+
+ TAstNode* typeMod;
+ if (typeName->typemod != -1) {
+ typeMod = L(A("PgConst"), QA(ToString(typeName->typemod)), L(A("PgType"), QA("int4")));
+ } else {
+ TVector<TAstNode*> args;
+ args.push_back(A("PgArray"));
+ for (int i = 0; i < ListLength(typeName->typmods); ++i) {
+ auto typeMod = ListNodeNth(typeName->typmods, i);
+ if (NodeTag(typeMod) != T_A_Const) {
+ AddError("Expected T_A_Const as typmod");
+ return nullptr;
+ }
+
+ auto aConst = CAST_NODE(A_Const, typeMod);
+ TString s;
+ if (!ValueAsString(aConst->val, aConst->isnull, s)) {
+ AddError("Unsupported format of typmod");
+ return nullptr;
+ }
+
+ args.push_back(L(A("PgConst"), QAX(s), L(A("PgType"), QA("cstring"))));
+ }
+
+ typeMod = L(A("PgCall"), QA(procDesc.Name), QL(), VL(args.data(), args.size()));
+ }
+
+ return L(A("PgCast"), input, L(A("PgType"), QAX(finalType)), typeMod);
+ }
+ }
+
+ AddError("Unsupported form of type cast");
+ return nullptr;
+ }
+
+ TAstNode* ParseAndOrExpr(const BoolExpr* value, const TExprSettings& settings, const TString& pgOpName) {
+ auto length = ListLength(value->args);
+ if (length < 2) {
+ AddError(TStringBuilder() << "Expected >1 args for " << pgOpName << " but have " << length << " args");
+ return nullptr;
+ }
+
+ auto lhs = ParseExpr(ListNodeNth(value->args, 0), settings);
+ if (!lhs) {
+ return nullptr;
+ }
+
+ for (auto i = 1; i < length; ++i) {
+ auto rhs = ParseExpr(ListNodeNth(value->args, i), settings);
+ if (!rhs) {
+ return nullptr;
+ }
+ lhs = L(A(pgOpName), lhs, rhs);
+ }
+
+ return lhs;
+ }
+
+ TAstNode* ParseBoolExpr(const BoolExpr* value, const TExprSettings& settings) {
+ AT_LOCATION(value);
+ switch (value->boolop) {
+ case AND_EXPR: {
+ return ParseAndOrExpr(value, settings, "PgAnd");
+ }
+ case OR_EXPR: {
+ return ParseAndOrExpr(value, settings, "PgOr");
+ }
+ case NOT_EXPR: {
+ if (ListLength(value->args) != 1) {
+ AddError("Expected 1 arg for NOT");
+ return nullptr;
+ }
+
+ auto arg = ParseExpr(ListNodeNth(value->args, 0), settings);
+ if (!arg) {
+ return nullptr;
+ }
+
+ return L(A("PgNot"), arg);
+ }
+ default:
+ AddError(TStringBuilder() << "BoolExprType unsupported value: " << (int)value->boolop);
+ return nullptr;
+ }
+ }
+
+ TAstNode* ParseWindowDef(const WindowDef* value) {
+ AT_LOCATION(value);
+ auto name = QAX(value->name);
+ auto refName = QAX(value->refname);
+ TVector<TAstNode*> sortItems;
+ for (int i = 0; i < ListLength(value->orderClause); ++i) {
+ auto node = ListNodeNth(value->orderClause, i);
+ if (NodeTag(node) != T_SortBy) {
+ NodeNotImplemented(value, node);
+ return nullptr;
+ }
+
+ auto sort = ParseSortBy(CAST_NODE_EXT(PG_SortBy, T_SortBy, node), true, false);
+ if (!sort) {
+ return nullptr;
+ }
+
+ sortItems.push_back(sort);
+ }
+
+ auto sort = QVL(sortItems.data(), sortItems.size());
+ TVector<TAstNode*> groupByItems;
+ for (int i = 0; i < ListLength(value->partitionClause); ++i) {
+ auto node = ListNodeNth(value->partitionClause, i);
+ TExprSettings settings;
+ settings.AllowColumns = true;
+ settings.AllowAggregates = true;
+ settings.Scope = "PARTITITON BY";
+ auto expr = ParseExpr(node, settings);
+ if (!expr) {
+ return nullptr;
+ }
+
+ auto lambda = L(A("lambda"), QL(), expr);
+ groupByItems.push_back(L(A("PgGroup"), L(A("Void")), lambda));
+ }
+
+ auto group = QVL(groupByItems.data(), groupByItems.size());
+ TVector<TAstNode*> optionItems;
+ if (value->frameOptions & FRAMEOPTION_NONDEFAULT) {
+ TString exclude;
+ if (value->frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW) {
+ if (exclude) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ exclude = "c";
+ }
+
+ if (value->frameOptions & FRAMEOPTION_EXCLUDE_GROUP) {
+ if (exclude) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ exclude = "cp";
+ }
+
+ if (value->frameOptions & FRAMEOPTION_EXCLUDE_TIES) {
+ if (exclude) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ exclude = "p";
+ }
+
+ if (exclude) {
+ optionItems.push_back(QL(QA("exclude"), QA(exclude)));
+ }
+
+ TString type;
+ if (value->frameOptions & FRAMEOPTION_RANGE) {
+ if (type) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ type = "range";
+ }
+
+ if (value->frameOptions & FRAMEOPTION_ROWS) {
+ if (type) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ type = "rows";
+ }
+
+ if (value->frameOptions & FRAMEOPTION_GROUPS) {
+ if (type) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ type = "groups";
+ }
+
+ if (!type) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ TString from;
+ if (value->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) {
+ if (from) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ from = "up";
+ }
+
+ if (value->frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING) {
+ if (from) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ from = "p";
+ auto offset = ConvertFrameOffset(value->startOffset);
+ if (!offset) {
+ return nullptr;
+ }
+
+ optionItems.push_back(QL(QA("from_value"), offset));
+ }
+
+ if (value->frameOptions & FRAMEOPTION_START_CURRENT_ROW) {
+ if (from) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ from = "c";
+ }
+
+ if (value->frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING) {
+ if (from) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ from = "f";
+ auto offset = ConvertFrameOffset(value->startOffset);
+ if (!offset) {
+ return nullptr;
+ }
+
+ optionItems.push_back(QL(QA("from_value"), offset));
+ }
+
+ if (value->frameOptions & FRAMEOPTION_START_UNBOUNDED_FOLLOWING) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ if (!from) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ TString to;
+ if (value->frameOptions & FRAMEOPTION_END_UNBOUNDED_PRECEDING) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ if (value->frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING) {
+ if (to) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ to = "p";
+ auto offset = ConvertFrameOffset(value->endOffset);
+ if (!offset) {
+ return nullptr;
+ }
+
+ optionItems.push_back(QL(QA("to_value"), offset));
+ }
+
+ if (value->frameOptions & FRAMEOPTION_END_CURRENT_ROW) {
+ if (to) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ to = "c";
+ }
+
+ if (value->frameOptions & FRAMEOPTION_END_OFFSET_FOLLOWING) {
+ if (to) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ to = "f";
+ auto offset = ConvertFrameOffset(value->endOffset);
+ if (!offset) {
+ return nullptr;
+ }
+
+ optionItems.push_back(QL(QA("to_value"), offset));
+ }
+
+ if (value->frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING) {
+ if (to) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ to = "uf";
+ }
+
+ if (!to) {
+ AddError("Wrong frame options");
+ return nullptr;
+ }
+
+ optionItems.push_back(QL(QA("type"), QAX(type)));
+ optionItems.push_back(QL(QA("from"), QAX(from)));
+ optionItems.push_back(QL(QA("to"), QAX(to)));
+ }
+
+ auto options = QVL(optionItems.data(), optionItems.size());
+ return L(A("PgWindow"), name, refName, group, sort, options);
+ }
+
+ TAstNode* ConvertFrameOffset(const Node* off) {
+ if (NodeTag(off) == T_A_Const
+ && NodeTag(CAST_NODE(A_Const, off)->val) == T_Integer) {
+ return L(A("Int32"), QA(ToString(IntVal(CAST_NODE(A_Const, off)->val))));
+ } else {
+ TExprSettings settings;
+ settings.AllowColumns = false;
+ settings.Scope = "FRAME";
+ auto offset = ParseExpr(off, settings);
+ if (!offset) {
+ return nullptr;
+ }
+
+ return L(A("EvaluateExpr"), L(A("Unwrap"), offset, L(A("String"), QA("Frame offset must be non-null"))));
+ }
+ }
+
+ TAstNode* ParseSortBy(const PG_SortBy* value, bool allowAggregates, bool useProjectionRefs) {
+ AT_LOCATION(value);
+ bool asc = true;
+ bool nullsFirst = true;
+ switch (value->sortby_dir) {
+ case SORTBY_DEFAULT:
+ case SORTBY_ASC:
+ if (Settings.PgSortNulls) {
+ nullsFirst = false;
+ }
+ break;
+ case SORTBY_DESC:
+ asc = false;
+ break;
+ default:
+ AddError(TStringBuilder() << "sortby_dir unsupported value: " << (int)value->sortby_dir);
+ return nullptr;
+ }
+
+ switch (value->sortby_nulls) {
+ case SORTBY_NULLS_DEFAULT:
+ break;
+ case SORTBY_NULLS_FIRST:
+ nullsFirst = true;
+ break;
+ case SORTBY_NULLS_LAST:
+ nullsFirst = false;
+ break;
+ default:
+ AddError(TStringBuilder() << "sortby_dir unsupported value: " << (int)value->sortby_dir);
+ return nullptr;
+ }
+
+ if (ListLength(value->useOp) > 0) {
+ AddError("Unsupported operators in sort_by");
+ return nullptr;
+ }
+
+ TAstNode* expr;
+ if (useProjectionRefs && NodeTag(value->node) == T_A_Const && (NodeTag(CAST_NODE(A_Const, value->node)->val) == T_Integer)) {
+ expr = MakeProjectionRef("ORDER BY", CAST_NODE(A_Const, value->node));
+ } else {
+ TExprSettings settings;
+ settings.AllowColumns = true;
+ settings.AllowSubLinks = true;
+ settings.Scope = "ORDER BY";
+ settings.AllowAggregates = allowAggregates;
+ expr = ParseExpr(value->node, settings);
+ }
+
+ if (!expr) {
+ return nullptr;
+ }
+
+ auto lambda = L(A("lambda"), QL(), expr);
+ return L(A("PgSort"), L(A("Void")), lambda, QA(asc ? "asc" : "desc"), QA(nullsFirst ? "first" : "last"));
+ }
+
+ TAstNode* ParseColumnRef(const ColumnRef* value, const TExprSettings& settings) {
+ AT_LOCATION(value);
+ if (!settings.AllowColumns) {
+ AddError(TStringBuilder() << "Columns are not allowed in: " << settings.Scope);
+ return nullptr;
+ }
+
+ if (ListLength(value->fields) == 0) {
+ AddError("No fields");
+ return nullptr;
+ }
+
+ if (ListLength(value->fields) > 2) {
+ AddError("Too many fields");
+ return nullptr;
+ }
+
+ bool isStar = false;
+ TVector<TString> fields;
+ for (int i = 0; i < ListLength(value->fields); ++i) {
+ auto x = ListNodeNth(value->fields, i);
+ if (isStar) {
+ AddError("Star is already defined");
+ return nullptr;
+ }
+
+ if (NodeTag(x) == T_String) {
+ fields.push_back(StrVal(x));
+ } else if (NodeTag(x) == T_A_Star) {
+ isStar = true;
+ } else {
+ NodeNotImplemented(value, x);
+ return nullptr;
+ }
+ }
+
+ if (isStar) {
+ if (fields.size() == 0) {
+ return L(A("PgStar"));
+ } else {
+ return L(A("PgQualifiedStar"), QAX(fields[0]));
+ }
+ } else if (fields.size() == 1) {
+ return L(A("PgColumnRef"), QAX(fields[0]));
+ } else {
+ return L(A("PgColumnRef"), QAX(fields[0]), QAX(fields[1]));
+ }
+ }
+
+ TAstNode* ParseAExprOp(const A_Expr* value, const TExprSettings& settings) {
+ AT_LOCATION(value);
+ if (ListLength(value->name) != 1) {
+ AddError(TStringBuilder() << "Unsupported count of names: " << ListLength(value->name));
+ return nullptr;
+ }
+
+ auto nameNode = ListNodeNth(value->name, 0);
+ if (NodeTag(nameNode) != T_String) {
+ NodeNotImplemented(value, nameNode);
+ return nullptr;
+ }
+
+ auto op = StrVal(nameNode);
+ if (!value->rexpr) {
+ AddError("Missing operands");
+ return nullptr;
+ }
+
+ if (!value->lexpr) {
+ auto rhs = ParseExpr(value->rexpr, settings);
+ if (!rhs) {
+ return nullptr;
+ }
+
+ return L(A("PgOp"), QAX(op), rhs);
+ }
+
+ auto lhs = ParseExpr(value->lexpr, settings);
+ auto rhs = ParseExpr(value->rexpr, settings);
+ if (!lhs || !rhs) {
+ return nullptr;
+ }
+
+ return L(A("PgOp"), QAX(op), lhs, rhs);
+ }
+
+ TAstNode* ParseAExprOpAnyAll(const A_Expr* value, const TExprSettings& settings, bool all) {
+ if (ListLength(value->name) != 1) {
+ AddError(TStringBuilder() << "Unsupported count of names: " << ListLength(value->name));
+ return nullptr;
+ }
+
+ auto nameNode = ListNodeNth(value->name, 0);
+ if (NodeTag(nameNode) != T_String) {
+ NodeNotImplemented(value, nameNode);
+ return nullptr;
+ }
+
+ auto op = StrVal(nameNode);
+ if (!value->lexpr || !value->rexpr) {
+ AddError("Missing operands");
+ return nullptr;
+ }
+
+ auto lhs = ParseExpr(value->lexpr, settings);
+ if (NodeTag(value->rexpr) == T_SubLink) {
+ auto sublink = CAST_NODE(SubLink, value->rexpr);
+ auto subselect = CAST_NODE(SelectStmt, sublink->subselect);
+ if (subselect->withClause && subselect->withClause->recursive) {
+ if (State.ApplicationName && State.ApplicationName->StartsWith("pgAdmin")) {
+ AddWarning(TIssuesIds::PG_COMPAT, "AEXPR_OP_ANY forced to false");
+ return L(A("PgConst"), QA("false"), L(A("PgType"), QA("bool")));
+ }
+ }
+ }
+
+ auto rhs = ParseExpr(value->rexpr, settings);
+ if (!lhs || !rhs) {
+ return nullptr;
+ }
+
+ return L(A(all ? "PgAllOp" : "PgAnyOp"), QAX(op), lhs, rhs);
+ }
+
+ TAstNode* ParseAExprLike(const A_Expr* value, const TExprSettings& settings, bool insensitive) {
+ if (ListLength(value->name) != 1) {
+ AddError(TStringBuilder() << "Unsupported count of names: " << ListLength(value->name));
+ return nullptr;
+ }
+
+ auto nameNode = ListNodeNth(value->name, 0);
+ if (NodeTag(nameNode) != T_String) {
+ NodeNotImplemented(value, nameNode);
+ return nullptr;
+ }
+
+ auto op = TString(StrVal(nameNode));
+ if (insensitive) {
+ if (op != "~~*" && op != "!~~*") {
+ AddError(TStringBuilder() << "Unsupported operation: " << op);
+ return nullptr;
+ }
+ } else {
+ if (op != "~~" && op != "!~~") {
+ AddError(TStringBuilder() << "Unsupported operation: " << op);
+ return nullptr;
+ }
+ }
+
+ if (!value->lexpr || !value->rexpr) {
+ AddError("Missing operands");
+ return nullptr;
+ }
+
+ auto lhs = ParseExpr(value->lexpr, settings);
+ auto rhs = ParseExpr(value->rexpr, settings);
+ if (!lhs || !rhs) {
+ return nullptr;
+ }
+
+ auto ret = L(A(insensitive ? "PgILike" : "PgLike"), lhs, rhs);
+ if (op[0] == '!') {
+ ret = L(A("PgNot"), ret);
+ }
+
+ return ret;
+ }
+
+ TAstNode* ParseAExprNullIf(const A_Expr* value, const TExprSettings& settings) {
+ if (ListLength(value->name) != 1) {
+ AddError(TStringBuilder() << "Unsupported count of names: " << ListLength(value->name));
+ return nullptr;
+ }
+ if (!value->lexpr || !value->rexpr) {
+ AddError("Missing operands");
+ return nullptr;
+ }
+
+ auto lhs = ParseExpr(value->lexpr, settings);
+ auto rhs = ParseExpr(value->rexpr, settings);
+ if (!lhs || !rhs) {
+ return nullptr;
+ }
+ return L(A("PgNullIf"), lhs, rhs);
+ }
+
+ TAstNode* ParseAExprIn(const A_Expr* value, const TExprSettings& settings) {
+ if (ListLength(value->name) != 1) {
+ AddError(TStringBuilder() << "Unsupported count of names: " << ListLength(value->name));
+ return nullptr;
+ }
+
+ auto nameNode = ListNodeNth(value->name, 0);
+ if (NodeTag(nameNode) != T_String) {
+ NodeNotImplemented(value, nameNode);
+ return nullptr;
+ }
+
+ auto op = TString(StrVal(nameNode));
+ if (op != "=" && op != "<>") {
+ AddError(TStringBuilder() << "Unsupported operation: " << op);
+ return nullptr;
+ }
+
+ if (!value->lexpr || !value->rexpr) {
+ AddError("Missing operands");
+ return nullptr;
+ }
+
+ auto lhs = ParseExpr(value->lexpr, settings);
+ if (!lhs) {
+ return nullptr;
+ }
+
+ if (NodeTag(value->rexpr) != T_List) {
+ NodeNotImplemented(value, value->rexpr);
+ return nullptr;
+ }
+
+ auto lst = CAST_NODE(List, value->rexpr);
+
+ TVector<TAstNode*> children;
+ children.reserve(2 + ListLength(lst));
+
+ children.push_back(A("PgIn"));
+ children.push_back(lhs);
+ for (int item = 0; item < ListLength(lst); ++item) {
+ auto cell = ParseExpr(ListNodeNth(lst, item), settings);
+ if (!cell) {
+ return nullptr;
+ }
+ children.push_back(cell);
+ }
+
+ auto ret = VL(children.data(), children.size());
+ if (op[0] == '<') {
+ ret = L(A("PgNot"), ret);
+ }
+
+ return ret;
+ }
+
+ TAstNode* ParseAExprBetween(const A_Expr* value, const TExprSettings& settings) {
+ if (!value->lexpr || !value->rexpr) {
+ AddError("Missing operands");
+ return nullptr;
+ }
+
+ if (NodeTag(value->rexpr) != T_List) {
+ AddError(TStringBuilder() << "Expected T_List tag, but have " << NodeTag(value->rexpr));
+ return nullptr;
+ }
+
+ const List* rexprList = CAST_NODE(List, value->rexpr);
+ if (ListLength(rexprList) != 2) {
+ AddError(TStringBuilder() << "Expected 2 args in BETWEEN range, but have " << ListLength(rexprList));
+ return nullptr;
+ }
+
+ auto b = ListNodeNth(rexprList, 0);
+ auto e = ListNodeNth(rexprList, 1);
+
+ auto lhs = ParseExpr(value->lexpr, settings);
+ auto rbhs = ParseExpr(b, settings);
+ auto rehs = ParseExpr(e, settings);
+ if (!lhs || !rbhs || !rehs) {
+ return nullptr;
+ }
+
+ A_Expr_Kind kind = value->kind;
+ bool inverse = false;
+ if (kind == AEXPR_NOT_BETWEEN) {
+ inverse = true;
+ kind = AEXPR_BETWEEN;
+ } else if (kind == AEXPR_NOT_BETWEEN_SYM) {
+ inverse = true;
+ kind = AEXPR_BETWEEN_SYM;
+ }
+
+ TAstNode* ret;
+ switch (kind) {
+ case AEXPR_BETWEEN:
+ case AEXPR_BETWEEN_SYM:
+ ret = L(A(kind == AEXPR_BETWEEN ? "PgBetween" : "PgBetweenSym"), lhs, rbhs, rehs);
+ break;
+ default:
+ AddError(TStringBuilder() << "BETWEEN kind unsupported value: " << (int)value->kind);
+ return nullptr;
+ }
+
+ if (inverse) {
+ ret = L(A("PgNot"), ret);
+ }
+
+ return ret;
+ }
+
+ TAstNode* ParseAExpr(const A_Expr* value, const TExprSettings& settings) {
+ AT_LOCATION(value);
+ switch (value->kind) {
+ case AEXPR_OP:
+ return ParseAExprOp(value, settings);
+ case AEXPR_LIKE:
+ case AEXPR_ILIKE:
+ return ParseAExprLike(value, settings, value->kind == AEXPR_ILIKE);
+ case AEXPR_IN:
+ return ParseAExprIn(value, settings);
+ case AEXPR_BETWEEN:
+ case AEXPR_NOT_BETWEEN:
+ case AEXPR_BETWEEN_SYM:
+ case AEXPR_NOT_BETWEEN_SYM:
+ return ParseAExprBetween(value, settings);
+ case AEXPR_OP_ANY:
+ case AEXPR_OP_ALL:
+ return ParseAExprOpAnyAll(value, settings, value->kind == AEXPR_OP_ALL);
+ case AEXPR_NULLIF:
+ return ParseAExprNullIf(value, settings);
+ default:
+ AddError(TStringBuilder() << "A_Expr_Kind unsupported value: " << (int)value->kind);
+ return nullptr;
+ }
+
+ }
+
+ void AddVariableDeclarations() {
+ for (const auto& [varName, typeName] : State.ParamNameToPgTypeName) {
+ const auto pgType = L(A("PgType"), QA(typeName));
+ State.Statements.push_back(L(A("declare"), A(varName), pgType));
+ }
+ }
+
+ template <typename T>
+ void NodeNotImplementedImpl(const Node* nodeptr) {
+ TStringBuilder b;
+ b << TypeName<T>() << ": ";
+ b << "alternative is not implemented yet : " << NodeTag(nodeptr);
+ AddError(b);
+ }
+
+ template <typename T>
+ void NodeNotImplemented(const T* outer, const Node* nodeptr) {
+ Y_UNUSED(outer);
+ NodeNotImplementedImpl<T>(nodeptr);
+ }
+
+ void NodeNotImplemented(const Node* nodeptr) {
+ TStringBuilder b;
+ b << "alternative is not implemented yet : " << NodeTag(nodeptr);
+ AddError(b);
+ }
+
+ TAstNode* VL(TAstNode** nodes, ui32 size, TPosition pos = {}) {
+ return TAstNode::NewList(pos.Row ? pos : State.Positions.back(), nodes, size, *AstParseResults[StatementId].Pool);
+ }
+
+ TAstNode* VL(TArrayRef<TAstNode*> nodes, TPosition pos = {}) {
+ return TAstNode::NewList(pos.Row ? pos : State.Positions.back(), nodes.data(), nodes.size(), *AstParseResults[StatementId].Pool);
+ }
+
+ TAstNode* QVL(TAstNode** nodes, ui32 size, TPosition pos = {}) {
+ return Q(VL(nodes, size, pos), pos);
+ }
+
+ TAstNode* QVL(TAstNode* node, TPosition pos = {}) {
+ return QVL(&node, 1, pos);
+ }
+
+ TAstNode* QVL(TArrayRef<TAstNode*> nodes, TPosition pos = {}) {
+ return Q(VL(nodes, pos), pos);
+ }
+
+ TAstNode* A(const TStringBuf str, TPosition pos = {}, ui32 flags = 0) {
+ return TAstNode::NewAtom(pos.Row ? pos : State.Positions.back(), str, *AstParseResults[StatementId].Pool, flags);
+ }
+
+ TAstNode* AX(const TString& str, TPosition pos = {}) {
+ return A(str, pos.Row ? pos : State.Positions.back(), TNodeFlags::ArbitraryContent);
+ }
+
+ TAstNode* Q(TAstNode* node, TPosition pos = {}) {
+ return L(A("quote", pos), node, pos);
+ }
+
+ TAstNode* QA(const TStringBuf str, TPosition pos = {}, ui32 flags = 0) {
+ return Q(A(str, pos, flags), pos);
+ }
+
+ TAstNode* QAX(const TString& str, TPosition pos = {}) {
+ return QA(str, pos, TNodeFlags::ArbitraryContent);
+ }
+
+ template <typename... TNodes>
+ TAstNode* L(TNodes... nodes) {
+ TLState state;
+ LImpl(state, nodes...);
+ return TAstNode::NewList(state.Position.Row ? state.Position : State.Positions.back(), state.Nodes.data(), state.Nodes.size(), *AstParseResults[StatementId].Pool);
+ }
+
+ template <typename... TNodes>
+ TAstNode* QL(TNodes... nodes) {
+ return Q(L(nodes...));
+ }
+
+ template <typename... TNodes>
+ TAstNode* E(TAstNode* list, TNodes... nodes) {
+ Y_ABORT_UNLESS(list->IsList());
+ TVector<TAstNode*> nodes_vec;
+ nodes_vec.reserve(list->GetChildrenCount() + sizeof...(nodes));
+
+ auto children = list->GetChildren();
+ if (children) {
+ nodes_vec.assign(children.begin(), children.end());
+ }
+ nodes_vec.assign({nodes...});
+ return VL(nodes_vec.data(), nodes_vec.size());
+ }
+
+private:
+ void AddError(const TString& value) {
+ AstParseResults[StatementId].Issues.AddIssue(TIssue(State.Positions.back(), value));
+ }
+
+ void AddWarning(int code, const TString& value) {
+ AstParseResults[StatementId].Issues.AddIssue(TIssue(State.Positions.back(), value).SetCode(code, ESeverity::TSeverityIds_ESeverityId_S_WARNING));
+ }
+
+ struct TLState {
+ TPosition Position;
+ TVector<TAstNode*> Nodes;
+ };
+
+ template <typename... TNodes>
+ void LImpl(TLState& state, TNodes... nodes);
+
+ void LImpl(TLState& state) {
+ Y_UNUSED(state);
+ }
+
+ void LImpl(TLState& state, TPosition pos) {
+ state.Position = pos;
+ }
+
+ void LImpl(TLState& state, TAstNode* node) {
+ state.Nodes.push_back(node);
+ }
+
+ template <typename T, typename... TNodes>
+ void LImpl(TLState& state, T node, TNodes... nodes) {
+ state.Nodes.push_back(node);
+ LImpl(state, nodes...);
+ }
+
+ void PushPosition(int location) {
+ if (location == -1) {
+ State.Positions.push_back(State.Positions.back());
+ return;
+ }
+
+ State.Positions.push_back(Location2Position(location));
+ };
+
+ void PopPosition() {
+ State.Positions.pop_back();
+ }
+
+ NYql::TPosition Location2Position(int location) const {
+ if (!QuerySize) {
+ return NYql::TPosition(0, 0);
+ }
+
+ if (location < 0) {
+ return NYql::TPosition(0, 0);
+ }
+
+ auto it = LowerBound(RowStarts.begin(), RowStarts.end(), Min((ui32)location, QuerySize));
+ Y_ENSURE(it != RowStarts.end());
+
+ if (*it == (ui32)location) {
+ auto row = 1 + it - RowStarts.begin();
+ auto column = 1;
+ return NYql::TPosition(column, row);
+ } else {
+ Y_ENSURE(it != RowStarts.begin());
+ auto row = it - RowStarts.begin();
+ auto column = 1 + location - *(it - 1);
+ return NYql::TPosition(column, row);
+ }
+ }
+
+ void ScanRows(const TString& query) {
+ QuerySize = query.size();
+ RowStarts.push_back(0);
+ TPosition position(0, 1);
+ TTextWalker walker(position, true);
+ auto prevRow = position.Row;
+ for (ui32 i = 0; i < query.size(); ++i) {
+ walker.Advance(query[i]);
+ while (position.Row != prevRow) {
+ RowStarts.push_back(i);
+ ++prevRow;
+ }
+ }
+
+ RowStarts.push_back(QuerySize);
+ }
+
+ TAstNode* MakeProjectionRef(const TStringBuf& scope, const A_Const* aConst) {
+ AT_LOCATION(aConst);
+ auto num = IntVal(aConst->val);
+ if (num <= 0) {
+ AddError(TStringBuilder() << scope << ": position " << num << " is not in select list");
+ return nullptr;
+ }
+
+ return L(A("PgProjectionRef"), QA(ToString(num - 1)));
+ }
+
+private:
+ TVector<TAstParseResult>& AstParseResults;
+ NSQLTranslation::TTranslationSettings Settings;
+ bool DqEngineEnabled = false;
+ bool DqEngineForce = false;
+ bool BlockEngineEnabled = false;
+ bool BlockEngineForce = false;
+ bool UnorderedResult = false;
+ TString TablePathPrefix;
+ TVector<ui32> RowStarts;
+ ui32 QuerySize;
+ TString Provider;
+ static const THashMap<TStringBuf, TString> ProviderToInsertModeMap;
+
+ TState State;
+ ui32 StatementId = 0;
+ TVector<TStmtParseInfo>* StmtParseInfo;
+ bool PerStatementResult;
+ TMaybe<ui32> SqlProcArgsCount;
+ bool HasSelectInLimitedView = false;
+};
+
+const THashMap<TStringBuf, TString> TConverter::ProviderToInsertModeMap = {
+ {NYql::KikimrProviderName, "insert_abort"},
+ {NYql::YtProviderName, "append"}
+};
+
+NYql::TAstParseResult PGToYql(const TString& query, const NSQLTranslation::TTranslationSettings& settings, TStmtParseInfo* stmtParseInfo) {
+ TVector<NYql::TAstParseResult> results;
+ TVector<TStmtParseInfo> stmtParseInfos;
+ TConverter converter(results, settings, query, &stmtParseInfos, false, Nothing());
+ NYql::PGParse(query, converter);
+ if (stmtParseInfo) {
+ Y_ENSURE(!stmtParseInfos.empty());
+ *stmtParseInfo = stmtParseInfos.back();
+ }
+ Y_ENSURE(!results.empty());
+ results.back().ActualSyntaxType = NYql::ESyntaxType::Pg;
+ return std::move(results.back());
+}
+
+TVector<NYql::TAstParseResult> PGToYqlStatements(const TString& query, const NSQLTranslation::TTranslationSettings& settings, TVector<TStmtParseInfo>* stmtParseInfo) {
+ TVector<NYql::TAstParseResult> results;
+ TConverter converter(results, settings, query, stmtParseInfo, true, Nothing());
+ NYql::PGParse(query, converter);
+ for (auto& res : results) {
+ res.ActualSyntaxType = NYql::ESyntaxType::Pg;
+ }
+ return results;
+}
+
+bool ParseTypeName(const PG_TypeName* typeName, TString& value, bool* setOf = nullptr) {
+ auto len = ListLength(typeName->names);
+ if (len < 1 || len > 2) {
+ return false;
+ }
+
+ if (len == 2) {
+ auto schemaStr = to_lower(TString(StrVal(ListNodeNth(typeName->names, 0))));
+ if (schemaStr != "pg_catalog") {
+ return false;
+ }
+ }
+
+ value = to_lower(TString(StrVal(ListNodeNth(typeName->names, len - 1))));
+ if (ListLength(typeName->arrayBounds) && !value.StartsWith('_')) {
+ value = "_" + value;
+ }
+
+ if (!setOf && typeName->setof) {
+ return false;
+ }
+
+ if (setOf) {
+ *setOf = typeName->setof;
+ }
+
+ return true;
+}
+
+bool ParseCreateFunctionStmtImpl(const CreateFunctionStmt* value, ui32 extensionIndex,
+ NPg::IExtensionSqlBuilder* builder, NYql::NPg::TProcDesc& desc) {
+ if (ListLength(value->funcname) != 1) {
+ return false;
+ }
+
+ auto nameNode = ListNodeNth(value->funcname, 0);
+ auto name = to_lower(TString(StrVal(nameNode)));
+ desc.ExtensionIndex = extensionIndex;
+ desc.Name = name;
+ desc.IsStrict = false;
+ if (value->returnType) {
+ TString resultTypeStr;
+ if (!ParseTypeName(value->returnType, resultTypeStr, &desc.ReturnSet)) {
+ return false;
+ }
+
+ if (builder) {
+ builder->PrepareType(extensionIndex, resultTypeStr);
+ }
+
+ desc.ResultType = NPg::LookupType(resultTypeStr).TypeId;
+ } else {
+ desc.ResultType = NPg::LookupType("record").TypeId;
+ }
+
+ for (ui32 pass = 0; pass < 2; ++pass) {
+ for (int i = 0; i < ListLength(value->options); ++i) {
+ auto node = LIST_CAST_NTH(DefElem, value->options, i);
+ TString defnameStr(node->defname);
+ if (pass == 1 && defnameStr == "as") {
+ auto asList = CAST_NODE(List, node->arg);
+ auto asListLen = ListLength(asList);
+ if (desc.Lang == NPg::LangC) {
+ if (asListLen < 1 || asListLen > 2) {
+ return false;
+ }
+
+ auto extStr = TString(StrVal(ListNodeNth(asList, 0)));
+ auto srcStr = asListLen > 1 ?
+ TString(StrVal(ListNodeNth(asList, 1))) :
+ name;
+
+ Y_ENSURE(extensionIndex == NPg::LookupExtensionByInstallName(extStr));
+ desc.Src = srcStr;
+ } else if (desc.Lang == NPg::LangInternal || desc.Lang == NPg::LangSQL) {
+ if (asListLen != 1) {
+ return false;
+ }
+
+ auto srcStr = TString(StrVal(ListNodeNth(asList, 0)));
+ desc.Src = srcStr;
+ }
+ } else if (pass == 0 && defnameStr == "strict") {
+ desc.IsStrict = BoolVal(node->arg);
+ } else if (pass == 0 && defnameStr == "language") {
+ auto langStr = to_lower(TString(StrVal(node->arg)));
+ if (langStr == "c") {
+ desc.Lang = NPg::LangC;
+ } else if (extensionIndex == 0 && langStr == "internal") {
+ desc.Lang = NPg::LangInternal;
+ } else if (langStr == "sql") {
+ desc.Lang = NPg::LangSQL;
+ } else {
+ return false;
+ }
+ }
+ }
+ }
+
+ bool hasArgNames = false;
+ for (int i = 0; i < ListLength(value->parameters); ++i) {
+ auto node = LIST_CAST_NTH(FunctionParameter, value->parameters, i);
+ hasArgNames = hasArgNames || (node->name != nullptr);
+ if (node->mode == FUNC_PARAM_IN || node->mode == FUNC_PARAM_DEFAULT) {
+ if (node->defexpr) {
+ desc.DefaultArgs.emplace_back();
+ auto& value = desc.DefaultArgs.back();
+ auto expr = node->defexpr;
+ if (NodeTag(expr) == T_TypeCast) {
+ expr = CAST_NODE(TypeCast, expr)->arg;
+ }
+
+ if (NodeTag(expr) != T_A_Const) {
+ return false;
+ }
+
+ auto pgConst = GetValueNType(CAST_NODE(A_Const, expr));
+ if (!pgConst) {
+ return false;
+ }
+
+ value = pgConst->value;
+ } else {
+ Y_ENSURE(desc.DefaultArgs.empty());
+ }
+
+ desc.InputArgNames.push_back(node->name ? node->name : "");
+ } else if (node->mode == FUNC_PARAM_OUT) {
+ desc.OutputArgNames.push_back(node->name ? node->name : "");
+ } else if (node->mode == FUNC_PARAM_VARIADIC) {
+ desc.VariadicArgName = node->name ? node->name : "";
+ } else {
+ return false;
+ }
+
+ TString argTypeStr;
+ if (!ParseTypeName(node->argType, argTypeStr)) {
+ return false;
+ }
+
+ if (builder) {
+ builder->PrepareType(extensionIndex, argTypeStr);
+ }
+
+ const auto& argTypeDesc = NPg::LookupType(argTypeStr);
+ if (node->mode == FUNC_PARAM_IN || node->mode == FUNC_PARAM_DEFAULT) {
+ desc.ArgTypes.push_back(argTypeDesc.TypeId);
+ } else if (node->mode == FUNC_PARAM_VARIADIC) {
+ desc.VariadicType = (argTypeDesc.ArrayTypeId == argTypeDesc.TypeId) ? argTypeDesc.ElementTypeId : argTypeDesc.TypeId;
+ desc.VariadicArgType = argTypeDesc.TypeId;
+ } else if (node->mode == FUNC_PARAM_OUT) {
+ desc.OutputArgTypes.push_back(argTypeDesc.TypeId);
+ }
+ }
+
+ if (!hasArgNames) {
+ desc.InputArgNames.clear();
+ desc.VariadicArgName.clear();
+ desc.OutputArgNames.clear();
+ }
+
+ if (desc.Lang == NPg::LangSQL) {
+ auto parser = NPg::GetSqlLanguageParser();
+ if (!value->sql_body) {
+ if (parser) {
+ parser->Parse(desc.Src, desc);
+ } else {
+ return false;
+ }
+ } else {
+ if (parser) {
+ parser->ParseNode(value->sql_body, desc);
+ } else {
+ return false;
+ }
+ }
+
+ if (!desc.ExprNode) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+class TExtensionHandler : public IPGParseEvents {
+public:
+ TExtensionHandler(ui32 extensionIndex, NYql::NPg::IExtensionSqlBuilder& builder)
+ : ExtensionIndex(extensionIndex)
+ , Builder(builder)
+ {}
+
+ void OnResult(const List* raw) final {
+ for (int i = 0; i < ListLength(raw); ++i) {
+ if (!ParseRawStmt(LIST_CAST_NTH(RawStmt, raw, i))) {
+ continue;
+ }
+ }
+ }
+
+ void OnError(const TIssue& issue) final {
+ throw yexception() << "Can't parse extension DDL: " << issue.ToString();
+ }
+
+ [[nodiscard]]
+ bool ParseRawStmt(const RawStmt* value) {
+ auto node = value->stmt;
+ switch (NodeTag(node)) {
+ case T_CreateFunctionStmt:
+ return ParseCreateFunctionStmt(CAST_NODE(CreateFunctionStmt, node));
+ case T_DefineStmt:
+ return ParseDefineStmt(CAST_NODE(DefineStmt, node));
+ case T_CreateStmt:
+ return ParseCreateStmt(CAST_NODE(CreateStmt, node));
+ case T_InsertStmt:
+ return ParseInsertStmt(CAST_NODE(InsertStmt, node));
+ case T_CreateCastStmt:
+ return ParseCreateCastStmt(CAST_NODE(CreateCastStmt, node));
+ case T_CreateOpClassStmt:
+ return ParseCreateOpClassStmt(CAST_NODE(CreateOpClassStmt, node));
+ default:
+ return false;
+ }
+ }
+
+ [[nodiscard]]
+ bool ParseDefineStmt(const DefineStmt* value) {
+ switch (value->kind) {
+ case OBJECT_TYPE:
+ return ParseDefineType(value);
+ case OBJECT_OPERATOR:
+ return ParseDefineOperator(value);
+ case OBJECT_AGGREGATE:
+ return ParseDefineAggregate(value);
+ default:
+ return false;
+ }
+ }
+
+ [[nodiscard]]
+ bool ParseDefineType(const DefineStmt* value) {
+ if (ListLength(value->defnames) != 1) {
+ return false;
+ }
+
+ auto nameNode = ListNodeNth(value->defnames, 0);
+ auto name = to_lower(TString(StrVal(nameNode)));
+ Builder.PrepareType(ExtensionIndex, name);
+
+ NPg::TTypeDesc desc = NPg::LookupType(name);
+
+ for (int i = 0; i < ListLength(value->definition); ++i) {
+ auto node = LIST_CAST_NTH(DefElem, value->definition, i);
+ auto defnameStr = to_lower(TString(node->defname));
+ if (defnameStr == "internallength") {
+ if (NodeTag(node->arg) == T_Integer) {
+ desc.TypeLen = IntVal(node->arg);
+ } else if (NodeTag(node->arg) == T_TypeName) {
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ if (value == "variable") {
+ desc.TypeLen = -1;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ } else if (defnameStr == "alignment") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ if (value == "double") {
+ desc.TypeAlign = 'd';
+ } else if (value == "int") {
+ desc.TypeAlign = 'i';
+ } else if (value == "short") {
+ desc.TypeAlign = 's';
+ } else if (value == "char") {
+ desc.TypeAlign = 'c';
+ } else {
+ throw yexception() << "Unsupported alignment: " << value;
+ }
+ } else if (defnameStr == "input") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ try {
+ desc.InFuncId = NPg::LookupProc(value, {NPg::LookupType("cstring").TypeId}).ProcId;
+ } catch (const yexception&) {
+ desc.InFuncId = NPg::LookupProc(value, {
+ NPg::LookupType("cstring").TypeId,
+ NPg::LookupType("oid").TypeId,
+ NPg::LookupType("integer").TypeId
+ }).ProcId;
+ }
+ } else if (defnameStr == "output") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ desc.OutFuncId = NPg::LookupProc(value, {desc.TypeId}).ProcId;
+ } else if (defnameStr == "send") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ desc.SendFuncId = NPg::LookupProc(value, {desc.TypeId}).ProcId;
+ } else if (defnameStr == "receive") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ try {
+ desc.ReceiveFuncId = NPg::LookupProc(value, {NPg::LookupType("internal").TypeId}).ProcId;
+ } catch (const yexception&) {
+ desc.ReceiveFuncId = NPg::LookupProc(value, {
+ NPg::LookupType("internal").TypeId,
+ NPg::LookupType("oid").TypeId,
+ NPg::LookupType("integer").TypeId
+ }).ProcId;
+ }
+ } else if (defnameStr == "delimiter") {
+ if (NodeTag(node->arg) != T_String) {
+ return false;
+ }
+
+ TString value(StrVal(node->arg));
+ Y_ENSURE(value.size() == 1);
+ desc.TypeDelim = value[0];
+ } else if (defnameStr == "typmod_in") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ desc.TypeModInFuncId = NPg::LookupProc(value, {NPg::LookupType("_cstring").TypeId}).ProcId;
+ } else if (defnameStr == "typmod_out") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ desc.TypeModInFuncId = NPg::LookupProc(value, {NPg::LookupType("int4").TypeId}).ProcId;
+ }
+ }
+
+ if (desc.TypeLen >= 0 && desc.TypeLen <= 8) {
+ desc.PassByValue = true;
+ }
+
+ Builder.UpdateType(desc);
+ return true;
+ }
+
+ [[nodiscard]]
+ bool ParseDefineOperator(const DefineStmt* value) {
+ if (ListLength(value->defnames) != 1) {
+ return false;
+ }
+
+ auto nameNode = ListNodeNth(value->defnames, 0);
+ auto name = to_lower(TString(StrVal(nameNode)));
+ TString procedureName;
+ TString commutator;
+ TString negator;
+ ui32 leftType = 0;
+ ui32 rightType = 0;
+ for (int i = 0; i < ListLength(value->definition); ++i) {
+ auto node = LIST_CAST_NTH(DefElem, value->definition, i);
+ auto defnameStr = to_lower(TString(node->defname));
+ if (defnameStr == "leftarg") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ leftType = NPg::LookupType(value).TypeId;
+ } else if (defnameStr == "rightarg") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ rightType = NPg::LookupType(value).TypeId;
+ } else if (defnameStr == "procedure") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ procedureName = value;
+ } else if (defnameStr == "commutator") {
+ if (NodeTag(node->arg) != T_String) {
+ return false;
+ }
+
+ commutator = StrVal(node->arg);
+ } else if (defnameStr == "negator") {
+ if (NodeTag(node->arg) != T_String) {
+ return false;
+ }
+
+ negator = StrVal(node->arg);
+ }
+ }
+
+ if (!leftType) {
+ return false;
+ }
+
+ if (procedureName.empty()) {
+ return false;
+ }
+
+ TVector<ui32> args;
+ args.push_back(leftType);
+ if (rightType) {
+ args.push_back(rightType);
+ }
+
+ Builder.PrepareOper(ExtensionIndex, name, args);
+ auto desc = NPg::LookupOper(name, args);
+ if (!commutator.empty()) {
+ TVector<ui32> commArgs;
+ commArgs.push_back(rightType);
+ commArgs.push_back(leftType);
+ Builder.PrepareOper(ExtensionIndex, commutator, commArgs);
+ desc.ComId = NPg::LookupOper(commutator, commArgs).OperId;
+ }
+
+ if (!negator.empty()) {
+ Builder.PrepareOper(ExtensionIndex, negator, args);
+ desc.NegateId = NPg::LookupOper(negator, args).OperId;
+ }
+
+ const auto& procDesc = NPg::LookupProc(procedureName, args);
+ desc.ProcId = procDesc.ProcId;
+ desc.ResultType = procDesc.ResultType;
+ Builder.UpdateOper(desc);
+ return true;
+ }
+
+ [[nodiscard]]
+ bool ParseDefineAggregate(const DefineStmt* value) {
+ if (ListLength(value->defnames) != 1) {
+ return false;
+ }
+
+ auto nameNode = ListNodeNth(value->defnames, 0);
+ auto name = to_lower(TString(StrVal(nameNode)));
+ TString sfunc;
+ ui32 stype = 0;
+ TString combinefunc;
+ TString finalfunc;
+ TString serialfunc;
+ TString deserialfunc;
+ bool hypothetical = false;
+ for (int i = 0; i < ListLength(value->definition); ++i) {
+ auto node = LIST_CAST_NTH(DefElem, value->definition, i);
+ auto defnameStr = to_lower(TString(node->defname));
+ if (defnameStr == "sfunc") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ sfunc = value;
+ } else if (defnameStr == "stype") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ stype = NPg::LookupType(value).TypeId;
+ } else if (defnameStr == "combinefunc") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ combinefunc = value;
+ } else if (defnameStr == "finalfunc") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ finalfunc = value;
+ } else if (defnameStr == "serialfunc") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ serialfunc = value;
+ } else if (defnameStr == "deserialfunc") {
+ if (NodeTag(node->arg) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) {
+ return false;
+ }
+
+ deserialfunc = value;
+ } else if (defnameStr == "hypothetical") {
+ if (NodeTag(node->arg) != T_Boolean) {
+ return false;
+ }
+
+ if (BoolVal(node->arg)) {
+ hypothetical = true;
+ }
+ }
+ }
+
+ if (!sfunc || !stype) {
+ return false;
+ }
+
+ NPg::TAggregateDesc desc;
+ desc.Name = name;
+ desc.ExtensionIndex = ExtensionIndex;
+ if (ListLength(value->args) != 2) {
+ return false;
+ }
+
+ auto numDirectArgs = intVal(lsecond(value->args));
+ if (numDirectArgs >= 0) {
+ desc.NumDirectArgs = numDirectArgs;
+ desc.Kind = NPg::EAggKind::OrderedSet;
+ Y_ENSURE(!hypothetical);
+ } else if (hypothetical) {
+ desc.Kind = NPg::EAggKind::Hypothetical;
+ }
+
+ auto args = linitial_node(List, value->args);
+ for (int i = 0; i < ListLength(args); ++i) {
+ auto node = LIST_CAST_NTH(FunctionParameter, args, i);
+ if (node->mode == FUNC_PARAM_IN || node->mode == FUNC_PARAM_DEFAULT) {
+ if (node->defexpr) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ TString argTypeStr;
+ if (!ParseTypeName(node->argType, argTypeStr)) {
+ return false;
+ }
+
+ Builder.PrepareType(ExtensionIndex, argTypeStr);
+ auto argTypeId = NPg::LookupType(argTypeStr).TypeId;
+ desc.ArgTypes.push_back(argTypeId);
+ }
+
+ desc.TransTypeId = stype;
+ TVector<ui32> stateWithArgs;
+ stateWithArgs.push_back(stype);
+ stateWithArgs.insert(stateWithArgs.end(), desc.ArgTypes.begin(), desc.ArgTypes.end());
+ desc.TransFuncId = NPg::LookupProc(sfunc, stateWithArgs).ProcId;
+ if (!finalfunc.empty()) {
+ desc.FinalFuncId = NPg::LookupProc(finalfunc, { stype }).ProcId;
+ }
+
+ if (!combinefunc.empty()) {
+ desc.CombineFuncId = NPg::LookupProc(combinefunc, { stype, stype }).ProcId;
+ }
+
+ if (!serialfunc.empty()) {
+ const auto& procDesc = NPg::LookupProc(serialfunc, { stype });
+ Y_ENSURE(procDesc.ResultType == NPg::LookupType("bytea").TypeId);
+ desc.SerializeFuncId = procDesc.ProcId;
+ }
+
+ if (!deserialfunc.empty()) {
+ Y_ENSURE(!serialfunc.empty());
+ const auto& procDesc = NPg::LookupProc(deserialfunc, { NPg::LookupType("bytea").TypeId, stype });
+ Y_ENSURE(procDesc.ResultType == stype);
+ desc.DeserializeFuncId = procDesc.ProcId;
+ }
+
+ Builder.CreateAggregate(desc);
+ return true;
+ }
+
+ [[nodiscard]]
+ bool ParseCreateFunctionStmt(const CreateFunctionStmt* value) {
+ NYql::NPg::TProcDesc desc;
+ if (!ParseCreateFunctionStmtImpl(value, ExtensionIndex, &Builder, desc)) {
+ return false;
+ }
+
+ Builder.CreateProc(desc);
+ return true;
+ }
+
+ [[nodiscard]]
+ bool ParseCreateStmt(const CreateStmt* value) {
+ NPg::TTableInfo table;
+ table.Schema = "pg_catalog";
+ table.Name = value->relation->relname;
+ table.Kind = NPg::ERelKind::Relation;
+ table.ExtensionIndex = ExtensionIndex;
+ TVector<NPg::TColumnInfo> columns;
+ for (int i = 0; i < ListLength(value->tableElts); ++i) {
+ auto node = ListNodeNth(value->tableElts, i);
+ if (NodeTag(node) != T_ColumnDef) {
+ continue;
+ }
+
+ auto columnDef = CAST_NODE(ColumnDef, node);
+ NPg::TColumnInfo column;
+ column.Schema = table.Schema;
+ column.TableName = table.Name;
+ column.Name = columnDef->colname;
+ column.ExtensionIndex = ExtensionIndex;
+ Y_ENSURE(ParseTypeName(columnDef->typeName, column.UdtType));
+ columns.push_back(column);
+ }
+
+ Builder.CreateTable(table, columns);
+ return true;
+ }
+
+ [[nodiscard]]
+ bool ParseInsertStmt(const InsertStmt* value) {
+ TString tableName = value->relation->relname;
+ TVector<TString> colNames;
+ for (int i = 0; i < ListLength(value->cols); ++i) {
+ auto node = LIST_CAST_NTH(ResTarget, value->cols, i);
+ colNames.push_back(node->name);
+ }
+
+ auto select = CAST_NODE(SelectStmt, value->selectStmt);
+ int rows = ListLength(select->valuesLists);
+ if (!rows) {
+ return false;
+ }
+
+ int cols = ListLength(CAST_NODE(List, ListNodeNth(select->valuesLists, 0)));
+ TVector<TMaybe<TString>> data;
+ data.reserve(rows * cols);
+
+ for (int rowIdx = 0; rowIdx < rows; ++rowIdx) {
+ const auto rawRow = CAST_NODE(List, ListNodeNth(select->valuesLists, rowIdx));
+
+ for (int colIdx = 0; colIdx < ListLength(rawRow); ++colIdx) {
+ const auto rawCell = ListNodeNth(rawRow, colIdx);
+ if (NodeTag(rawCell) != T_A_Const) {
+ return false;
+ }
+ auto pgConst = GetValueNType(CAST_NODE(A_Const, rawCell));
+ if (!pgConst) {
+ return false;
+ }
+ data.push_back(pgConst->value);
+ }
+ }
+
+ Builder.InsertValues(NPg::TTableInfoKey{"pg_catalog", tableName}, colNames, data);
+ return true;
+ }
+
+ [[nodiscard]]
+ bool ParseCreateCastStmt(const CreateCastStmt* value) {
+ TString sourceType;
+ if (!ParseTypeName(value->sourcetype, sourceType)) {
+ return false;
+ }
+
+ TString targetType;
+ if (!ParseTypeName(value->targettype, targetType)) {
+ return false;
+ }
+
+ NPg::TCastDesc desc;
+ desc.ExtensionIndex = ExtensionIndex;
+ desc.SourceId = NPg::LookupType(sourceType).TypeId;
+ desc.TargetId = NPg::LookupType(targetType).TypeId;
+ if (value->func) {
+ if (ListLength(value->func->objname) != 1) {
+ return false;
+ }
+
+ TString funcName = StrVal(ListNodeNth(value->func->objname, 0));
+ TVector<ui32> argTypes;
+ for (int i = 0; i < ListLength(value->func->objargs); ++i) {
+ auto node = ListNodeNth(value->func->objargs, i);
+ if (NodeTag(node) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node), value)) {
+ return false;
+ }
+
+ argTypes.push_back(NPg::LookupType(value).TypeId);
+ }
+
+ desc.FunctionId = NPg::LookupProc(funcName, argTypes).ProcId;
+ } else if (value->inout) {
+ desc.Method = NPg::ECastMethod::InOut;
+ } else {
+ desc.Method = NPg::ECastMethod::Binary;
+ }
+
+ switch (value->context) {
+ case COERCION_IMPLICIT:
+ desc.CoercionCode = NPg::ECoercionCode::Implicit;
+ break;
+ case COERCION_ASSIGNMENT:
+ desc.CoercionCode = NPg::ECoercionCode::Assignment;
+ break;
+ case COERCION_EXPLICIT:
+ desc.CoercionCode = NPg::ECoercionCode::Explicit;
+ break;
+ default:
+ return false;
+ }
+
+ Builder.CreateCast(desc);
+ return true;
+ }
+
+ [[nodiscard]]
+ bool ParseCreateOpClassStmt(const CreateOpClassStmt* value) {
+ if (!value->isDefault) {
+ return false;
+ }
+
+ if (ListLength(value->opclassname) != 1) {
+ return false;
+ }
+
+ auto opClassName = to_lower(TString(StrVal(ListNodeNth(value->opclassname, 0))));
+ if (ListLength(value->opfamilyname) > 1) {
+ return false;
+ }
+
+ TString familyName;
+ if (ListLength(value->opfamilyname) == 1) {
+ familyName = to_lower(TString(StrVal(ListNodeNth(value->opfamilyname, 0))));
+ }
+
+ auto amName = to_lower(TString(value->amname));
+ NPg::EOpClassMethod method;
+ if (amName == "btree") {
+ method = NPg::EOpClassMethod::Btree;
+ } else if (amName == "hash") {
+ method = NPg::EOpClassMethod::Hash;
+ } else {
+ return false;
+ }
+
+ TString dataType;
+ if (!ParseTypeName(value->datatype, dataType)) {
+ return false;
+ }
+
+ auto typeId = NPg::LookupType(dataType).TypeId;
+ NPg::TOpClassDesc desc;
+ desc.ExtensionIndex = ExtensionIndex;
+ desc.Method = method;
+ desc.TypeId = typeId;
+ desc.Name = opClassName;
+ if (familyName.empty()) {
+ familyName = amName + "/" + opClassName;
+ }
+
+ desc.Family = familyName;
+ TVector<NPg::TAmOpDesc> ops;
+ TVector<NPg::TAmProcDesc> procs;
+
+ for (int i = 0; i < ListLength(value->items); ++i) {
+ auto node = LIST_CAST_NTH(CreateOpClassItem, value->items, i);
+ if (node->itemtype != OPCLASS_ITEM_OPERATOR && node->itemtype != OPCLASS_ITEM_FUNCTION) {
+ continue;
+ }
+
+ if (ListLength(node->name->objname) != 1) {
+ return false;
+ }
+
+ TString funcName = StrVal(ListNodeNth(node->name->objname, 0));
+ if (node->itemtype == OPCLASS_ITEM_OPERATOR) {
+ NPg::TAmOpDesc amOpDesc;
+ amOpDesc.ExtensionIndex = ExtensionIndex;
+ amOpDesc.Family = familyName;
+ amOpDesc.Strategy = node->number;
+ amOpDesc.LeftType = typeId;
+ amOpDesc.RightType = typeId;
+ amOpDesc.OperId = NPg::LookupOper(funcName, {typeId,typeId}).OperId;
+ ops.push_back(amOpDesc);
+ } else {
+ NPg::TAmProcDesc amProcDesc;
+ amProcDesc.ExtensionIndex = ExtensionIndex;
+ amProcDesc.Family = familyName;
+ amProcDesc.ProcNum = node->number;
+ amProcDesc.LeftType = typeId;
+ amProcDesc.RightType = typeId;
+ TVector<ui32> argTypes;
+ for (int i = 0; i < ListLength(node->name->objargs); ++i) {
+ auto typeName = ListNodeNth(node->name->objargs, i);
+ if (NodeTag(typeName) != T_TypeName) {
+ return false;
+ }
+
+ TString value;
+ if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, typeName), value)) {
+ return false;
+ }
+
+ argTypes.push_back(NPg::LookupType(value).TypeId);
+ }
+
+ amProcDesc.ProcId = NPg::LookupProc(funcName, argTypes).ProcId;
+ procs.push_back(amProcDesc);
+ }
+ }
+
+ Builder.CreateOpClass(desc, ops, procs);
+ return true;
+ }
+
+private:
+ const ui32 ExtensionIndex;
+ NYql::NPg::IExtensionSqlBuilder& Builder;
+};
+
+class TExtensionSqlParser : public NYql::NPg::IExtensionSqlParser {
+public:
+ void Parse(ui32 extensionIndex, const TVector<TString>& sqls, NYql::NPg::IExtensionSqlBuilder& builder) final {
+ TExtensionHandler handler(extensionIndex, builder);
+ for (const auto& sql : sqls) {
+ NYql::PGParse(sql, handler);
+ }
+
+ NKikimr::NMiniKQL::RebuildTypeIndex();
+ }
+};
+
+class TSystemFunctionsHandler : public IPGParseEvents {
+public:
+ TSystemFunctionsHandler(TVector<NPg::TProcDesc>& procs)
+ : Procs(procs)
+ {}
+
+ void OnResult(const List* raw) final {
+ for (int i = 0; i < ListLength(raw); ++i) {
+ if (!ParseRawStmt(LIST_CAST_NTH(RawStmt, raw, i))) {
+ continue;
+ }
+ }
+ }
+
+ void OnError(const TIssue& issue) final {
+ throw yexception() << "Can't parse system functions: " << issue.ToString();
+ }
+
+ [[nodiscard]]
+ bool ParseRawStmt(const RawStmt* value) {
+ auto node = value->stmt;
+ switch (NodeTag(node)) {
+ case T_CreateFunctionStmt:
+ return ParseCreateFunctionStmt(CAST_NODE(CreateFunctionStmt, node));
+ default:
+ return false;
+ }
+ }
+
+ [[nodiscard]]
+ bool ParseCreateFunctionStmt(const CreateFunctionStmt* value) {
+ NYql::NPg::TProcDesc desc;
+ if (!ParseCreateFunctionStmtImpl(value, 0, nullptr, desc)) {
+ return false;
+ }
+
+ Procs.push_back(desc);
+ return true;
+ }
+
+private:
+ TVector<NPg::TProcDesc>& Procs;
+};
+
+class TSystemFunctionsParser : public NYql::NPg::ISystemFunctionsParser {
+public:
+ void Parse(const TString& sql, TVector<NPg::TProcDesc>& procs) const final {
+ TSystemFunctionsHandler handler(procs);
+ NYql::PGParse(sql, handler);
+ }
+};
+
+class TSqlLanguageParser : public NYql::NPg::ISqlLanguageParser, public IPGParseEvents {
+public:
+ TSqlLanguageParser() {
+ Settings.ClusterMapping["pg_catalog"] = TString(PgProviderName);
+ Settings.Mode = NSQLTranslation::ESqlMode::LIMITED_VIEW;
+ }
+
+ void Parse(const TString& sql, NPg::TProcDesc& proc) final {
+ Y_ENSURE(!FreezeGuard.Defined());
+ CurrentProc = &proc;
+ NYql::PGParse(sql, *this);
+ CurrentProc = nullptr;
+ }
+
+ void ParseNode(const Node* stmt, NPg::TProcDesc& proc) final {
+ Y_ENSURE(!FreezeGuard.Defined());
+ proc.ExprNode = nullptr;
+ if (proc.VariadicType) {
+ // Can't be expressed as usual lambda
+ return;
+ }
+
+ TVector<NYql::TAstParseResult> results(1);
+ results[0].Pool = std::make_unique<TMemoryPool>(4096);
+ TVector<TStmtParseInfo> stmtParseInfos(1);
+ TConverter converter(results, Settings, "", &stmtParseInfos, false, proc.ArgTypes.size());
+ converter.PrepareStatements();
+ TAstNode* root = nullptr;
+ switch (NodeTag(stmt)) {
+ case T_SelectStmt:
+ root = converter.ParseSelectStmt(CAST_NODE(SelectStmt, stmt), {.Inner = false});
+ break;
+ case T_ReturnStmt:
+ root = converter.ParseReturnStmt(CAST_NODE(ReturnStmt, stmt));
+ break;
+ default:
+ return;
+ }
+
+ if (!root) {
+ Cerr << "Can't parse SQL for function: " << proc.Name << ", " << results[0].Issues.ToString();
+ return;
+ }
+
+ root = converter.L(converter.A("block"), converter.Q(converter.FinishStatements()));
+ if (NodeTag(stmt) == T_SelectStmt) {
+ root = converter.AsScalarContext(root);
+ }
+
+ TVector<TAstNode*> args;
+ for (ui32 i = 0; i < proc.ArgTypes.size(); ++i) {
+ args.push_back(converter.A("$p" + ToString(i + 1)));
+ }
+
+ root = converter.MakeLambda(args, root);
+ auto program = converter.L(converter.L(converter.A("return"), root));
+ TExprNode::TPtr graph;
+ Ctx.IssueManager.Reset();
+ if (!CompileExpr(*program, graph, Ctx, nullptr, nullptr, false, Max<ui32>(), 1)) {
+ Cerr << "Can't compile SQL for function: " << proc.Name << ", " << Ctx.IssueManager.GetIssues().ToString();
+ return;
+ }
+
+ SavedNodes.push_back(graph);
+ proc.ExprNode = graph.Get();
+ }
+
+ void Freeze() final {
+ Y_ENSURE(!FreezeGuard.Defined());
+ FreezeGuard.ConstructInPlace(Ctx);
+ }
+
+ TExprContext& GetContext() final {
+ Y_ENSURE(FreezeGuard.Defined());
+ return Ctx;
+ }
+
+ void OnResult(const List* raw) final {
+ if (ListLength(raw) == 1) {
+ ParseNode(LIST_CAST_NTH(RawStmt, raw, 0)->stmt, *CurrentProc);
+ }
+ }
+
+ void OnError(const TIssue& issue) final {
+ throw yexception() << "Can't parse SQL for function: " << CurrentProc->Name << ", " << issue.ToString();
+ }
+
+private:
+ NSQLTranslation::TTranslationSettings Settings;
+ TExprContext Ctx;
+ TVector<TExprNode::TPtr> SavedNodes;
+ TMaybe<TExprContext::TFreezeGuard> FreezeGuard;
+ NPg::TProcDesc* CurrentProc = nullptr;
+};
+
+std::unique_ptr<NPg::IExtensionSqlParser> CreateExtensionSqlParser() {
+ return std::make_unique<TExtensionSqlParser>();
+}
+
+std::unique_ptr<NYql::NPg::ISystemFunctionsParser> CreateSystemFunctionsParser() {
+ return std::make_unique<TSystemFunctionsParser>();
+}
+
+std::unique_ptr<NYql::NPg::ISqlLanguageParser> CreateSqlLanguageParser() {
+ return std::make_unique<TSqlLanguageParser>();
+}
+
+} // NSQLTranslationPG