diff options
author | vvvv <[email protected]> | 2024-11-07 12:29:36 +0300 |
---|---|---|
committer | vvvv <[email protected]> | 2024-11-07 13:49:47 +0300 |
commit | d4c258e9431675bab6745c8638df6e3dfd4dca6b (patch) | |
tree | b5efcfa11351152a4c872fccaea35749141c0b11 /yql/essentials/sql/pg/pg_sql_ut.cpp | |
parent | 13a4f274caef5cfdaf0263b24e4d6bdd5521472b (diff) |
Moved other yql/essentials libs YQL-19206
init
commit_hash:7d4c435602078407bbf20dd3c32f9c90d2bbcbc0
Diffstat (limited to 'yql/essentials/sql/pg/pg_sql_ut.cpp')
-rw-r--r-- | yql/essentials/sql/pg/pg_sql_ut.cpp | 1120 |
1 files changed, 1120 insertions, 0 deletions
diff --git a/yql/essentials/sql/pg/pg_sql_ut.cpp b/yql/essentials/sql/pg/pg_sql_ut.cpp new file mode 100644 index 00000000000..99bab179676 --- /dev/null +++ b/yql/essentials/sql/pg/pg_sql_ut.cpp @@ -0,0 +1,1120 @@ +#include "ut/util.h" + +#include <library/cpp/testing/unittest/registar.h> + +#include <yql/essentials/parser/pg_wrapper/interface/parser.h> + +#include <util/system/tempfile.h> + +using namespace NSQLTranslation; + +Y_UNIT_TEST_SUITE(PgSqlParsingOnly) { + Y_UNIT_TEST(Locking) { + auto res = PgSqlToYql("SELECT 1 FROM plato.Input FOR UPDATE"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_EQUAL(res.Issues.Size(), 1); + + auto issue = *(res.Issues.begin()); + UNIT_ASSERT(issue.GetMessage().find("locking") != TString::npos); + } + + Y_UNIT_TEST(InsertStmt) { + auto res = PgSqlToYql("INSERT INTO plato.Input VALUES (1, 1)"); + UNIT_ASSERT(res.Root); + } + + Y_UNIT_TEST(InsertStmt_DefaultValues) { + auto res = PgSqlToYql("INSERT INTO plato.Input DEFAULT VALUES"); + UNIT_ASSERT(res.Root); + + const NYql::TAstNode* writeNode = nullptr; + VisitAstNodes(*res.Root, [&writeNode] (const NYql::TAstNode& node) { + const bool isWriteNode = node.IsList() && node.GetChildrenCount() > 0 + && node.GetChild(0)->IsAtom() && node.GetChild(0)->GetContent() == "Write!"; + if (isWriteNode) { + writeNode = &node; + } + }); + + UNIT_ASSERT(writeNode); + UNIT_ASSERT(writeNode->GetChildrenCount() > 5); + const auto optionsQListNode = writeNode->GetChild(5); + UNIT_ASSERT(optionsQListNode->ToString().Contains("'default_values")); + } + + Y_UNIT_TEST(InsertStmt_Returning) { + auto res = PgSqlToYql("INSERT INTO plato.Input VALUES (1, 1) RETURNING *"); + UNIT_ASSERT(res.Root); + const NYql::TAstNode* writeNode = nullptr; + VisitAstNodes(*res.Root, [&writeNode] (const NYql::TAstNode& node) { + const bool isWriteNode = node.IsList() && node.GetChildrenCount() > 0 + && node.GetChild(0)->IsAtom() && node.GetChild(0)->GetContent() == "Write!"; + if (isWriteNode) { + writeNode = &node; + } + }); + UNIT_ASSERT(writeNode); + UNIT_ASSERT(writeNode->GetChildrenCount() > 5); + const auto optionsQListNode = writeNode->GetChild(5); + UNIT_ASSERT_STRINGS_EQUAL( + optionsQListNode->ToString(), + R"('('('mode 'append) '('returning '((PgResultItem '"" (Void) (lambda '() (PgStar)))))))" + ); + } + + Y_UNIT_TEST(DeleteStmt) { + auto res = PgSqlToYql("DELETE FROM plato.Input"); + UNIT_ASSERT(res.Root); + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let read0 (Read! world (DataSource '"yt" '"plato") (Key '('table (String '"input"))) (Void) '())) + (let world (Left! read0)) + (let world (Write! world (DataSink '"yt" '"plato") (Key '('table (String '"input"))) (Void) '('('pg_delete (PgSelect '('('set_items '((PgSetItem '('('result '((PgResultItem '"" (Void) (lambda '() (PgStar))))) '('from '('((Right! read0) '"input" '()))) '('join_ops '('('('push)))))))) '('set_ops '('push))))) '('mode 'delete)))) + (let world (CommitAll! world)) + (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(DeleteStmt_Returning) { + auto res = PgSqlToYql("DELETE FROM plato.Input RETURNING name, price AS new_price"); + UNIT_ASSERT(res.Root); + const NYql::TAstNode* writeNode = nullptr; + VisitAstNodes(*res.Root, [&writeNode] (const NYql::TAstNode& node) { + const bool isWriteNode = node.IsList() && node.GetChildrenCount() > 0 + && node.GetChild(0)->IsAtom() && node.GetChild(0)->GetContent() == "Write!"; + if (isWriteNode) { + writeNode = &node; + } + }); + UNIT_ASSERT(writeNode); + UNIT_ASSERT(writeNode->GetChildrenCount() > 5); + const auto optionsQListNode = writeNode->GetChild(5); + UNIT_ASSERT_STRINGS_EQUAL( + optionsQListNode->GetChild(1)->GetChild(2)->ToString(), + R"('('returning '((PgResultItem '"name" (Void) (lambda '() (PgColumnRef '"name"))) (PgResultItem '"new_price" (Void) (lambda '() (PgColumnRef '"price"))))))" + ); + } + + Y_UNIT_TEST(CreateTableStmt_Basic) { + auto res = PgSqlToYql("CREATE TABLE t (a int, b text)"); + UNIT_ASSERT(res.Root); + + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") (Key '('tablescheme (String '"t"))) (Void) '('('mode 'create) '('columns '('('a (PgType 'int4) '('columnConstraints '())) '('b (PgType 'text) '('columnConstraints '()))))))) + (let world (CommitAll! world)) + (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(CreateTableStmt_SystemColumns) { + auto res = PgSqlToYql("CREATE TABLE t(XMIN int)"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_EQUAL(res.Issues.Size(), 1); + + auto issue = *(res.Issues.begin()); + UNIT_ASSERT(issue.GetMessage().find("system column") != TString::npos); + } + + Y_UNIT_TEST(CreateTableStmt_NotNull) { + auto res = PgSqlToYql("CREATE TABLE t (a int NOT NULL, b text)"); + UNIT_ASSERT(res.Root); + + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") (Key '('tablescheme (String '"t"))) (Void) '('('mode 'create) '('columns '('('a (PgType 'int4) '('columnConstraints '('('not_null)))) '('b (PgType 'text) '('columnConstraints '()))))))) + (let world (CommitAll! world)) + (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(CreateTableStmt_JustPK) { + auto res = PgSqlToYql("CREATE TABLE t (a int PRIMARY KEY, b text)"); + UNIT_ASSERT(res.Root); + + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") (Key '('tablescheme (String '"t"))) (Void) '('('mode 'create) '('columns '('('a (PgType 'int4) '('columnConstraints '('('not_null)))) '('b (PgType 'text) '('columnConstraints '())))) '('primarykey '('a))))) + (let world (CommitAll! world)) + (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(CreateTableStmt_Default) { + auto res = PgSqlToYql("CREATE TABLE t (a int PRIMARY KEY, b int DEFAULT 0)"); + UNIT_ASSERT(res.Root); + + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") (Key '('tablescheme (String '"t"))) (Void) '('('mode 'create) '('columns '('('a (PgType 'int4) '('columnConstraints '('('not_null)))) '('b (PgType 'int4) '('columnConstraints '('('default (PgConst '0 (PgType 'int4)))))))) '('primarykey '('a))))) + (let world (CommitAll! world)) + (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(CreateTableStmt_PKAndNotNull) { + auto res = PgSqlToYql("CREATE TABLE t (a int PRIMARY KEY NOT NULL, b text)"); + UNIT_ASSERT(res.Root); + + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") (Key '('tablescheme (String '"t"))) (Void) '('('mode 'create) '('columns '('('a (PgType 'int4) '('columnConstraints '('('not_null)))) '('b (PgType 'text) '('columnConstraints '())))) '('primarykey '('a))))) + (let world (CommitAll! world)) + (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(CreateTableStmt_PKAndOtherNotNull) { + auto res = PgSqlToYql("CREATE TABLE t (a int PRIMARY KEY, b text NOT NULL)"); + UNIT_ASSERT(res.Root); + + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") (Key '('tablescheme (String '"t"))) (Void) '('('mode 'create) '('columns '('('a (PgType 'int4) '('columnConstraints '('('not_null)))) '('b (PgType 'text) '('columnConstraints '('('not_null)))))) '('primarykey '('a))))) + (let world (CommitAll! world)) + (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(CreateTableStmt_TableLevelPK) { + auto res = PgSqlToYql("CREATE TABLE t (a int, b text NOT NULL, PRIMARY KEY (a, b))"); + UNIT_ASSERT(res.Root); + + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") (Key '('tablescheme (String '"t"))) (Void) '('('mode 'create) '('columns '('('a (PgType 'int4) '('columnConstraints '('('not_null)))) '('b (PgType 'text) '('columnConstraints '('('not_null)))))) '('primarykey '('a 'b))))) + (let world (CommitAll! world)) + (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(CreateTableStmt_RepeatingColumnNames) { + auto res = PgSqlToYql("CREATE TABLE t (a int, a text)"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_EQUAL(res.Issues.Size(), 1); + + auto issue = *(res.Issues.begin()); + UNIT_ASSERT(issue.GetMessage().find("duplicate") != TString::npos); + } + + Y_UNIT_TEST(CreateTableStmt_PKHasColumnsNotBelongingToTable_Fails) { + auto res = PgSqlToYql("CREATE TABLE t (a int, primary key(b))"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_EQUAL(res.Issues.Size(), 1); + + auto issue = *(res.Issues.begin()); + UNIT_ASSERT(issue.GetMessage().find("PK column does not belong to table") != TString::npos); + } + + Y_UNIT_TEST(CreateTableStmt_AliasSerialToIntType) { + auto res = PgSqlToYql("CREATE TABLE t (a SerIAL)"); + UNIT_ASSERT(res.Root); + + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") (Key '('tablescheme (String '"t"))) (Void) '('('mode 'create) '('columns '('('a (PgType 'int4) '('columnConstraints '('('serial))))))))) + (let world (CommitAll! world)) + (return world)) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(CreateTableStmt_Temp) { + auto res = PgSqlToYql("create temp table t ()"); + UNIT_ASSERT(res.Root); + + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") (Key '('tablescheme (String '"t"))) (Void) '('('mode 'create) '('columns '()) '('temporary)))) + (let world (CommitAll! world)) + (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(CreateSeqStmt) { + auto res = PgSqlToYql( + "CREATE TEMP SEQUENCE IF NOT EXISTS seq AS integer START WITH 10 INCREMENT BY 2 NO MINVALUE NO MAXVALUE CACHE 3;"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TString program = R"( + ((let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") + (Key '('pgObject (String '"seq") (String 'pgSequence))) (Void) '( + '('mode 'create_if_not_exists) '('temporary) '('"as" '"int4") + '('"start" '10) '('"increment" '2) '('"cache" '3)))) + (let world (CommitAll! world)) (return world)) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(DropSequenceStmt) { + auto res = PgSqlToYql("DROP SEQUENCE IF EXISTS seq;"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) (let world (Write! world (DataSink '"kikimr" '"") (Key '('pgObject (String '"seq") (String 'pgSequence))) (Void) '('('mode 'drop_if_exists)))) + (let world (CommitAll! world)) + (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(AlterSequenceStmt) { + auto res = PgSqlToYql("ALTER SEQUENCE IF EXISTS seq AS integer START WITH 10 INCREMENT BY 2 NO MINVALUE NO MAXVALUE CACHE 3;"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") + (Key '('pgObject (String '"seq") (String 'pgSequence))) + (Void) '('('mode 'alter_if_exists) '('"as" '"int4") '('"start" '10) '('"increment" '2) '('"cache" '3)))) + (let world (CommitAll! world)) (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(AlterTableSetDefaultNextvalStmt) { + auto res = PgSqlToYql("ALTER TABLE public.t ALTER COLUMN id SET DEFAULT nextval('seq');"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") + (Key '('tablescheme (String '"t"))) (Void) '('('mode 'alter) '('actions '('('alterColumns '('('"id" '('setDefault '('nextval 'seq)))))))))) + (let world (CommitAll! world)) (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(AlterTableSetDefaultNextvalStmtWithSchemaname) { + auto res = PgSqlToYql("ALTER TABLE public.t ALTER COLUMN id SET DEFAULT nextval('public.seq'::regclass);"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") + (Key '('tablescheme (String '"t"))) (Void) '('('mode 'alter) '('actions '('('alterColumns '('('"id" '('setDefault '('nextval 'seq)))))))))) + (let world (CommitAll! world)) (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(AlterTableStmtWithCast) { + auto res = PgSqlToYql("ALTER TABLE public.t ALTER COLUMN id SET DEFAULT nextval('seq'::regclass);"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") + (Key '('tablescheme (String '"t"))) (Void) '('('mode 'alter) '('actions '('('alterColumns '('('"id" '('setDefault '('nextval 'seq)))))))))) + (let world (CommitAll! world)) (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(AlterTableDropDefaultStmt) { + auto res = PgSqlToYql("ALTER TABLE public.t ALTER COLUMN id DROP DEFAULT;"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") + (Key '('tablescheme (String '"t"))) (Void) '('('mode 'alter) '('actions '('('alterColumns '('('"id" '('setDefault '('Null)))))))))) + (let world (CommitAll! world)) (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(VariableShowStmt) { + auto res = PgSqlToYql("Show server_version_num"); + UNIT_ASSERT(res.Root); + + TString program = fmt::format(R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let output (PgSelect '('('set_items '((PgSetItem '('('result '((PgResultItem '"server_version_num" (Void) (lambda '() (PgConst '"{}" (PgType 'text)))))))))) '('set_ops '('push))))) + (let result_sink (DataSink 'result)) + (let world (Write! world result_sink (Key) output '('('type) '('autoref)))) + (let world (Commit! world result_sink)) + (let world (CommitAll! world)) + (return world) + ) + )", NYql::GetPostgresServerVersionNum()); + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + TMap<TString, TString> GetParamNameToPgType(const NYql::TAstNode& root) { + TMap<TString, TString> actualParamToType; + + VisitAstNodes(root, [&actualParamToType] (const NYql::TAstNode& node) { + bool isDeclareNode = + node.IsListOfSize(3) && node.GetChild(0)->IsAtom() + && node.GetChild(0)->GetContent() == "declare"; + if (isDeclareNode) { + const auto varNameNode = node.GetChild(1); + UNIT_ASSERT(varNameNode->IsAtom()); + const auto varName = varNameNode->GetContent(); + + const auto varTypeNode = node.GetChild(2); + UNIT_ASSERT(varTypeNode->IsListOfSize(2)); + UNIT_ASSERT(varTypeNode->GetChild(0)->GetContent() == "PgType"); + actualParamToType[TString(varName)] = varTypeNode->GetChild(1)->ToString(); + } + }); + + return actualParamToType; + } + + Y_UNIT_TEST(ParamRef_IntAndPoint) { + TTranslationSettings settings; + + settings.PgParameterTypeOids = {NYql::NPg::LookupType("int4").TypeId, NYql::NPg::LookupType("point").TypeId}; + auto res = SqlToYqlWithMode( + R"(select $1 as "x", $2 as "y")", + NSQLTranslation::ESqlMode::QUERY, + 10, + {}, + EDebugOutput::None, + false, + settings); + TMap<TString, TString> expectedParamToType { + {"$p1", "'int4"}, + {"$p2", "'point"}, + }; + UNIT_ASSERT(res.Root); + const auto actualParamToTypes = GetParamNameToPgType(*res.Root); + UNIT_ASSERT(expectedParamToType.size() == actualParamToTypes.size()); + UNIT_ASSERT_EQUAL(expectedParamToType, actualParamToTypes); + } + + Y_UNIT_TEST(ParamRef_IntUnknownInt) { + TTranslationSettings settings; + settings.PgParameterTypeOids = {NYql::NPg::LookupType("int4").TypeId, NYql::NPg::LookupType("unknown").TypeId, NYql::NPg::LookupType("int4").TypeId}; + auto res = SqlToYqlWithMode( + R"(select $1 as "x", $2 as "y", $3 as "z")", + NSQLTranslation::ESqlMode::QUERY, + 10, + {}, + EDebugOutput::None, + false, + settings); + TMap<TString, TString> expectedParamToType { + {"$p1", "'int4"}, + {"$p2", "'unknown"}, + {"$p3", "'int4"}, + }; + UNIT_ASSERT(res.Root); + const auto actualParamToTypes = GetParamNameToPgType(*res.Root); + UNIT_ASSERT(expectedParamToType.size() == actualParamToTypes.size()); + UNIT_ASSERT_EQUAL(expectedParamToType, actualParamToTypes); + } + + Y_UNIT_TEST(ParamRef_NoTypeOids) { + TTranslationSettings settings; + settings.PgParameterTypeOids = {}; + auto res = PgSqlToYql(R"(select $1 as "x", $2 as "y", $3 as "z")"); + TMap<TString, TString> expectedParamToType { + {"$p1", "'unknown"}, + {"$p2", "'unknown"}, + {"$p3", "'unknown"}, + }; + UNIT_ASSERT(res.Root); + auto actualParamToTypes = GetParamNameToPgType(*res.Root); + UNIT_ASSERT_VALUES_EQUAL(expectedParamToType, actualParamToTypes); + } + + Y_UNIT_TEST(DropTableStmt) { + auto res = PgSqlToYql("drop table plato.Input"); + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"yt" '"plato") (Key '('tablescheme (String '"input"))) (Void) '('('mode 'drop)))) + (let world (CommitAll! world)) + (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(DropTableStmtMultiple) { + auto res = PgSqlToYql("DROP TABLE FakeTable1, FakeTable2"); + TString program = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let world (Write! world (DataSink '"kikimr" '"") (Key '('tablescheme (String '"FakeTable1"))) (Void) '('('mode 'drop)))) + (let world (Write! world (DataSink '"kikimr" '"") (Key '('tablescheme (String '"FakeTable2"))) (Void) '('('mode 'drop)))) + (let world (CommitAll! world)) + (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(program); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(DropTableUnknownClusterStmt) { + auto res = PgSqlToYql("drop table if exists pub.t"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_EQUAL(res.Issues.Size(), 1); + + auto issue = *(res.Issues.begin()); + UNIT_ASSERT_C(issue.GetMessage().find("Unknown cluster: pub") != TString::npos, res.Issues.ToString()); + } + + Y_UNIT_TEST(PublicSchemeRemove) { + auto res = PgSqlToYql("DROP TABLE IF EXISTS public.t; CREATE TABLE public.t(id INT PRIMARY KEY, foo INT);\ +INSERT INTO public.t VALUES(1, 2);\ +UPDATE public.t SET foo = 3 WHERE id == 1;\ +DELETE FROM public.t WHERE id == 1;\ +SELECT COUNT(*) FROM public.t;"); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Root->ToString().find("public") == TString::npos); + } + + Y_UNIT_TEST(UpdateStmt) { + auto res = PgSqlToYql("UPDATE plato.Input SET kind = 'test' where kind = 'testtest'"); + TString updateStmtProg = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let read0 (Read! world (DataSource '"yt" '"plato") (Key '('table (String '"input"))) (Void) '())) + (let world (Left! read0)) + (let world (block '((let update_select + (PgSelect '( + '('set_items '((PgSetItem + '('('emit_pg_star) + '('result '((PgResultItem '"" (Void) (lambda '() (PgStar))) + (PgResultItem '"kind" (Void) (lambda '() (PgConst '"test" (PgType 'unknown)))))) + '('from '('((Right! read0) '"input" '()))) + '('join_ops '('('('push)))) + '('where (PgWhere (Void) (lambda '() (PgOp '"=" (PgColumnRef '"kind") (PgConst '"testtest" (PgType 'unknown)))))) '('unknowns_allowed))))) + '('set_ops '('push))) + ) + ) + (let sink (DataSink '"yt" '"plato")) + (let key (Key '('table (String '"input")))) + (return (Write! world sink key (Void) '('('pg_update update_select) '('mode 'update))))))) + (let world (CommitAll! world)) + (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(updateStmtProg); + + UNIT_ASSERT_C(res.Issues.Empty(), "Failed to parse statement, issues: " + res.Issues.ToString()); + UNIT_ASSERT_C(res.Root, "Failed to parse statement, root is nullptr"); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } + + Y_UNIT_TEST(BlockEngine) { + auto res = PgSqlToYql("set blockEngine='auto'; select 1;"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_STRING_CONTAINS(res.Root->ToString(), "(let world (Configure! world (DataSource 'config) 'BlockEngine 'auto))"); + + res = PgSqlToYql("set Blockengine='force'; select 1;"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT_STRING_CONTAINS(res.Root->ToString(), "(let world (Configure! world (DataSource 'config) 'BlockEngine 'force))"); + + res = PgSqlToYql("set BlockEngine='disable'; select 1;"); + UNIT_ASSERT(res.Root); + UNIT_ASSERT(!res.Root->ToString().Contains("BlockEngine")); + + res = PgSqlToYql("set BlockEngine='foo'; select 1;"); + UNIT_ASSERT(!res.Root); + UNIT_ASSERT_EQUAL(res.Issues.Size(), 1); + + auto issue = *(res.Issues.begin()); + UNIT_ASSERT(issue.GetMessage().Contains("VariableSetStmt, not supported BlockEngine option value: foo")); + } + + Y_UNIT_TEST(SetConfig_SearchPath) { + TTranslationSettings settings; + settings.GUCSettings = std::make_shared<TGUCSettings>(); + settings.ClusterMapping["pg_catalog"] = NYql::PgProviderName; + settings.DefaultCluster = ""; + + auto res = SqlToYqlWithMode( + R"(select set_config('search_path', 'pg_catalog', false);)", + NSQLTranslation::ESqlMode::QUERY, + 10, + {}, + EDebugOutput::ToCerr, + false, + settings); + UNIT_ASSERT_C(res.IsOk(), res.Issues.ToString()); + UNIT_ASSERT(res.Root); + + res = SqlToYqlWithMode( + R"(select oid, +typinput::int4 as typinput, +typname, +typnamespace, +typtype +from pg_type)", + NSQLTranslation::ESqlMode::QUERY, + 10, + {}, + EDebugOutput::None, + false, + settings); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Root); + + res = SqlToYqlWithMode( + R"(select oid, +typinput::int4 as typinput, +typname, +typnamespace, +typtype +from pg_catalog.pg_type)", + NSQLTranslation::ESqlMode::QUERY, + 10, + {}, + EDebugOutput::None, + false, + settings); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Root); + + res = SqlToYqlWithMode( + R"(select set_config('search_path', 'public', false);)", + NSQLTranslation::ESqlMode::QUERY, + 10, + {}, + EDebugOutput::None, + false, + settings); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Root); + + res = SqlToYqlWithMode( + R"(select * from pg_type;)", + NSQLTranslation::ESqlMode::QUERY, + 10, + {}, + EDebugOutput::None, + false, + settings); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Root); + } +} + +Y_UNIT_TEST_SUITE(PgExtensions) { + using namespace NYql; + + Y_UNIT_TEST(Empty) { + NPg::ClearExtensions(); + UNIT_ASSERT_VALUES_EQUAL(NPg::ExportExtensions(), ""); + NPg::ImportExtensions("", true, nullptr); + } + + Y_UNIT_TEST(ProcsAndType) { + NPg::ClearExtensions(); + if (NPg::AreAllFunctionsAllowed()) { + return; + } + + NPg::TExtensionDesc desc; + TTempFileHandle h; + TStringBuf sql = R"( + CREATE OR REPLACE FUNCTION mytype_in(cstring) + RETURNS mytype + AS '$libdir/MyExt','mytype_in_func' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OR REPLACE FUNCTION mytype_out(mytype) + RETURNS cstring + AS '$libdir/MyExt','mytype_out_func' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE TYPE mytype ( + alignment = double, + internallength = 65, + input = mytype_in, + output = mytype_out + ); + )"; + + h.Write(sql.data(), sql.size()); + desc.Name = "MyExt"; + desc.InstallName = "$libdir/MyExt"; + desc.SqlPaths.push_back(h.Name()); + NPg::RegisterExtensions({desc}, true, *NSQLTranslationPG::CreateExtensionSqlParser(), nullptr); + auto validate = [&]() { + const auto& type = NPg::LookupType("mytype"); + UNIT_ASSERT_VALUES_EQUAL(type.Category, 'U'); + UNIT_ASSERT_VALUES_EQUAL(type.TypeLen, 65); + UNIT_ASSERT_VALUES_EQUAL(type.TypeAlign, 'd'); + const auto& arrType = NPg::LookupType("_mytype"); + UNIT_ASSERT_VALUES_EQUAL(arrType.ElementTypeId, type.TypeId); + const auto& inProc = NPg::LookupProc("mytype_in", { NPg::LookupType("cstring").TypeId }); + UNIT_ASSERT_VALUES_EQUAL(inProc.ArgTypes.size(), 1); + UNIT_ASSERT_VALUES_EQUAL(inProc.Src, "mytype_in_func"); + UNIT_ASSERT(inProc.IsStrict); + const auto& outProc = NPg::LookupProc("mytype_out", { NPg::LookupType("mytype").TypeId }); + UNIT_ASSERT_VALUES_EQUAL(outProc.ArgTypes.size(), 1); + UNIT_ASSERT_VALUES_EQUAL(outProc.Src, "mytype_out_func"); + UNIT_ASSERT(outProc.IsStrict); + UNIT_ASSERT_VALUES_EQUAL(type.InFuncId, inProc.ProcId); + UNIT_ASSERT_VALUES_EQUAL(type.OutFuncId, outProc.ProcId); + }; + + validate(); + auto exported = NPg::ExportExtensions(); + NPg::ClearExtensions(); + NPg::ImportExtensions(exported, true, nullptr); + validate(); + } + + Y_UNIT_TEST(InsertValues) { + NPg::ClearExtensions(); + NPg::TExtensionDesc desc; + TTempFileHandle h; + TStringBuf sql = R"( + CREATE TABLE mytable( + foo int4, + bar text, + baz double + ); + + INSERT INTO mytable(bar, foo, baz) + VALUES ('a', 1, null),('b', null, -3.4); + )"; + + h.Write(sql.data(), sql.size()); + desc.Name = "MyExt"; + desc.InstallName = "$libdir/MyExt"; + desc.SqlPaths.push_back(h.Name()); + NPg::RegisterExtensions({desc}, true, *NSQLTranslationPG::CreateExtensionSqlParser(), nullptr); + auto validate = [&]() { + const auto& table = NPg::LookupStaticTable({"pg_catalog","mytable"}); + UNIT_ASSERT(table.Kind == NPg::ERelKind::Relation); + size_t remap[2]; + size_t rowStep; + const auto& data = *NPg::ReadTable({"pg_catalog", "mytable"}, {"foo", "bar"}, remap, rowStep); + UNIT_ASSERT_VALUES_EQUAL(rowStep, 3); + UNIT_ASSERT_VALUES_EQUAL(data.size(), 2 * rowStep); + UNIT_ASSERT_VALUES_EQUAL(data[rowStep * 0 + remap[0]], "1"); + UNIT_ASSERT_VALUES_EQUAL(data[rowStep * 0 + remap[1]], "a"); + UNIT_ASSERT(!data[rowStep * 1 + remap[0]].Defined()); + UNIT_ASSERT_VALUES_EQUAL(data[rowStep * 1 + remap[1]], "b"); + }; + + validate(); + auto exported = NPg::ExportExtensions(); + NPg::ClearExtensions(); + NPg::ImportExtensions(exported, true, nullptr); + validate(); + } + + Y_UNIT_TEST(Casts) { + NPg::ClearExtensions(); + if (NPg::AreAllFunctionsAllowed()) { + return; + } + + NPg::TExtensionDesc desc; + TTempFileHandle h; + TStringBuf sql = R"( + CREATE TYPE foo ( + alignment = double, + internallength = variable + ); + + CREATE TYPE bar ( + alignment = double, + internallength = variable + ); + + CREATE OR REPLACE FUNCTION bar(foo) + RETURNS bar + AS '$libdir/MyExt','foo_to_bar' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE CAST (foo AS bar) WITH FUNCTION bar(foo); + )"; + + h.Write(sql.data(), sql.size()); + desc.Name = "MyExt"; + desc.InstallName = "$libdir/MyExt"; + desc.SqlPaths.push_back(h.Name()); + NPg::RegisterExtensions({desc}, true, *NSQLTranslationPG::CreateExtensionSqlParser(), nullptr); + auto validate = [&]() { + auto sourceId = NPg::LookupType("foo").TypeId; + auto targetId = NPg::LookupType("bar").TypeId; + UNIT_ASSERT(NPg::HasCast(sourceId, targetId)); + const auto& cast = NPg::LookupCast(sourceId, targetId); + UNIT_ASSERT_VALUES_EQUAL(cast.SourceId, sourceId); + UNIT_ASSERT_VALUES_EQUAL(cast.TargetId, targetId); + UNIT_ASSERT_VALUES_EQUAL((ui32)cast.Method, (ui32)NPg::ECastMethod::Function); + UNIT_ASSERT_VALUES_EQUAL(cast.CoercionCode, NPg::ECoercionCode::Explicit); + UNIT_ASSERT_VALUES_EQUAL(cast.FunctionId, NPg::LookupProc("bar",{sourceId}).ProcId); + }; + + validate(); + auto exported = NPg::ExportExtensions(); + NPg::ClearExtensions(); + NPg::ImportExtensions(exported, true, nullptr); + validate(); + } + + Y_UNIT_TEST(Operators) { + NPg::ClearExtensions(); + if (NPg::AreAllFunctionsAllowed()) { + return; + } + + NPg::TExtensionDesc desc; + TTempFileHandle h; + TStringBuf sql = R"( + CREATE TYPE foo ( + alignment = double, + internallength = variable + ); + + CREATE OR REPLACE FUNCTION foo_lt(foo, foo) + RETURNS bool + AS '$libdir/MyExt','foo_lt' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OR REPLACE FUNCTION foo_le(foo, foo) + RETURNS bool + AS '$libdir/MyExt','foo_le' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OR REPLACE FUNCTION foo_gt(foo, foo) + RETURNS bool + AS '$libdir/MyExt','foo_gt' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OR REPLACE FUNCTION foo_ge(foo, foo) + RETURNS bool + AS '$libdir/MyExt','foo_ge' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OPERATOR < ( + LEFTARG = foo, RIGHTARG = foo, PROCEDURE = foo_lt, + COMMUTATOR = '>', NEGATOR = '>=' + ); + + CREATE OPERATOR <= ( + LEFTARG = foo, RIGHTARG = foo, PROCEDURE = foo_le, + COMMUTATOR = '>=', NEGATOR = '>' + ); + + CREATE OPERATOR > ( + LEFTARG = foo, RIGHTARG = foo, PROCEDURE = foo_gt, + COMMUTATOR = '<', NEGATOR = '<=' + ); + + CREATE OPERATOR >= ( + LEFTARG = foo, RIGHTARG = foo, PROCEDURE = foo_ge, + COMMUTATOR = '<=', NEGATOR = '<' + ); + )"; + + h.Write(sql.data(), sql.size()); + desc.Name = "MyExt"; + desc.InstallName = "$libdir/MyExt"; + desc.SqlPaths.push_back(h.Name()); + NPg::RegisterExtensions({desc}, true, *NSQLTranslationPG::CreateExtensionSqlParser(), nullptr); + auto validate = [&]() { + auto typeId = NPg::LookupType("foo").TypeId; + TVector<ui32> args { typeId, typeId }; + auto lessProcId = NPg::LookupProc("foo_lt", args).ProcId; + auto lessOrEqualProcId = NPg::LookupProc("foo_le", args).ProcId; + auto greaterProcId = NPg::LookupProc("foo_gt", args).ProcId; + auto greaterOrEqualProcId = NPg::LookupProc("foo_ge", args).ProcId; + + const auto& lessOp = NPg::LookupOper("<", args); + const auto& lessOrEqualOp = NPg::LookupOper("<=", args); + const auto& greaterOp = NPg::LookupOper(">", args); + const auto& greaterOrEqualOp = NPg::LookupOper(">=", args); + + UNIT_ASSERT_VALUES_EQUAL(lessOp.Name, "<"); + UNIT_ASSERT_VALUES_EQUAL(lessOp.LeftType, typeId); + UNIT_ASSERT_VALUES_EQUAL(lessOp.RightType, typeId); + UNIT_ASSERT_VALUES_EQUAL(lessOp.ProcId, lessProcId); + UNIT_ASSERT_VALUES_EQUAL(lessOp.ComId, greaterOp.OperId); + UNIT_ASSERT_VALUES_EQUAL(lessOp.NegateId, greaterOrEqualOp.OperId); + + UNIT_ASSERT_VALUES_EQUAL(lessOrEqualOp.Name, "<="); + UNIT_ASSERT_VALUES_EQUAL(lessOrEqualOp.LeftType, typeId); + UNIT_ASSERT_VALUES_EQUAL(lessOrEqualOp.RightType, typeId); + UNIT_ASSERT_VALUES_EQUAL(lessOrEqualOp.ProcId, lessOrEqualProcId); + UNIT_ASSERT_VALUES_EQUAL(lessOrEqualOp.ComId, greaterOrEqualOp.OperId); + UNIT_ASSERT_VALUES_EQUAL(lessOrEqualOp.NegateId, greaterOp.OperId); + + UNIT_ASSERT_VALUES_EQUAL(greaterOp.Name, ">"); + UNIT_ASSERT_VALUES_EQUAL(greaterOp.LeftType, typeId); + UNIT_ASSERT_VALUES_EQUAL(greaterOp.RightType, typeId); + UNIT_ASSERT_VALUES_EQUAL(greaterOp.ProcId, greaterProcId); + UNIT_ASSERT_VALUES_EQUAL(greaterOp.ComId, lessOp.OperId); + UNIT_ASSERT_VALUES_EQUAL(greaterOp.NegateId, lessOrEqualOp.OperId); + + UNIT_ASSERT_VALUES_EQUAL(greaterOrEqualOp.Name, ">="); + UNIT_ASSERT_VALUES_EQUAL(greaterOrEqualOp.LeftType, typeId); + UNIT_ASSERT_VALUES_EQUAL(greaterOrEqualOp.RightType, typeId); + UNIT_ASSERT_VALUES_EQUAL(greaterOrEqualOp.ProcId, greaterOrEqualProcId); + UNIT_ASSERT_VALUES_EQUAL(greaterOrEqualOp.ComId, lessOrEqualOp.OperId); + UNIT_ASSERT_VALUES_EQUAL(greaterOrEqualOp.NegateId, lessOp.OperId); + }; + + validate(); + auto exported = NPg::ExportExtensions(); + NPg::ClearExtensions(); + NPg::ImportExtensions(exported, true, nullptr); + validate(); + } + + Y_UNIT_TEST(Aggregates) { + NPg::ClearExtensions(); + if (NPg::AreAllFunctionsAllowed()) { + return; + } + + NPg::TExtensionDesc desc; + TTempFileHandle h; + TStringBuf sql = R"( + CREATE TYPE foo ( + alignment = double, + internallength = variable + ); + + CREATE OR REPLACE FUNCTION foo_agg_trans(internal, foo) + RETURNS internal + AS '$libdir/MyExt','foo_agg_trans' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OR REPLACE FUNCTION foo_agg_final(internal) + RETURNS foo + AS '$libdir/MyExt','foo_agg_final' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OR REPLACE FUNCTION foo_agg_combine(internal, internal) + RETURNS internal + AS '$libdir/MyExt','foo_agg_combine' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OR REPLACE FUNCTION foo_agg_serial(internal) + RETURNS bytea + AS '$libdir/MyExt','foo_agg_serial' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OR REPLACE FUNCTION foo_agg_deserial(bytea, internal) + RETURNS internal + AS '$libdir/MyExt','foo_agg_deserial' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE AGGREGATE foo_agg(foo) ( + sfunc = foo_agg_trans, + stype = internal, + serialfunc = foo_agg_serial, + deserialfunc = foo_agg_deserial, + combinefunc = foo_agg_combine, + finalfunc = foo_agg_final + ); + )"; + + h.Write(sql.data(), sql.size()); + desc.Name = "MyExt"; + desc.InstallName = "$libdir/MyExt"; + desc.SqlPaths.push_back(h.Name()); + NPg::RegisterExtensions({desc}, true, *NSQLTranslationPG::CreateExtensionSqlParser(), nullptr); + auto validate = [&]() { + auto typeId = NPg::LookupType("foo").TypeId; + auto internalTypeId = NPg::LookupType("internal").TypeId; + auto byteaTypeId = NPg::LookupType("bytea").TypeId; + const auto& desc = NPg::LookupAggregation("foo_agg", { typeId }); + UNIT_ASSERT_VALUES_EQUAL(desc.Name, "foo_agg"); + UNIT_ASSERT_VALUES_EQUAL(desc.ArgTypes, TVector<ui32>{typeId}); + UNIT_ASSERT_VALUES_EQUAL(desc.TransTypeId, internalTypeId); + UNIT_ASSERT_VALUES_EQUAL(desc.TransFuncId, NPg::LookupProc("foo_agg_trans", {internalTypeId, typeId}).ProcId); + UNIT_ASSERT_VALUES_EQUAL(desc.FinalFuncId, NPg::LookupProc("foo_agg_final", {internalTypeId}).ProcId); + UNIT_ASSERT_VALUES_EQUAL(desc.SerializeFuncId, NPg::LookupProc("foo_agg_serial", {internalTypeId}).ProcId); + UNIT_ASSERT_VALUES_EQUAL(desc.DeserializeFuncId, NPg::LookupProc("foo_agg_deserial", {byteaTypeId, internalTypeId}).ProcId); + UNIT_ASSERT_VALUES_EQUAL(desc.CombineFuncId, NPg::LookupProc("foo_agg_combine", {internalTypeId, internalTypeId}).ProcId); + }; + + validate(); + auto exported = NPg::ExportExtensions(); + NPg::ClearExtensions(); + NPg::ImportExtensions(exported, true, nullptr); + validate(); + } + + Y_UNIT_TEST(OpClasses) { + NPg::ClearExtensions(); + if (NPg::AreAllFunctionsAllowed()) { + return; + } + + NPg::TExtensionDesc desc; + TTempFileHandle h; + TStringBuf sql = R"( + CREATE TYPE foo ( + alignment = double, + internallength = variable + ); + + CREATE OR REPLACE FUNCTION foo_lt(foo, foo) + RETURNS bool + AS '$libdir/MyExt','foo_lt' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OR REPLACE FUNCTION foo_le(foo, foo) + RETURNS bool + AS '$libdir/MyExt','foo_le' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OR REPLACE FUNCTION foo_gt(foo, foo) + RETURNS bool + AS '$libdir/MyExt','foo_gt' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OR REPLACE FUNCTION foo_ge(foo, foo) + RETURNS bool + AS '$libdir/MyExt','foo_ge' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OR REPLACE FUNCTION foo_eq(foo, foo) + RETURNS bool + AS '$libdir/MyExt','foo_eq' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OR REPLACE FUNCTION foo_cmp(foo, foo) + RETURNS interger + AS '$libdir/MyExt','foo_cmp' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE; + + CREATE OPERATOR < ( + LEFTARG = foo, RIGHTARG = foo, PROCEDURE = foo_lt, + COMMUTATOR = '>', NEGATOR = '>=' + ); + + CREATE OPERATOR <= ( + LEFTARG = foo, RIGHTARG = foo, PROCEDURE = foo_le, + COMMUTATOR = '>=', NEGATOR = '>' + ); + + CREATE OPERATOR > ( + LEFTARG = foo, RIGHTARG = foo, PROCEDURE = foo_gt, + COMMUTATOR = '<', NEGATOR = '<=' + ); + + CREATE OPERATOR >= ( + LEFTARG = foo, RIGHTARG = foo, PROCEDURE = foo_ge, + COMMUTATOR = '<=', NEGATOR = '<' + ); + + CREATE OPERATOR = ( + LEFTARG = foo, RIGHTARG = foo, PROCEDURE = foo_eq + ); + + CREATE OPERATOR CLASS btree_foo_ops + DEFAULT FOR TYPE foo USING btree AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 foo_cmp (foo1 foo, foo2 foo); + + CREATE OR REPLACE FUNCTION foo_hash(foo) + RETURNS integer + AS '$libdir/MyExt','foo_hash' + LANGUAGE 'c' STRICT IMMUTABLE PARALLEL SAFE; + + CREATE OPERATOR CLASS hash_foo_ops + DEFAULT FOR TYPE foo USING hash AS + OPERATOR 1 = , + FUNCTION 1 foo_hash(foo); + )"; + + h.Write(sql.data(), sql.size()); + desc.Name = "MyExt"; + desc.InstallName = "$libdir/MyExt"; + desc.SqlPaths.push_back(h.Name()); + NPg::RegisterExtensions({desc}, true, *NSQLTranslationPG::CreateExtensionSqlParser(), nullptr); + auto validate = [&]() { + const auto& typeDesc = NPg::LookupType("foo"); + auto typeId = typeDesc.TypeId; + TVector<ui32> args { typeId, typeId }; + UNIT_ASSERT_VALUES_EQUAL(typeDesc.CompareProcId, NPg::LookupProc("foo_cmp", args).ProcId); + UNIT_ASSERT_VALUES_EQUAL(typeDesc.LessProcId, NPg::LookupProc("foo_lt", args).ProcId); + UNIT_ASSERT_VALUES_EQUAL(typeDesc.EqualProcId, NPg::LookupProc("foo_eq", args).ProcId); + UNIT_ASSERT_VALUES_EQUAL(typeDesc.HashProcId, NPg::LookupProc("foo_hash", {typeId}).ProcId); + + const auto& opClassBtree = *NPg::LookupDefaultOpClass(NPg::EOpClassMethod::Btree, typeId); + UNIT_ASSERT_VALUES_EQUAL(opClassBtree.Name, "btree_foo_ops"); + UNIT_ASSERT_VALUES_EQUAL(opClassBtree.Family, "btree/btree_foo_ops"); + UNIT_ASSERT_VALUES_EQUAL(NPg::LookupAmOp(opClassBtree.FamilyId, (ui32)NPg::EBtreeAmStrategy::Less, typeId, typeId).OperId, + NPg::LookupOper("<", args).OperId); + UNIT_ASSERT_VALUES_EQUAL(NPg::LookupAmProc(opClassBtree.FamilyId, (ui32)NPg::EBtreeAmProcNum::Compare, typeId, typeId).ProcId, + NPg::LookupProc("foo_cmp", args).ProcId); + + const auto& opClassHash = *NPg::LookupDefaultOpClass(NPg::EOpClassMethod::Hash, typeId); + UNIT_ASSERT_VALUES_EQUAL(opClassHash.Name, "hash_foo_ops"); + UNIT_ASSERT_VALUES_EQUAL(opClassHash.Family, "hash/hash_foo_ops"); + UNIT_ASSERT_VALUES_EQUAL(NPg::LookupAmProc(opClassHash.FamilyId, (ui32)NPg::EHashAmProcNum::Hash, typeId, typeId).ProcId, + NPg::LookupProc("foo_hash", {typeId}).ProcId); + }; + + validate(); + auto exported = NPg::ExportExtensions(); + NPg::ClearExtensions(); + NPg::ImportExtensions(exported, true, nullptr); + validate(); + } +} |