sql2yql.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. #include <yql/essentials/ast/yql_ast.h>
  2. #include <yql/essentials/ast/yql_ast_annotation.h>
  3. #include <yql/essentials/ast/yql_expr.h>
  4. #include <yql/essentials/parser/lexer_common/hints.h>
  5. #include <yql/essentials/sql/sql.h>
  6. #include <yql/essentials/sql/v1/sql.h>
  7. #include <yql/essentials/providers/common/provider/yql_provider_names.h>
  8. #include <yql/essentials/parser/pg_wrapper/interface/parser.h>
  9. #include <library/cpp/getopt/last_getopt.h>
  10. #include <yql/essentials/sql/v1/format/sql_format.h>
  11. #include <library/cpp/testing/unittest/registar.h>
  12. #include <util/stream/file.h>
  13. #include <util/generic/hash.h>
  14. #include <util/generic/hash_set.h>
  15. #include <util/generic/string.h>
  16. #include <util/string/escape.h>
  17. #include <google/protobuf/message.h>
  18. #include <google/protobuf/descriptor.h>
  19. #include <google/protobuf/repeated_field.h>
  20. struct TPosOutput {
  21. IOutputStream& Out;
  22. ui32 Line;
  23. ui32 Column;
  24. TPosOutput(IOutputStream& out)
  25. : Out(out)
  26. , Line(1)
  27. , Column(0)
  28. {
  29. }
  30. void Output(ui32 line, ui32 column, const TString& value) {
  31. while (Line < line) {
  32. Out << Endl;
  33. ++Line;
  34. Column = 0;
  35. }
  36. while (Column < column) {
  37. Out << " ";
  38. ++Column;
  39. }
  40. if (value != "<EOF>") {
  41. Out << value;
  42. Column += value.size();
  43. }
  44. }
  45. };
  46. static void ExtractQuery(TPosOutput& out, const google::protobuf::Message& node);
  47. static void VisitField(TPosOutput& out, const google::protobuf::FieldDescriptor& descr, const google::protobuf::Message& field) {
  48. using namespace google::protobuf;
  49. const Descriptor* d = descr.message_type();
  50. if (!d) {
  51. ythrow yexception() << "Invalid AST: non-message node encountered";
  52. }
  53. if (d->name() == "TToken") {
  54. const Reflection* r = field.GetReflection();
  55. out.Output(r->GetUInt32(field, d->field(0)), r->GetUInt32(field, d->field(1)), r->GetString(field, d->field(2)));
  56. } else {
  57. ExtractQuery(out, field);
  58. }
  59. }
  60. static void ExtractQuery(TPosOutput& out, const google::protobuf::Message& node) {
  61. using namespace google::protobuf;
  62. TVector<const FieldDescriptor*> fields;
  63. const Reflection* ref = node.GetReflection();
  64. ref->ListFields(node, &fields);
  65. for (auto it = fields.begin(); it != fields.end(); ++it) {
  66. if ((*it)->is_repeated()) {
  67. const ui32 fieldSize = ref->FieldSize(node, *it);
  68. for (ui32 i = 0; i < fieldSize; ++i) {
  69. VisitField(out, **it, ref->GetRepeatedMessage(node, *it, i));
  70. }
  71. } else {
  72. VisitField(out, **it, ref->GetMessage(node, *it));
  73. }
  74. }
  75. }
  76. bool TestFormat(
  77. const NSQLTranslation::TTranslators& translators,
  78. const TString& query,
  79. const NSQLTranslation::TTranslationSettings& settings,
  80. const TString& queryFile,
  81. const NYql::TAstParseResult& parseRes,
  82. const TString& outFileName,
  83. const bool checkDoubleFormatting
  84. ) {
  85. TStringStream yqlProgram;
  86. parseRes.Root->PrettyPrintTo(yqlProgram, NYql::TAstPrintFlags::PerLine | NYql::TAstPrintFlags::ShortQuote);
  87. TString frmQuery;
  88. NYql::TIssues issues;
  89. auto formatter = NSQLFormat::MakeSqlFormatter(settings);
  90. if (!formatter->Format(query, frmQuery, issues)) {
  91. Cerr << "Failed to format query: " << issues.ToString() << Endl;
  92. return false;
  93. }
  94. NYql::TAstParseResult frmParseRes = NSQLTranslation::SqlToYql(translators, frmQuery, settings);
  95. if (!frmParseRes.Issues.Empty()) {
  96. frmParseRes.Issues.PrintWithProgramTo(Cerr, queryFile, frmQuery);
  97. if (AnyOf(frmParseRes.Issues, [](const auto& issue) { return issue.GetSeverity() == NYql::TSeverityIds::S_ERROR;})) {
  98. return false;
  99. }
  100. }
  101. if (!frmParseRes.IsOk()) {
  102. Cerr << "No error reported, but no yql compiled result!" << Endl << Endl;
  103. return false;
  104. }
  105. TStringStream frmYqlProgram;
  106. frmParseRes.Root->PrettyPrintTo(frmYqlProgram, NYql::TAstPrintFlags::PerLine | NYql::TAstPrintFlags::ShortQuote);
  107. if (yqlProgram.Str() != frmYqlProgram.Str()) {
  108. Cerr << "source query's AST and formatted query's AST are not same\n";
  109. return false;
  110. }
  111. TString frmQuery2;
  112. if (!formatter->Format(frmQuery, frmQuery2, issues)) {
  113. Cerr << "Failed to format already formatted query: " << issues.ToString() << Endl;
  114. return false;
  115. }
  116. if (checkDoubleFormatting && frmQuery != frmQuery2) {
  117. Cerr << "Formatting an already formatted query yielded a different resut" << Endl
  118. << "Add /* skip double format */ to suppress" << Endl;
  119. return false;
  120. }
  121. if (!outFileName.empty()) {
  122. TFixedBufferFileOutput out{outFileName};
  123. out << frmQuery;
  124. }
  125. return true;
  126. }
  127. class TStoreMappingFunctor: public NLastGetopt::IOptHandler {
  128. public:
  129. TStoreMappingFunctor(THashMap<TString, TString>* target, char delim = '@')
  130. : Target(target)
  131. , Delim(delim)
  132. {
  133. }
  134. void HandleOpt(const NLastGetopt::TOptsParser* parser) final {
  135. const TStringBuf val(parser->CurValOrDef());
  136. const auto service = TString(val.After(Delim));
  137. auto res = Target->emplace(TString(val.Before(Delim)), service);
  138. if (!res.second) {
  139. /// force replace already exist parametr
  140. res.first->second = service;
  141. }
  142. }
  143. private:
  144. THashMap<TString, TString>* Target;
  145. char Delim;
  146. };
  147. int BuildAST(int argc, char* argv[]) {
  148. NLastGetopt::TOpts opts = NLastGetopt::TOpts::Default();
  149. TString outFileName;
  150. TString queryString;
  151. ui16 syntaxVersion;
  152. TString outFileNameFormat;
  153. THashMap<TString, TString> clusterMapping;
  154. clusterMapping["plato"] = NYql::YtProviderName;
  155. clusterMapping["pg_catalog"] = NYql::PgProviderName;
  156. clusterMapping["information_schema"] = NYql::PgProviderName;
  157. THashMap<TString, TString> tables;
  158. THashSet<TString> flags;
  159. opts.AddLongOption('o', "output", "save output to file").RequiredArgument("file").StoreResult(&outFileName);
  160. opts.AddLongOption('q', "query", "query string").RequiredArgument("query").StoreResult(&queryString);
  161. opts.AddLongOption('t', "tree", "print AST proto text").NoArgument();
  162. opts.AddLongOption('d', "diff", "print inlined diff for original query and query build from AST if they differ").NoArgument();
  163. opts.AddLongOption('D', "dump", "dump inlined diff for original query and query build from AST").NoArgument();
  164. opts.AddLongOption('p', "print-query", "print given query before parsing").NoArgument();
  165. opts.AddLongOption('y', "yql", "translate result to Yql and print it").NoArgument();
  166. opts.AddLongOption('l', "lexer", "print query token stream").NoArgument();
  167. opts.AddLongOption("ansi-lexer", "use ansi lexer").NoArgument();
  168. opts.AddLongOption("pg", "use pg_query parser").NoArgument();
  169. opts.AddLongOption('a', "ann", "print Yql annotations").NoArgument();
  170. opts.AddLongOption('C', "cluster", "set cluster to service mapping").RequiredArgument("name@service").Handler(new TStoreMappingFunctor(&clusterMapping));
  171. opts.AddLongOption('T', "table", "set table to filename mapping").RequiredArgument("table@path").Handler(new TStoreMappingFunctor(&tables));
  172. opts.AddLongOption('R', "replace", "replace Output table with each statement result").NoArgument();
  173. opts.AddLongOption("sqllogictest", "input files are in sqllogictest format").NoArgument();
  174. opts.AddLongOption("syntax-version", "SQL syntax version").StoreResult(&syntaxVersion).DefaultValue(1);
  175. opts.AddLongOption('F', "flags", "SQL pragma flags").SplitHandler(&flags, ',');
  176. opts.AddLongOption("assume-ydb-on-slash", "Assume YDB provider if cluster name starts with '/'").NoArgument();
  177. opts.AddLongOption("test-format", "compare formatted query's AST with the original query's AST (only syntaxVersion=1 is supported).").NoArgument();
  178. opts.AddLongOption("test-double-format", "check if formatting already formatted query produces the same result").NoArgument();
  179. opts.AddLongOption("test-antlr4", "check antlr4 parser").NoArgument();
  180. opts.AddLongOption("format-output", "Saves formatted query to it").RequiredArgument("format-output").StoreResult(&outFileNameFormat);
  181. opts.SetFreeArgDefaultTitle("query file");
  182. opts.AddHelpOption();
  183. NLastGetopt::TOptsParseResult res(&opts, argc, argv);
  184. TVector<TString> queryFiles(res.GetFreeArgs());
  185. THolder<TFixedBufferFileOutput> outFile;
  186. if (!outFileName.empty()) {
  187. outFile.Reset(new TFixedBufferFileOutput(outFileName));
  188. }
  189. IOutputStream& out = outFile ? *outFile.Get() : Cout;
  190. if (!res.Has("query") && queryFiles.empty()) {
  191. Cerr << "No --query nor query file was specified" << Endl << Endl;
  192. opts.PrintUsage(argv[0], Cerr);
  193. }
  194. NSQLTranslation::TTranslators translators(
  195. nullptr,
  196. NSQLTranslationV1::MakeTranslator(),
  197. NSQLTranslationPG::MakeTranslator()
  198. );
  199. TVector<TString> queries;
  200. int errors = 0;
  201. for (ui32 i = 0; i <= queryFiles.size(); ++i) {
  202. queries.clear();
  203. TString queryFile("query");
  204. if (i < queryFiles.size()) {
  205. queryFile = queryFiles[i];
  206. TAutoPtr<TFileInput> filePtr;
  207. if (queryFile != "-") {
  208. filePtr.Reset(new TFileInput(queryFile));
  209. }
  210. IInputStream& in = filePtr.Get() ? *filePtr : Cin;
  211. if (res.Has("sqllogictest")) {
  212. ui32 lineNum = 1;
  213. TString line;
  214. bool take = false;
  215. while (in.ReadLine(line)) {
  216. if (line.StartsWith("statement") || line.StartsWith("query")) {
  217. take = true;
  218. queries.emplace_back();
  219. queryFile = queryFiles[i] + " line " + ToString(lineNum + 1);
  220. } else if (line.StartsWith("----") || line.empty()) {
  221. take = false;
  222. } else if (take) {
  223. queries.back().append(line).append("\n");
  224. }
  225. ++lineNum;
  226. }
  227. } else {
  228. queries.push_back(in.ReadAll());
  229. }
  230. } else {
  231. queries.push_back(queryString);
  232. }
  233. for (const auto& query: queries) {
  234. if (query.empty()) {
  235. continue;
  236. }
  237. if (res.Has("print-query")) {
  238. out << query << Endl;
  239. }
  240. google::protobuf::Arena arena;
  241. NSQLTranslation::TTranslationSettings settings;
  242. settings.Arena = &arena;
  243. settings.ClusterMapping = clusterMapping;
  244. settings.Flags = flags;
  245. settings.SyntaxVersion = syntaxVersion;
  246. settings.AnsiLexer = res.Has("ansi-lexer");
  247. settings.WarnOnV0 = false;
  248. settings.V0ForceDisable = false;
  249. settings.AssumeYdbOnClusterWithSlash = res.Has("assume-ydb-on-slash");
  250. settings.TestAntlr4 = res.Has("test-antlr4");
  251. settings.EmitReadsForExists = true;
  252. if (res.Has("lexer")) {
  253. NYql::TIssues issues;
  254. auto lexer = NSQLTranslation::SqlLexer(translators, query, issues, settings);
  255. NSQLTranslation::TParsedTokenList tokens;
  256. if (lexer && NSQLTranslation::Tokenize(*lexer, query, queryFile, tokens, issues, NSQLTranslation::SQL_MAX_PARSER_ERRORS)) {
  257. for (auto& token : tokens) {
  258. out << token.Line << ":" << token.LinePos << "\t\t" << token.Name << "(" << EscapeC(token.Content) << ")\n";
  259. }
  260. }
  261. if (!issues.Empty()) {
  262. issues.PrintTo(Cerr);
  263. }
  264. bool hasError = AnyOf(issues, [](const auto& issue) { return issue.GetSeverity() == NYql::TSeverityIds::S_ERROR;});
  265. if (hasError) {
  266. ++errors;
  267. }
  268. continue;
  269. }
  270. NYql::TAstParseResult parseRes;
  271. if (res.Has("pg")) {
  272. parseRes = NSQLTranslationPG::PGToYql(query, settings);
  273. } else {
  274. if (res.Has("tree") || res.Has("diff") || res.Has("dump")) {
  275. google::protobuf::Message* ast(NSQLTranslation::SqlAST(translators, query, queryFile, parseRes.Issues,
  276. NSQLTranslation::SQL_MAX_PARSER_ERRORS, settings));
  277. if (ast) {
  278. if (res.Has("tree")) {
  279. out << ast->DebugString() << Endl;
  280. }
  281. if (res.Has("diff") || res.Has("dump")) {
  282. TStringStream result;
  283. TPosOutput posOut(result);
  284. ExtractQuery(posOut, *ast);
  285. if (res.Has("dump") || query != result.Str()) {
  286. out << NUnitTest::ColoredDiff(query, result.Str()) << Endl;
  287. }
  288. }
  289. NSQLTranslation::TSQLHints hints;
  290. auto lexer = SqlLexer(translators, query, parseRes.Issues, settings);
  291. if (lexer && CollectSqlHints(*lexer, query, queryFile, settings.File, hints, parseRes.Issues,
  292. settings.MaxErrors, settings.Antlr4Parser)) {
  293. parseRes = NSQLTranslation::SqlASTToYql(translators, query, *ast, hints, settings);
  294. }
  295. }
  296. } else {
  297. parseRes = NSQLTranslation::SqlToYql(translators, query, settings);
  298. }
  299. }
  300. if (parseRes.Root) {
  301. TStringStream yqlProgram;
  302. parseRes.Root->PrettyPrintTo(yqlProgram, NYql::TAstPrintFlags::PerLine | NYql::TAstPrintFlags::ShortQuote);
  303. if (res.Has("yql")) {
  304. out << yqlProgram.Str();
  305. }
  306. if (res.Has("ann")) {
  307. TMemoryPool pool(1024);
  308. NYql::AnnotatePositions(*parseRes.Root, pool)->PrettyPrintTo(out, NYql::TAstPrintFlags::PerLine);
  309. }
  310. }
  311. bool hasError = false;
  312. if (!parseRes.Issues.Empty()) {
  313. parseRes.Issues.PrintWithProgramTo(Cerr, queryFile, query);
  314. hasError = AnyOf(parseRes.Issues, [](const auto& issue) { return issue.GetSeverity() == NYql::TSeverityIds::S_ERROR;});
  315. }
  316. if (!parseRes.IsOk() && !hasError) {
  317. hasError = true;
  318. Cerr << "No error reported, but no yql compiled result!" << Endl << Endl;
  319. }
  320. if (res.Has("test-format") && syntaxVersion == 1 && !hasError && parseRes.Root) {
  321. hasError = !TestFormat(translators, query, settings, queryFile, parseRes, outFileNameFormat, res.Has("test-double-format"));
  322. }
  323. if (hasError) {
  324. ++errors;
  325. }
  326. }
  327. }
  328. return errors;
  329. }
  330. int main(int argc, char* argv[]) {
  331. try {
  332. return BuildAST(argc, argv);
  333. } catch (const yexception& e) {
  334. Cerr << "Caught exception:" << e.what() << Endl;
  335. return 1;
  336. } catch (...) {
  337. Cerr << "Caught exception" << Endl;
  338. return 1;
  339. }
  340. return 0;
  341. }