1 #include "common/utils_sql.h"
2 #include "common/utils.h"
3 #include "db/sqlquery.h"
4 #include "parser/token.h"
5 #include "parser/lexer.h"
6 #include "parser/keywords.h"
7 #include "log.h"
8 #include <QHash>
9 #include <QPair>
10 #include <QString>
11 #include <QDebug>
12 #include <QMetaType>
13
14 QString invalidIdCharacters = "[]()\"'@*.,+-=/%&|:; \t\n<>";
15 QHash<NameWrapper,QPair<QChar,QChar>> wrapperChars;
16 QHash<NameWrapper,QPair<QChar,bool>> wrapperEscapedEnding;
17 QList<NameWrapper> sqlite3Wrappers;
18
initUtilsSql()19 void initUtilsSql()
20 {
21 wrapperChars[NameWrapper::BRACKET] = QPair<QChar,QChar>('[', ']');
22 wrapperChars[NameWrapper::QUOTE] = QPair<QChar,QChar>('\'', '\'');
23 wrapperChars[NameWrapper::BACK_QUOTE] = QPair<QChar,QChar>('`', '`');
24 wrapperChars[NameWrapper::DOUBLE_QUOTE] = QPair<QChar,QChar>('"', '"');
25
26 wrapperEscapedEnding[NameWrapper::BRACKET] = QPair<QChar,bool>(']', false);
27 wrapperEscapedEnding[NameWrapper::QUOTE] = QPair<QChar,bool>('\'', true);
28 wrapperEscapedEnding[NameWrapper::BACK_QUOTE] = QPair<QChar,bool>('`', true);
29 wrapperEscapedEnding[NameWrapper::DOUBLE_QUOTE] = QPair<QChar,bool>('"', true);
30
31 sqlite3Wrappers << NameWrapper::DOUBLE_QUOTE
32 << NameWrapper::BRACKET
33 << NameWrapper::QUOTE
34 << NameWrapper::BACK_QUOTE;
35
36 qRegisterMetaType<SqlQueryPtr>("SqlQueryPtr");
37 }
38
doesObjectNeedWrapping(const QString & str)39 bool doesObjectNeedWrapping(const QString& str)
40 {
41 if (str.isEmpty())
42 return true;
43
44 if (isObjWrapped(str))
45 return false;
46
47 if (isKeyword(str))
48 return true;
49
50 for (int i = 0; i < str.size(); i++)
51 if (doesObjectNeedWrapping(str[i]))
52 return true;
53
54 if (str[0].isDigit())
55 return true;
56
57 return false;
58 }
59
doesObjectNeedWrapping(const QChar & c)60 bool doesObjectNeedWrapping(const QChar& c)
61 {
62 return invalidIdCharacters.indexOf(c) >= 0;
63 }
64
isObjectWrapped(const QChar & c)65 bool isObjectWrapped(const QChar& c)
66 {
67 return !doesObjectNeedWrapping(c);
68 }
69
wrapObjIfNeeded(const QString & obj,NameWrapper favWrapper)70 QString wrapObjIfNeeded(const QString& obj, NameWrapper favWrapper)
71 {
72 if (doesObjectNeedWrapping(obj))
73 return wrapObjName(obj, favWrapper);
74
75 return obj;
76 }
77
wrapObjIfNeeded(const QString & obj,bool useDoubleQuoteForEmptyValue,NameWrapper favWrapper)78 QString wrapObjIfNeeded(const QString& obj, bool useDoubleQuoteForEmptyValue, NameWrapper favWrapper)
79 {
80 return wrapObjIfNeeded(obj, ((useDoubleQuoteForEmptyValue && obj.isEmpty()) ? NameWrapper::DOUBLE_QUOTE : favWrapper));
81 }
82
wrapObjName(const QString & obj,bool useDoubleQuoteForEmptyValue,NameWrapper favWrapper)83 QString wrapObjName(const QString& obj, bool useDoubleQuoteForEmptyValue, NameWrapper favWrapper)
84 {
85 return wrapObjName(obj, ((useDoubleQuoteForEmptyValue && obj.isEmpty()) ? NameWrapper::DOUBLE_QUOTE : favWrapper));
86 }
87
wrapObjName(const QString & obj,NameWrapper favWrapper)88 QString wrapObjName(const QString& obj, NameWrapper favWrapper)
89 {
90 QString result = obj;
91 if (result.isNull())
92 result = "";
93
94 QPair<QChar,QChar> wrapChars = getQuoteCharacter(result, favWrapper);
95
96 if (wrapChars.first.isNull() || wrapChars.second.isNull())
97 {
98 qDebug() << "No quote character possible for object name: " << result;
99 return result;
100 }
101 result.prepend(wrapChars.first);
102 result.append(wrapChars.second);
103 return result;
104 }
105
getQuoteCharacter(QString & obj,NameWrapper favWrapper)106 QPair<QChar,QChar> getQuoteCharacter(QString& obj, NameWrapper favWrapper)
107 {
108 QList<NameWrapper> wrappers = sqlite3Wrappers;
109
110 // Move favourite wrapper to front of list
111 if (wrappers.contains(favWrapper))
112 {
113 wrappers.removeOne(favWrapper);
114 wrappers.insert(0, favWrapper);
115 }
116
117 QPair<QChar,QChar> wrapChars;
118 for (NameWrapper wrapper : wrappers)
119 {
120 wrapChars = wrapperChars[wrapper];
121 if (obj.indexOf(wrapChars.first) > -1)
122 continue;
123
124 if (obj.indexOf(wrapChars.second) > -1)
125 continue;
126
127 return wrapChars;
128 }
129
130 return QPair<QChar,QChar>();
131 }
132
wrapObjNames(const QList<QString> & objList,NameWrapper favWrapper)133 QList<QString> wrapObjNames(const QList<QString>& objList, NameWrapper favWrapper)
134 {
135 QList<QString> results;
136 for (int i = 0; i < objList.size(); i++)
137 results << wrapObjName(objList[i], favWrapper);
138
139 return results;
140 }
141
wrapObjNamesIfNeeded(const QList<QString> & objList,NameWrapper favWrapper)142 QList<QString> wrapObjNamesIfNeeded(const QList<QString>& objList, NameWrapper favWrapper)
143 {
144 QList<QString> results;
145 for (int i = 0; i < objList.size(); i++)
146 results << wrapObjIfNeeded(objList[i], favWrapper);
147
148 return results;
149 }
150
getAllNameWrappers()151 QList<NameWrapper> getAllNameWrappers()
152 {
153 return {NameWrapper::DOUBLE_QUOTE, NameWrapper::BRACKET, NameWrapper::BACK_QUOTE, NameWrapper::QUOTE};
154 }
155
wrapValueIfNeeded(const QString & str)156 QString wrapValueIfNeeded(const QString& str)
157 {
158 return wrapValueIfNeeded(QVariant::fromValue(str));
159 }
160
wrapValueIfNeeded(const QVariant & value)161 QString wrapValueIfNeeded(const QVariant& value)
162 {
163 if (isNumeric(value))
164 return value.toString();
165
166 return wrapString(value.toString());
167 }
168
wrapString(const QString & str)169 QString wrapString(const QString& str)
170 {
171 QString result = str;
172 result.prepend("'");
173 result.append("'");
174 return result;
175 }
176
doesStringNeedWrapping(const QString & str)177 bool doesStringNeedWrapping(const QString& str)
178 {
179 if (str.size() == 0)
180 return false;
181
182 return str[0] == '\'' && str[str.length()-1] == '\'';
183 }
184
isStringWrapped(const QString & str)185 bool isStringWrapped(const QString& str)
186 {
187 return !doesStringNeedWrapping(str);
188 }
189
wrapStringIfNeeded(const QString & str)190 QString wrapStringIfNeeded(const QString& str)
191 {
192 if (isStringWrapped(str))
193 return wrapString(str);
194
195 return str;
196 }
197
escapeString(QString & str)198 QString escapeString(QString& str)
199 {
200 return str.replace('\'', "''");
201 }
202
escapeString(const QString & str)203 QString escapeString(const QString& str)
204 {
205 QString newStr = str;
206 return newStr.replace('\'', "''");
207 }
208
stripString(QString & str)209 QString stripString(QString& str)
210 {
211 if (str.length() <= 1)
212 return str;
213
214 if (str[0] == '\'' && str[str.length()-1] == '\'')
215 return str.mid(1, str.length()-2);
216
217 return str;
218 }
219
stripString(const QString & str)220 QString stripString(const QString& str)
221 {
222 QString newStr = str;
223 return stripString(newStr);
224 }
225
stripEndingSemicolon(const QString & str)226 QString stripEndingSemicolon(const QString& str)
227 {
228 QString newStr = rStrip(str);
229 if (newStr.size() == 0)
230 return str;
231
232 if (newStr[newStr.size()-1] == ';')
233 {
234 newStr.chop(1);
235 return newStr;
236 }
237 else
238 return str;
239 }
240
stripObjName(const QString & str)241 QString stripObjName(const QString &str)
242 {
243 QString newStr = str;
244 return stripObjName(newStr);
245 }
246
stripObjName(QString & str)247 QString stripObjName(QString &str)
248 {
249 if (str.isNull())
250 return str;
251
252 if (str.length() <= 1)
253 return str;
254
255 if (!isObjWrapped(str))
256 return str;
257
258 return str.mid(1, str.length()-2);
259 }
260
isObjWrapped(const QString & str)261 bool isObjWrapped(const QString& str)
262 {
263 return getObjWrapper(str) != NameWrapper::null;
264 }
265
doesNotContainEndingWrapperChar(const QString & str,NameWrapper wrapper)266 bool doesNotContainEndingWrapperChar(const QString& str, NameWrapper wrapper)
267 {
268 QString innerPart = str.mid(1, str.length() - 2);
269 const QChar& endingChar = wrapperEscapedEnding[wrapper].first;
270 bool escapingAllowed = wrapperEscapedEnding[wrapper].second;
271 int idx = -1;
272 int lastIdx = innerPart.length() - 1;
273 while ((idx = innerPart.indexOf(endingChar, idx + 1)) > -1)
274 {
275 if (idx == lastIdx || !escapingAllowed || innerPart[idx + 1] != endingChar)
276 return false;
277
278 idx++; // we had occurrence, but it was escaped, so we need to skip the second (escape) char
279 }
280 return true;
281 }
282
getObjWrapper(const QString & str)283 NameWrapper getObjWrapper(const QString& str)
284 {
285 if (str.isEmpty())
286 return NameWrapper::null;
287
288 for (NameWrapper wrapper : sqlite3Wrappers)
289 {
290 QPair<QChar,QChar> chars = wrapperChars[wrapper];
291 if (str[0] == chars.first && str[str.length()-1] == chars.second && doesNotContainEndingWrapperChar(str, wrapper))
292 return wrapper;
293 }
294 return NameWrapper::null;
295 }
296
isWrapperChar(const QChar & c)297 bool isWrapperChar(const QChar& c)
298 {
299 for (NameWrapper wrapper : sqlite3Wrappers)
300 {
301 QPair<QChar,QChar> chars = wrapperChars[wrapper];
302 if (c == chars.first || c == chars.second)
303 return true;
304 }
305 return false;
306 }
307
qHash(NameWrapper wrapper)308 int qHash(NameWrapper wrapper)
309 {
310 return (uint)wrapper;
311 }
312
getPrefixDb(const QString & origDbName)313 QString getPrefixDb(const QString& origDbName)
314 {
315 if (origDbName.isEmpty())
316 return "main";
317 else
318 return wrapObjIfNeeded(origDbName);
319 }
320
isSystemTable(const QString & name)321 bool isSystemTable(const QString &name)
322 {
323 return name.startsWith("sqlite_");
324 }
325
isSystemIndex(const QString & name)326 bool isSystemIndex(const QString &name)
327 {
328 return name.startsWith("sqlite_autoindex_");
329 }
330
331
stripObjName(TokenPtr token)332 TokenPtr stripObjName(TokenPtr token)
333 {
334 if (!token)
335 return token;
336
337 token->value = stripObjName(token->value);
338 return token;
339 }
340
removeComments(const QString & value)341 QString removeComments(const QString& value)
342 {
343 Lexer lexer;
344 TokenList tokens = lexer.tokenize(value);
345 while (tokens.remove(Token::COMMENT))
346 continue;
347
348 return tokens.detokenize();
349 }
350
splitQueriesUpdateCaseWhenDepth(Token::Type type,const QString & value,int & caseWhenDepth)351 void splitQueriesUpdateCaseWhenDepth(Token::Type type, const QString& value, int& caseWhenDepth)
352 {
353 if (type != Token::KEYWORD)
354 return;
355
356 if (value == "CASE")
357 caseWhenDepth++;
358 else if (value == "END" && caseWhenDepth > 0)
359 caseWhenDepth--;
360 }
361
splitQueries(const TokenList & tokenizedQuery,bool * complete)362 QList<TokenList> splitQueries(const TokenList& tokenizedQuery, bool* complete)
363 {
364 QList<TokenList> queries;
365 TokenList currentQueryTokens;
366 QString value;
367 int caseWhenDepth = 0;
368 int createTriggerMeter = 0;
369 bool insideTrigger = false;
370 bool completeQuery = false;
371 for (const TokenPtr& token : tokenizedQuery)
372 {
373 value = token->value.toUpper();
374 if (!token->isWhitespace())
375 completeQuery = false;
376
377 if (insideTrigger)
378 {
379 if (token->type == Token::KEYWORD && value == "END" && caseWhenDepth <= 0 && caseWhenDepth == 0)
380 {
381 insideTrigger = false;
382 completeQuery = true;
383 }
384
385 currentQueryTokens << token;
386 splitQueriesUpdateCaseWhenDepth(token->type, value, caseWhenDepth);
387 continue;
388 }
389
390 splitQueriesUpdateCaseWhenDepth(token->type, value, caseWhenDepth);
391
392 if (token->type == Token::KEYWORD)
393 {
394 if (value == "CREATE" || value == "TRIGGER" || value == "BEGIN")
395 createTriggerMeter++;
396
397 if (createTriggerMeter == 3)
398 insideTrigger = true;
399
400 currentQueryTokens << token;
401 }
402 else if (token->type == Token::OPERATOR && value == ";")
403 {
404 createTriggerMeter = 0;
405 caseWhenDepth = 0;
406 currentQueryTokens << token;
407 queries << currentQueryTokens;
408 currentQueryTokens.clear();
409 completeQuery = true;
410 }
411 else
412 {
413 currentQueryTokens << token;
414 }
415 }
416
417 if (currentQueryTokens.size() > 0)
418 queries << currentQueryTokens;
419
420 if (complete)
421 *complete = completeQuery;
422
423 return queries;
424 }
425
quickSplitQueries(const QString & sql,bool keepEmptyQueries,bool removeComments)426 QStringList quickSplitQueries(const QString& sql, bool keepEmptyQueries, bool removeComments)
427 {
428 QChar c;
429 bool inString = false;
430 bool inMultiLineComment = false;
431 bool inSingleLineComment = false;
432 QStringList queries;
433 QString query;
434 QString trimmed;
435 for (int i = 0, total = sql.size(); i < total; ++i)
436 {
437 c = sql[i];
438
439 // String
440 if (inString)
441 {
442 query += c;
443 if (c == '\'')
444 {
445 inString = false;
446 }
447 continue;
448 }
449
450 // One-line comment
451 if (inSingleLineComment)
452 {
453 if (!removeComments)
454 query += c;
455
456 if (c == '\r' && (i + 1) < total && sql[i+1] == '\n')
457 {
458 if (!removeComments)
459 query += '\n';
460
461 i++;
462 inSingleLineComment = false;
463 }
464 else if (c == '\n' || c == '\r')
465 inSingleLineComment = false;
466
467 continue;
468 }
469
470 // Multi-line comment
471 if (inMultiLineComment)
472 {
473 if (!removeComments)
474 query += c;
475
476 if (c == '*' && (i + 1) < total && sql[i+1] == '/')
477 {
478 if (!removeComments)
479 query += '/';
480
481 i++;
482 inMultiLineComment = false;
483 }
484
485 continue;
486 }
487
488 // Everything rest
489 if (c == '\'')
490 {
491 query += c;
492 inString = true;
493 }
494 else if (c == '-' && (i + 1) < total && sql[i+1] == '-')
495 {
496 inSingleLineComment = true;
497 i++;
498 if (!removeComments)
499 query += "--";
500 }
501 else if (c == '/' && (i + 1) < total && sql[i+1] == '*')
502 {
503 inMultiLineComment = true;
504 i++;
505 if (!removeComments)
506 query += "/*";
507 }
508 else if (c == ';')
509 {
510 query += c;
511 if (keepEmptyQueries || (!(trimmed = query.trimmed()).isEmpty() && trimmed != ";"))
512 queries << query;
513
514 query.clear();
515 }
516 else
517 {
518 query += c;
519 }
520 }
521
522 if (!query.isNull() && (!(trimmed = query.trimmed()).isEmpty() && trimmed != ";"))
523 queries << query;
524
525 return queries;
526 }
527
splitQueries(const QString & sql,bool keepEmptyQueries,bool removeComments,bool * complete)528 QStringList splitQueries(const QString& sql, bool keepEmptyQueries, bool removeComments, bool* complete)
529 {
530 TokenList tokens = Lexer::tokenize(sql);
531 if (removeComments)
532 tokens = tokens.filterOut(Token::COMMENT);
533
534 QList<TokenList> tokenizedQueries = splitQueries(tokens, complete);
535
536 QString query;
537 QStringList queries;
538 for (const TokenList& queryTokens : tokenizedQueries)
539 {
540 query = queryTokens.detokenize();
541 if (keepEmptyQueries || (!query.trimmed().isEmpty() && query.trimmed() != ";"))
542 queries << query;
543 }
544
545 return queries;
546 }
547
getQueryWithPosition(const QStringList & queries,int position,int * startPos)548 QString getQueryWithPosition(const QStringList& queries, int position, int* startPos)
549 {
550 int currentPos = 0;
551 int length = 0;
552
553 if (startPos)
554 *startPos = 0;
555
556 for (const QString& query : queries)
557 {
558 length = query.length();
559 if (position >= currentPos && position < currentPos+length)
560 return query;
561
562 currentPos += length;
563
564 if (startPos)
565 *startPos += length;
566 }
567
568 // If we passed all queries and it happens that the cursor is just after last query - this is the query we want.
569 if (position == currentPos && queries.size() > 0)
570 {
571 if (startPos)
572 *startPos -= length;
573
574 return queries.last();
575 }
576
577 if (startPos)
578 *startPos = -1;
579
580 return QString();
581 }
582
getQueryWithPosition(const QString & queries,int position,int * startPos)583 QString getQueryWithPosition(const QString& queries, int position, int* startPos)
584 {
585 QStringList queryList = splitQueries(queries);
586 return getQueryWithPosition(queryList, position, startPos);
587 }
588
trimBindParamPrefix(const QString & param)589 QString trimBindParamPrefix(const QString& param)
590 {
591 if (param == "?")
592 return param;
593
594 if (param.startsWith("$") || param.startsWith("@") || param.startsWith(":") || param.startsWith("?"))
595 return param.mid(1);
596
597 return param;
598 }
599
getQueriesWithParamNames(const QString & query)600 QList<QueryWithParamNames> getQueriesWithParamNames(const QString& query)
601 {
602 QList<QueryWithParamNames> results;
603
604 TokenList allTokens = Lexer::tokenize(query);
605 QList<TokenList> queries = splitQueries(allTokens);
606
607 QString queryStr;
608 QStringList paramNames;
609 for (const TokenList& tokens : queries)
610 {
611 paramNames.clear();
612 for (const TokenPtr& token : tokens.filter(Token::BIND_PARAM))
613 paramNames << token->value;
614
615 queryStr = tokens.detokenize().trimmed();
616 if (!queryStr.isEmpty())
617 results << QueryWithParamNames(queryStr, paramNames);
618 }
619 return results;
620 }
621
getQueriesWithParamCount(const QString & query)622 QList<QueryWithParamCount> getQueriesWithParamCount(const QString& query)
623 {
624 QList<QueryWithParamCount> results;
625
626 TokenList allTokens = Lexer::tokenize(query);
627 QList<TokenList> queries = splitQueries(allTokens);
628
629 QString queryStr;
630 for (const TokenList& tokens : queries)
631 {
632 queryStr = tokens.detokenize().trimmed();
633 if (!queryStr.isEmpty())
634 results << QueryWithParamCount(queryStr, tokens.filter(Token::BIND_PARAM).size());
635 }
636
637 return results;
638 }
639
getQueryWithParamNames(const QString & query)640 QueryWithParamNames getQueryWithParamNames(const QString& query)
641 {
642 TokenList allTokens = Lexer::tokenize(query);
643
644 QStringList paramNames;
645 for (const TokenPtr& token : allTokens.filter(Token::BIND_PARAM))
646 paramNames << token->value;
647
648 return QueryWithParamNames(query, paramNames);
649 }
650
getQueryWithParamCount(const QString & query)651 QueryWithParamCount getQueryWithParamCount(const QString& query)
652 {
653 TokenList allTokens = Lexer::tokenize(query);
654 return QueryWithParamCount(query, allTokens.filter(Token::BIND_PARAM).size());
655 }
656
commentAllSqlLines(const QString & sql)657 QString commentAllSqlLines(const QString& sql)
658 {
659 QStringList lines = splitByLines(sql);
660 QMutableStringListIterator it(lines);
661 while (it.hasNext())
662 it.next().prepend("-- ");
663
664 return joinLines(lines);
665 }
666
getBindTokenName(const TokenPtr & token)667 QString getBindTokenName(const TokenPtr& token)
668 {
669 if (token->type != Token::BIND_PARAM)
670 return QString();
671
672 if (token->value == "?")
673 return token->value;
674
675 return token->value.mid(1);
676 }
677
getQueryAccessMode(const QString & query,bool * isSelect)678 QueryAccessMode getQueryAccessMode(const QString& query, bool* isSelect)
679 {
680 static QStringList readOnlyCommands = {"ANALYZE", "EXPLAIN", "PRAGMA", "SELECT"};
681
682 if (isSelect)
683 *isSelect = false;
684
685 TokenList tokens = Lexer::tokenize(query);
686 int keywordIdx = tokens.indexOf(Token::KEYWORD);
687 if (keywordIdx < 0)
688 return QueryAccessMode::WRITE;
689
690 int cmdIdx = readOnlyCommands.indexOf(tokens[keywordIdx]->value.toUpper());
691 if (keywordIdx > -1 && cmdIdx > -1)
692 {
693 if (cmdIdx == 3 && isSelect)
694 *isSelect = true;
695
696 return QueryAccessMode::READ;
697 }
698
699 if (keywordIdx > -1 && tokens[keywordIdx]->value.toUpper() == "WITH")
700 {
701 bool matched = false;
702 bool queryIsSelect = false;
703 int depth = 0;
704 for (TokenPtr token : tokens)
705 {
706 switch (token->type)
707 {
708 case Token::PAR_LEFT:
709 depth++;
710 break;
711 case Token::PAR_RIGHT:
712 depth--;
713 break;
714 case Token::KEYWORD:
715 if (depth == 0)
716 {
717 QString val = token->value.toUpper();
718 if (val == "SELECT")
719 {
720 matched = true;
721 queryIsSelect = true;
722 }
723 else if (val == "DELETE" || val == "UPDATE" || val == "INSERT")
724 {
725 matched = true;
726 }
727 }
728 break;
729 default:
730 break;
731 }
732
733 if (matched)
734 break;
735 }
736
737 if (queryIsSelect)
738 {
739 if (isSelect)
740 *isSelect = true;
741
742 return QueryAccessMode::READ;
743 }
744 }
745
746 return QueryAccessMode::WRITE;
747 }
748
valueListToSqlList(const QVariantList & values)749 QStringList valueListToSqlList(const QVariantList& values)
750 {
751 QStringList argList;
752 for (const QVariant& value : values)
753 {
754 if (!value.isValid() || value.isNull())
755 {
756 argList << "NULL";
757 continue;
758 }
759
760 switch (value.userType())
761 {
762 case QVariant::Int:
763 case QVariant::UInt:
764 case QVariant::LongLong:
765 case QVariant::ULongLong:
766 argList << value.toString();
767 break;
768 case QVariant::Double:
769 argList << doubleToString(value);
770 break;
771 case QVariant::Bool:
772 argList << QString::number(value.toInt());
773 break;
774 case QVariant::ByteArray:
775 argList << "X'" + value.toByteArray().toHex().toUpper() + "'";
776 break;
777 default:
778 argList << wrapString(escapeString(value.toString()));
779 break;
780 }
781 }
782 return argList;
783 }
784
wrapStrings(const QStringList & strList)785 QStringList wrapStrings(const QStringList& strList)
786 {
787 QStringList list;
788 for (const QString& str : strList)
789 list << wrapString(str);
790
791 return list;
792 }
793
trimQueryEnd(const QString & query)794 QString trimQueryEnd(const QString &query)
795 {
796 QString q = query.trimmed();
797 while (q.endsWith(";"))
798 {
799 q.chop(1);
800 q = q.trimmed();
801 }
802 return q;
803 }
804
toSqliteDataType(const QString & typeStr)805 SqliteDataType toSqliteDataType(const QString& typeStr)
806 {
807 QString upperType = typeStr.trimmed().toUpper();
808 if (upperType == "INTEGER")
809 return SqliteDataType::INTEGER;
810
811 if (upperType == "REAL")
812 return SqliteDataType::REAL;
813
814 if (upperType == "TEXT")
815 return SqliteDataType::TEXT;
816
817 if (upperType == "BLOB")
818 return SqliteDataType::BLOB;
819
820 if (upperType == "NULL")
821 return SqliteDataType::_NULL;
822
823 return SqliteDataType::UNKNOWN;
824 }
825