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