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