pg_sql_autoparam_ut.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. #include "ut/util.h"
  2. #include <library/cpp/testing/unittest/registar.h>
  3. using namespace NSQLTranslation;
  4. Y_UNIT_TEST_SUITE(PgSqlParsingAutoparam) {
  5. Y_UNIT_TEST(AutoParamValues_DisabledByDefault) {
  6. auto res = PgSqlToYql("insert into plato.Output values (1,2,3), (1,2,3)");
  7. UNIT_ASSERT_C(res.Issues.Empty(), "Failed to parse statement, issues: " + res.Issues.ToString());
  8. auto params = dynamic_cast<TTestAutoParamBuilder*>(res.PgAutoParamValues.Get());
  9. UNIT_ASSERT_C(!params, "Expected no auto parametrization");
  10. }
  11. Y_UNIT_TEST(AutoParamValues_NoParametersWithDefaults) {
  12. TTranslationSettings settings;
  13. settings.AutoParametrizeEnabled = true;
  14. settings.AutoParametrizeValuesStmt = true;
  15. auto res = SqlToYqlWithMode(
  16. R"(CREATE TABLE t (a int PRIMARY KEY, b int DEFAULT 0))",
  17. NSQLTranslation::ESqlMode::QUERY,
  18. 10,
  19. {},
  20. EDebugOutput::None,
  21. false,
  22. settings);
  23. UNIT_ASSERT_C(res.Issues.Empty(), "Failed to parse statement, issues: " + res.Issues.ToString());
  24. auto params = dynamic_cast<TTestAutoParamBuilder*>(res.PgAutoParamValues.Get());
  25. UNIT_ASSERT_C(!params, "Expected no auto parametrization");
  26. }
  27. using TUsedParamsGetter = std::function<void(TSet<TString>&, const NYql::TAstNode& node)>;
  28. void GetUsedParamsInValues(TSet<TString>& usedParams, const NYql::TAstNode& node) {
  29. const bool isPgSetItem =
  30. node.IsListOfSize(2) && node.GetChild(0)->IsAtom()
  31. && node.GetChild(0)->GetContent() == "PgSetItem";
  32. if (!isPgSetItem) {
  33. return;
  34. }
  35. const auto pgSetItemOptions = node.GetChild(1)->GetChild(1);
  36. for (const auto* pgOption : pgSetItemOptions->GetChildren()) {
  37. const bool isQuotedList =
  38. pgOption->IsListOfSize(2) && pgOption->GetChild(0)->IsAtom()
  39. && pgOption->GetChild(0)->GetContent() == "quote";
  40. if (!isQuotedList) {
  41. continue;
  42. }
  43. const auto* option = pgOption->GetChild(1);
  44. const auto* optionName = option->GetChild(0);
  45. const bool isValuesNode =
  46. optionName->IsListOfSize(2) && optionName->GetChild(0)->IsAtom()
  47. && optionName->GetChild(0)->GetContent() == "quote"
  48. && optionName->GetChild(1)->GetContent() == "values";
  49. if (!isValuesNode) {
  50. continue;
  51. }
  52. const auto values = option->GetChild(2);
  53. if (values->IsAtom()) {
  54. usedParams.insert(TString(values->GetContent()));
  55. }
  56. }
  57. }
  58. void TestAutoParam(const TString& query, const THashMap<TString, TString>& expectedParamNameToValue, const TMap<TString, TString>& expectedParamTypes, TUsedParamsGetter usedParamsGetter) {
  59. TTranslationSettings settings;
  60. settings.AutoParametrizeEnabled = true;
  61. settings.AutoParametrizeValuesStmt = true;
  62. auto res = SqlToYqlWithMode(
  63. query,
  64. NSQLTranslation::ESqlMode::QUERY,
  65. 10,
  66. {},
  67. EDebugOutput::None,
  68. false,
  69. settings);
  70. UNIT_ASSERT_C(res.Issues.Empty(), "Failed to parse statement, issues: " + res.Issues.ToString());
  71. auto params = dynamic_cast<TTestAutoParamBuilder*>(res.PgAutoParamValues.Get());
  72. UNIT_ASSERT_C(params && params->Size() > 0, "Expected auto param values");
  73. TSet<TString> declaredParams;
  74. TMap<TString, TString> actualParamTypes;
  75. VisitAstNodes(*res.Root, [&declaredParams, &actualParamTypes] (const NYql::TAstNode& node) {
  76. const bool isDeclareNode =
  77. node.IsList() && node.GetChildrenCount() > 0
  78. && node.GetChild(0)->IsAtom()
  79. && node.GetChild(0)->GetContent() == "declare";
  80. if (isDeclareNode) {
  81. UNIT_ASSERT_VALUES_EQUAL(node.GetChildrenCount(), 3);
  82. const auto name = TString(node.GetChild(1)->GetContent());
  83. declaredParams.insert(name);
  84. actualParamTypes[name] = node.GetChild(2)->ToString();
  85. }
  86. });
  87. UNIT_ASSERT_VALUES_EQUAL(expectedParamTypes, actualParamTypes);
  88. TSet<TString> usedParams;
  89. VisitAstNodes(*res.Root, [&usedParams, &usedParamsGetter] (const auto& node) { return usedParamsGetter(usedParams, node); });
  90. UNIT_ASSERT_VALUES_EQUAL(declaredParams, usedParams);
  91. TSet<TString> expectedParams;
  92. for (auto& [expectedParamName, val] : expectedParamNameToValue) {
  93. UNIT_ASSERT_C(params->State.contains(expectedParamName),
  94. "Autoparametrized values do not contain expected param: " << expectedParamName);
  95. UNIT_ASSERT_STRINGS_EQUAL(val, params->GetParamValue(expectedParamName));
  96. UNIT_ASSERT_C(declaredParams.contains(expectedParamName),
  97. "Declared params don't contain expected param name: " << expectedParamName);
  98. expectedParams.insert(expectedParamName);
  99. }
  100. UNIT_ASSERT_VALUES_EQUAL(declaredParams.size(), expectedParams.size());
  101. }
  102. Y_UNIT_TEST(AutoParamValues_Int4) {
  103. TString query = R"(insert into plato.Output values (1,2), (3,4), (4,5))";
  104. TString expectedParam = "[int4,int4:1,2,3,4,4,5]";
  105. TString type = "(ListType (TupleType (PgType 'int4) (PgType 'int4)))";
  106. TestAutoParam(query, {{"a0", expectedParam}}, {{"a0", type}}, GetUsedParamsInValues);
  107. }
  108. Y_UNIT_TEST(AutoParamValues_Int4Text) {
  109. TString query = R"(insert into plato.Output values (1,'2'), (3,'4'))";
  110. TString expectedParam = "[int4,unknown:1,2,3,4]";
  111. TString type = "(ListType (TupleType (PgType 'int4) (PgType 'unknown)))";
  112. TestAutoParam(query, {{"a0", expectedParam}}, {{"a0", type}}, GetUsedParamsInValues);
  113. }
  114. Y_UNIT_TEST(AutoParamValues_MultipleStmts) {
  115. TString query = R"(
  116. insert into plato.Output values (1,'2'), (3,'4');
  117. insert into plato.Output1 values (1.23);
  118. )";
  119. TString expectedParam0 = "[int4,unknown:1,2,3,4]";
  120. TString expectedParam1 = "[numeric:1.23]";
  121. TMap<TString, TString> expectedParamTypes {
  122. {"a0", "(ListType (TupleType (PgType 'int4) (PgType 'unknown)))"},
  123. {"a1", "(ListType (TupleType (PgType 'numeric)))"}
  124. };
  125. TestAutoParam(query, {{"a0", expectedParam0}, {"a1", expectedParam1}}, expectedParamTypes, GetUsedParamsInValues);
  126. }
  127. Y_UNIT_TEST(AutoParamValues_WithNull) {
  128. TString query = R"(
  129. insert into plato.Output values (null, '2'), (3, '4')
  130. )";
  131. TString expectedParam = "[int4,unknown:#,2,3,4]";
  132. TString type = "(ListType (TupleType (PgType 'int4) (PgType 'unknown)))";
  133. TestAutoParam(query, {{"a0", expectedParam}}, {{"a0", type}}, GetUsedParamsInValues);
  134. }
  135. Y_UNIT_TEST(AutoParamValues_NullCol) {
  136. TString query = R"(
  137. insert into plato.Output values (null,1), (null,1)
  138. )";
  139. TString expectedParam = "[unknown,int4:#,1,#,1]";
  140. TString type = "(ListType (TupleType (PgType 'unknown) (PgType 'int4)))";
  141. TestAutoParam(query, {{"a0", expectedParam}}, {{"a0", type}}, GetUsedParamsInValues);
  142. }
  143. Y_UNIT_TEST(AutoParamConsts_Where) {
  144. TString query = R"(
  145. select * from plato.Output where key > 1
  146. )";
  147. TString expectedParam = "int4:1";
  148. // We expect: (PgOp '">" (PgColumnRef '"key") a0)
  149. const TUsedParamsGetter usedInWhereComp = [] (TSet<TString>& usedParams, const NYql::TAstNode& node) {
  150. const auto maybeQuote = MaybeGetQuotedValue(node);
  151. if (!maybeQuote) {
  152. return;
  153. }
  154. const auto quotedVal = maybeQuote.GetRef();
  155. const bool isWhere =
  156. quotedVal->IsListOfSize(2) && quotedVal->GetChild(1)->IsListOfSize(3)
  157. && quotedVal->GetChild(1)->IsListOfSize(3)
  158. && quotedVal->GetChild(1)->GetChild(0)->IsAtom()
  159. && quotedVal->GetChild(1)->GetChild(0)->GetContent() == "PgWhere";
  160. if (!isWhere) {
  161. return;
  162. }
  163. const auto* whereCallable = quotedVal->GetChild(1);
  164. const auto* whereLambda = whereCallable->GetChild(2);
  165. const auto* pgOp = whereLambda->GetChild(2);
  166. const bool isBinaryOp = pgOp->IsListOfSize(4);
  167. if (!isBinaryOp) {
  168. return;
  169. }
  170. const auto* pgBinOpSecondArg = pgOp->GetChild(3);
  171. usedParams.insert(TString(pgBinOpSecondArg->GetContent()));
  172. };
  173. TString type = "(PgType 'int4)";
  174. TestAutoParam(query, {{"a0", expectedParam}}, {{"a0", type}}, usedInWhereComp);
  175. }
  176. Y_UNIT_TEST(AutoParamConsts_Select) {
  177. TString query = R"(
  178. select 1, 'test', B'10001'
  179. )";
  180. TString expectedParamInt4 = "int4:1";
  181. TString expectedParamText = "unknown:test";
  182. TString expectedParamBit = "bit:b10001";
  183. const TUsedParamsGetter dummyGetter = [] (TSet<TString>& usedParams, const NYql::TAstNode&) {
  184. usedParams = {"a0", "a1", "a2"};
  185. };
  186. TMap<TString, TString> expectedParamTypes {
  187. {"a0", "(PgType 'int4)"},
  188. {"a1", "(PgType 'unknown)"},
  189. {"a2", "(PgType 'bit)"},
  190. };
  191. TestAutoParam(query, {
  192. {"a0", expectedParamInt4},
  193. {"a1", expectedParamText},
  194. {"a2", expectedParamBit},
  195. }, expectedParamTypes, dummyGetter);
  196. }
  197. Y_UNIT_TEST(AutoParamValues_FailToInferColumnType) {
  198. const auto query = R"(INSERT INTO test VALUES (1), ('2');)";
  199. TMap<TString, TString> paramToType = {{"a0", "(PgType 'int4)"}, {"a1", "(PgType 'unknown)"}};
  200. TString expectedParamInt4 = "int4:1";
  201. TString expectedParamText = "unknown:2";
  202. const TUsedParamsGetter dummyGetter = [] (TSet<TString>& usedParams, const NYql::TAstNode&) {
  203. usedParams = {"a0", "a1"};
  204. };
  205. TestAutoParam(query, {{"a0", expectedParamInt4}, {"a1", expectedParamText}}, paramToType, dummyGetter);
  206. }
  207. }