1 #include "sqlenterpriseformatter.h"
2 #include "formatstatement.h"
3 #include "common/unused.h"
4 #include "common/global.h"
5 #include <QDebug>
6 #include <parser/lexer.h>
7 #include <parser/parser.h>
8 
SqlEnterpriseFormatter()9 SqlEnterpriseFormatter::SqlEnterpriseFormatter()
10 {
11 }
12 
format(SqliteQueryPtr query)13 QString SqlEnterpriseFormatter::format(SqliteQueryPtr query)
14 {
15     QList<Comment*> comments = collectComments(query->tokens);
16 
17     int wrapperIdx = cfg.SqlEnterpriseFormatter.Wrappers.get().indexOf(cfg.SqlEnterpriseFormatter.PrefferedWrapper.get());
18 
19     NameWrapper wrapper = getAllNameWrappers()[wrapperIdx];
20 
21     FormatStatement *formatStmt = FormatStatement::forQuery(query.data());
22     if (!formatStmt)
23         return query->detokenize();
24 
25     formatStmt->setSelectedWrapper(wrapper);
26     formatStmt->setConfig(&cfg);
27     QString formatted = formatStmt->format();
28     delete formatStmt;
29 
30     QString formattedWithComments = applyComments(formatted, comments);
31     for (Comment* c : comments)
32         delete c;
33 
34     return formattedWithComments;
35 }
36 
init()37 bool SqlEnterpriseFormatter::init()
38 {
39     Q_INIT_RESOURCE(sqlenterpriseformatter);
40 
41     static_qstring(query1, "SELECT (2 + 4) AND (3 + 5), 4 NOT IN (SELECT t1.'some[_]name' + t2.[some'name2] FROM xyz t1 JOIN zxc t2 ON (t1.aaa = t2.aaa)) "
42                            "FROM a, (SELECT id FROM table2);");
43     static_qstring(query2, "INSERT INTO table1 (id, value1, value2) VALUES (1, (2 + 5), (SELECT id FROM table2));");
44     static_qstring(query3, "CREATE TABLE tab (id INTEGER PRIMARY KEY, /*a primary key column*/ value1 VARCHAR(6), "
45                            "value2 /*column with constraints*/ NUMBER(8,2) NOT NULL DEFAULT 1.0"
46                            ");");
47     static_qstring(query4, "CREATE UNIQUE INDEX IF NOT EXISTS dbName.idx1 ON [messages column] (id COLLATE x ASC, lang DESC, description);");
48 
49     Parser parser;
50 
51     for (const QString& q : {query1, query2, query3, query4})
52     {
53         if (!parser.parse(q))
54         {
55             qWarning() << "SqlEnterpriseFormatter preview query parsing error:" << parser.getErrorString();
56             continue;
57         }
58         previewQueries << parser.getQueries().first();
59     }
60 
61     updatePreview();
62 
63     return GenericPlugin::init();
64 }
65 
deinit()66 void SqlEnterpriseFormatter::deinit()
67 {
68     Q_CLEANUP_RESOURCE(sqlenterpriseformatter);
69 }
70 
71 
updatePreview()72 void SqlEnterpriseFormatter::updatePreview()
73 {
74     QStringList output;
75     for (const SqliteQueryPtr& q : previewQueries)
76         output << format(q);
77 
78     cfg.SqlEnterpriseFormatter.PreviewCode.set(output.join("\n\n"));
79 }
80 
configModified(CfgEntry * entry)81 void SqlEnterpriseFormatter::configModified(CfgEntry* entry)
82 {
83     if (entry == &cfg.SqlEnterpriseFormatter.PreviewCode)
84         return;
85 
86     updatePreview();
87 }
88 
getNameWrapperStr(NameWrapper wrapper)89 QString Cfg::getNameWrapperStr(NameWrapper wrapper)
90 {
91     return wrapObjName(QObject::tr("name", "example name wrapper"), wrapper);
92 }
93 
getNameWrapperStrings()94 QStringList Cfg::getNameWrapperStrings()
95 {
96     QStringList strings;
97     for (NameWrapper nw : getAllNameWrappers())
98         strings << wrapObjName(QObject::tr("name", "example name wrapper"), nw);
99 
100     return strings;
101 }
102 
getMainUiConfig()103 CfgMain* SqlEnterpriseFormatter::getMainUiConfig()
104 {
105     return &cfg;
106 }
107 
getConfigUiForm() const108 QString SqlEnterpriseFormatter::getConfigUiForm() const
109 {
110     return "SqlEnterpriseFormatter";
111 }
112 
configDialogOpen()113 void SqlEnterpriseFormatter::configDialogOpen()
114 {
115     connect(&cfg.SqlEnterpriseFormatter, SIGNAL(changed(CfgEntry*)), this, SLOT(configModified(CfgEntry*)));
116 }
117 
configDialogClosed()118 void SqlEnterpriseFormatter::configDialogClosed()
119 {
120     disconnect(&cfg.SqlEnterpriseFormatter, SIGNAL(changed(CfgEntry*)), this, SLOT(configModified(CfgEntry*)));
121 }
122 
collectComments(const TokenList & tokens)123 QList<SqlEnterpriseFormatter::Comment *> SqlEnterpriseFormatter::collectComments(const TokenList &tokens)
124 {
125     QList<Comment*> results;
126 
127     QList<TokenList> tokensInLines = tokensByLines(tokens);
128     Comment* prevCommentInThisLine = nullptr;
129     Comment* cmt = nullptr;
130     bool tokensBefore = false;
131     int pos = 0;
132     int line = 0;
133     for (const TokenList& tokensInLine : tokensInLines)
134     {
135         tokensBefore = true;
136         prevCommentInThisLine = nullptr;
137         for (const TokenPtr& token : tokensInLine)
138         {
139             if (token->type == Token::Type::SPACE)
140                 continue;
141 
142             if (prevCommentInThisLine)
143                 prevCommentInThisLine->tokensAfter = true;
144 
145             if (token->type == Token::Type::COMMENT)
146             {
147                 cmt = new Comment;
148                 cmt->tokensBefore = tokensBefore;
149                 cmt->position = pos;
150                 cmt->multiline = token->value.startsWith("/*");
151                 if (cmt->multiline)
152                     cmt->contents = token->value.mid(2, token->value.length() - 4).trimmed();
153                 else
154                     cmt->contents = token->value.mid(2).trimmed();
155 
156                 results << cmt;
157                 prevCommentInThisLine = cmt;
158                 continue;
159             }
160 
161             tokensBefore = true;
162             pos++;
163         }
164         line++;
165     }
166 
167     return results;
168 }
169 
tokensByLines(const TokenList & tokens,bool includeSpaces)170 QList<TokenList> SqlEnterpriseFormatter::tokensByLines(const TokenList &tokens, bool includeSpaces)
171 {
172     QList<TokenList> tokensInLines;
173     TokenList tokensInLine;
174     for (const TokenPtr& token : tokens)
175     {
176         if (includeSpaces || token->type != Token::Type::SPACE)
177             tokensInLine << token;
178 
179         if (token->type == Token::Type::SPACE && token->value.contains('\n'))
180         {
181             tokensInLines << tokensInLine;
182             tokensInLine.clear();
183         }
184     }
185     if (tokensInLine.size() > 0)
186         tokensInLines << tokensInLine;
187 
188     return tokensInLines;
189 }
190 
adjustCommentsToEnd(const TokenList & inputTokens)191 TokenList SqlEnterpriseFormatter::adjustCommentsToEnd(const TokenList &inputTokens)
192 {
193     QList<TokenList> tokensInLines = tokensByLines(inputTokens, true);
194     TokenList newTokens;
195     TokenList commentTokensForLine;
196     TokenPtr newLineToken;
197     for (const TokenList& tokensInLine : tokensInLines)
198     {
199         commentTokensForLine.clear();
200         newLineToken.clear();
201         for (const TokenPtr& token : tokensInLine)
202         {
203             if (token->type == Token::Type::COMMENT)
204             {
205                 wrapComment(token, true);
206                 //token->value = " " + endLineCommentTpl.arg(token->value);
207                 commentTokensForLine << token;
208             }
209             else if (token->type == Token::Type::SPACE && token->value.contains("\n"))
210                 newLineToken = token;
211             else
212                 newTokens << token;
213         }
214 
215         newTokens += commentTokensForLine;
216         if (newLineToken)
217             newTokens << newLineToken;
218     }
219     return newTokens;
220 }
221 
wrapOnlyComments(const TokenList & inputTokens)222 TokenList SqlEnterpriseFormatter::wrapOnlyComments(const TokenList &inputTokens)
223 {
224     QList<TokenList> tokensInLines = tokensByLines(inputTokens, true);
225     TokenList newTokens;
226     bool lineEnd = true;
227     for (const TokenList& tokensInLine : reverse(tokensInLines))
228     {
229         lineEnd = true;
230         for (const TokenPtr& token : reverse(tokensInLine))
231         {
232             if (!token->isWhitespace())
233                 lineEnd = false;
234 
235             if (token->type == Token::Type::COMMENT)
236                 wrapComment(token, lineEnd);
237 
238             newTokens << token;
239         }
240     }
241     return reverse(newTokens);
242 }
243 
optimizeInnerComments(const TokenList & inputTokens)244 TokenList SqlEnterpriseFormatter::optimizeInnerComments(const TokenList &inputTokens)
245 {
246     // TODO
247     return inputTokens;
248 }
249 
optimizeEndLineComments(const TokenList & inputTokens)250 TokenList SqlEnterpriseFormatter::optimizeEndLineComments(const TokenList &inputTokens)
251 {
252     // TODO
253     return inputTokens;
254 }
255 
indentMultiLineComments(const TokenList & inputTokens)256 void SqlEnterpriseFormatter::indentMultiLineComments(const TokenList &inputTokens)
257 {
258     UNUSED(inputTokens);
259     // TODO
260 }
261 
wrapComment(const TokenPtr & token,bool isAtLineEnd)262 void SqlEnterpriseFormatter::wrapComment(const TokenPtr &token, bool isAtLineEnd)
263 {
264     static_qstring(multiCommentTpl, "/* %1 */");
265     static_qstring(endLineCommentTpl, "-- %1");
266 
267     bool isMultiLine = token->value.contains("\n");
268     if (isAtLineEnd && !isMultiLine && cfg.SqlEnterpriseFormatter.PreferredCommentMarker.get() == "--")
269         token->value = endLineCommentTpl.arg(token->value);
270     else
271         token->value = multiCommentTpl.arg(token->value);
272 }
273 
applyComments(const QString & formatted,QList<SqlEnterpriseFormatter::Comment * > comments)274 QString SqlEnterpriseFormatter::applyComments(const QString& formatted, QList<SqlEnterpriseFormatter::Comment*> comments)
275 {
276     if (comments.size() == 0)
277         return formatted;
278 
279     int currentCommentPosition = comments.first()->position;
280 
281     TokenList allTokens = Lexer::tokenize(formatted);
282     TokenList newTokens;
283     int currentTokenPosition = 0;
284     for (const TokenPtr& token : allTokens)
285     {
286         if (currentTokenPosition == currentCommentPosition)
287         {
288             newTokens << TokenPtr::create(Token::Type::COMMENT, comments.first()->contents);
289             comments.removeFirst();
290             if (comments.size() > 0)
291                 currentCommentPosition = comments.first()->position;
292             else
293                 currentCommentPosition = -1;
294         }
295 
296         newTokens << token;
297         if (token->type != Token::Type::SPACE)
298             currentTokenPosition++;
299     }
300 
301     // Any remaining comments
302     for (Comment* cmt : comments)
303         newTokens << TokenPtr::create(Token::Type::COMMENT, cmt->contents);
304 
305     if (cfg.SqlEnterpriseFormatter.MoveAllCommentsToLineEnd.get())
306         newTokens = adjustCommentsToEnd(newTokens);
307     else
308         newTokens = wrapOnlyComments(newTokens);
309 
310     newTokens = optimizeInnerComments(newTokens);
311     newTokens = optimizeEndLineComments(newTokens);
312     indentMultiLineComments(newTokens);
313 
314     return newTokens.detokenize();
315 }
316