1 #include "sqltablemodel.h"
2 #include "common/utils_sql.h"
3 #include "sqlqueryitem.h"
4 #include "services/notifymanager.h"
5 #include "uiconfig.h"
6 #include "common/unused.h"
7 #include <QDebug>
8 #include <QApplication>
9 #include <schemaresolver.h>
10 #include <querygenerator.h>
11 
SqlTableModel(QObject * parent)12 SqlTableModel::SqlTableModel(QObject *parent) :
13     SqlQueryModel(parent)
14 {
15 }
16 
getDatabase() const17 QString SqlTableModel::getDatabase() const
18 {
19     return database;
20 }
21 
getTable() const22 QString SqlTableModel::getTable() const
23 {
24     return table;
25 }
26 
setDatabaseAndTable(const QString & database,const QString & table)27 void SqlTableModel::setDatabaseAndTable(const QString& database, const QString& table)
28 {
29     this->database = database;
30     this->table = table;
31     setQuery("SELECT * FROM "+getDataSource());
32 
33     QString dbName = database;
34     if (database.toLower() == "main" || database.isEmpty())
35         dbName = QString();
36 
37     tablesInUse.clear();
38     tablesInUse << DbAndTable(db, dbName, table);
39 
40     SchemaResolver resolver(db);
41     isWithOutRowIdTable = resolver.isWithoutRowIdTable(database, table);
42 }
43 
features() const44 SqlQueryModel::Features SqlTableModel::features() const
45 {
46     return INSERT_ROW|DELETE_ROW|FILTERING;
47 }
48 
commitAddedRow(const QList<SqlQueryItem * > & itemsInRow,QList<SqlQueryModel::CommitSuccessfulHandler> & successfulCommitHandlers)49 bool SqlTableModel::commitAddedRow(const QList<SqlQueryItem*>& itemsInRow, QList<SqlQueryModel::CommitSuccessfulHandler>& successfulCommitHandlers)
50 {
51     QList<SqlQueryModelColumnPtr> modelColumns = getTableColumnModels(table);
52     if (modelColumns.size() != itemsInRow.size())
53     {
54         qCritical() << "Tried to SqlTableModel::commitAddedRow() with number of columns in argument different than model resolved for the table.";
55         return false;
56     }
57 
58     // Check that just in case:
59     if (modelColumns.size() == 0)
60     {
61         qCritical() << "Tried to SqlTableModel::commitAddedRow() with number of resolved columns in the table equal to 0!";
62         return false;
63     }
64 
65     // Prepare column placeholders and their values
66     QStringList colNameList;
67     QStringList sqlValues;
68     QList<QVariant> args;
69     updateColumnsAndValues(itemsInRow, modelColumns, colNameList, sqlValues, args);
70 
71     // Prepare SQL query
72     QString sql = getInsertSql(modelColumns, colNameList, sqlValues, args);
73 
74     // Execute query
75     SqlQueryPtr result = db->exec(sql, args);
76 
77     // Handle error
78     if (result->isError())
79     {
80         QString errMsg = tr("Error while committing new row: %1").arg(result->getErrorText());
81         for (SqlQueryItem* item : itemsInRow)
82             item->setCommittingError(true, errMsg);
83 
84         notifyError(errMsg);
85         return false;
86     }
87 
88     // Reloading row with actual values (because of DEFAULT, AUTOINCR)
89     RowId rowId;
90     if (isWithOutRowIdTable)
91     {
92         SqlQueryItem* item = nullptr;
93         int i = 0;
94         for (const SqlQueryModelColumnPtr& modelColumn : modelColumns)
95         {
96             item = itemsInRow[i++];
97             if (modelColumn->isPk())
98                 rowId[modelColumn->column] = item->getValue();
99         }
100     }
101     else
102         rowId = result->getInsertRowId();
103 
104     // After all items are committed successfully, update data/metadata for inserted rows/items
105     successfulCommitHandlers << [this, itemsInRow, modelColumns, rowId]()
106     {
107         updateRowAfterInsert(itemsInRow, modelColumns, rowId);
108     };
109 
110     return true;
111 }
112 
commitDeletedRow(const QList<SqlQueryItem * > & itemsInRow,QList<SqlQueryModel::CommitSuccessfulHandler> & successfulCommitHandlers)113 bool SqlTableModel::commitDeletedRow(const QList<SqlQueryItem*>& itemsInRow, QList<SqlQueryModel::CommitSuccessfulHandler>& successfulCommitHandlers)
114 {
115     if (itemsInRow.size() == 0)
116     {
117         qCritical() << "Tried to SqlTableModel::commitDeletedRow() with number of items equal to 0!";
118         return false;
119     }
120 
121     // This should not happen anymore (since WITHOUT ROWID tables should be handled properly now,
122     // but we will keep this here for a while, just in case.
123 //    if (itemsInRow[0]->isJustInsertedWithOutRowId())
124 //    {
125 //        QString msg = tr("When inserted new row to the WITHOUT ROWID table, using DEFAULT value for PRIMARY KEY, "
126 //                         "the table has to be reloaded in order to delete the new row.");
127 //        notifyError(tr("Error while deleting row from table %1: %2").arg(table).arg(msg));
128 //        return false;
129 //    }
130 
131     RowId rowId = itemsInRow[0]->getRowId();
132     if (rowId.isEmpty())
133         return false;
134 
135     CommitDeleteQueryBuilder queryBuilder;
136     queryBuilder.setTable(wrapObjIfNeeded(table));
137     queryBuilder.setRowId(rowId);
138 
139     QString sql = queryBuilder.build();
140     QHash<QString, QVariant> args = queryBuilder.getQueryArgs();
141 
142     SqlQueryPtr result = db->exec(sql, args);
143     if (result->isError())
144     {
145         QString errMsg = tr("Error while deleting row from table %1: %2").arg(table, result->getErrorText());
146         for (SqlQueryItem* item : itemsInRow)
147             item->setCommittingError(true, errMsg);
148 
149         notifyError(errMsg);
150         return false;
151     }
152 
153     if (!SqlQueryModel::commitDeletedRow(itemsInRow, successfulCommitHandlers))
154         qCritical() << "Could not delete row from SqlQueryView while committing row deletion.";
155 
156     return true;
157 }
158 
applyFilter(const QString & value,FilterValueProcessor valueProc)159 void SqlTableModel::applyFilter(const QString& value, FilterValueProcessor valueProc)
160 {
161     static_qstring(sql, "SELECT * FROM %1 WHERE %2");
162 
163     if (value.isEmpty())
164     {
165         resetFilter();
166         return;
167     }
168 
169     QStringList conditions;
170     for (SqlQueryModelColumnPtr column : columns)
171         conditions << wrapObjIfNeeded(column->column)+" "+valueProc(value);
172 
173     setQuery(sql.arg(getDataSource(), conditions.join(" OR ")));
174     executeQuery();
175 }
176 
applyFilter(const QStringList & values,FilterValueProcessor valueProc)177 void SqlTableModel::applyFilter(const QStringList& values, FilterValueProcessor valueProc)
178 {
179     static_qstring(sql, "SELECT * FROM %1 WHERE %2");
180     if (values.isEmpty())
181     {
182         resetFilter();
183         return;
184     }
185 
186     if (values.size() != columns.size())
187     {
188         qCritical() << "Asked to per-column filter, but number columns"
189                     << columns.size() << "is different than number of values" << values.size();
190         return;
191     }
192 
193     QStringList conditions;
194     for (int i = 0, total = columns.size(); i < total; ++i)
195     {
196         if (values[i].isEmpty())
197             continue;
198 
199         conditions << wrapObjIfNeeded(columns[i]->column)+" "+valueProc(values[i]);
200     }
201 
202     setQuery(sql.arg(getDataSource(), conditions.join(" AND ")));
203     executeQuery();
204 }
205 
stringFilterValueProcessor(const QString & value)206 QString SqlTableModel::stringFilterValueProcessor(const QString& value)
207 {
208     static_qstring(pattern, "LIKE '%%1%'");
209     return pattern.arg(escapeString(value));
210 }
211 
regExpFilterValueProcessor(const QString & value)212 QString SqlTableModel::regExpFilterValueProcessor(const QString& value)
213 {
214     static_qstring(pattern, "REGEXP '%1'");
215     return pattern.arg(escapeString(value));
216 }
217 
supportsModifyingQueriesInMenu() const218 bool SqlTableModel::supportsModifyingQueriesInMenu() const
219 {
220     return true;
221 }
222 
applySqlFilter(const QString & value)223 void SqlTableModel::applySqlFilter(const QString& value)
224 {
225     if (value.isEmpty())
226     {
227         resetFilter();
228         return;
229     }
230 
231     setQuery("SELECT * FROM "+getDataSource()+" WHERE "+value);
232     executeQuery();
233 }
234 
applyStringFilter(const QString & value)235 void SqlTableModel::applyStringFilter(const QString& value)
236 {
237     applyFilter(value, &stringFilterValueProcessor);
238 }
239 
applyStringFilter(const QStringList & values)240 void SqlTableModel::applyStringFilter(const QStringList& values)
241 {
242     applyFilter(values, &stringFilterValueProcessor);
243 }
244 
applyRegExpFilter(const QString & value)245 void SqlTableModel::applyRegExpFilter(const QString& value)
246 {
247     applyFilter(value, &regExpFilterValueProcessor);
248 }
249 
applyRegExpFilter(const QStringList & values)250 void SqlTableModel::applyRegExpFilter(const QStringList& values)
251 {
252     applyFilter(values, &regExpFilterValueProcessor);
253 }
254 
resetFilter()255 void SqlTableModel::resetFilter()
256 {
257     setQuery("SELECT * FROM "+getDataSource());
258     //reload();
259     executeQuery();
260 }
261 
generateSelectQueryForItems(const QList<SqlQueryItem * > & items)262 QString SqlTableModel::generateSelectQueryForItems(const QList<SqlQueryItem*>& items)
263 {
264     QHash<QString, QVariantList> values = toValuesGroupedByColumns(items);
265 
266     QueryGenerator generator;
267     return generator.generateSelectFromTable(db, database, table, values);
268 }
269 
generateInsertQueryForItems(const QList<SqlQueryItem * > & items)270 QString SqlTableModel::generateInsertQueryForItems(const QList<SqlQueryItem*>& items)
271 {
272     QHash<QString, QVariantList> values = toValuesGroupedByColumns(items);
273 
274     QueryGenerator generator;
275     return generator.generateInsertToTable(db, database, table, values);
276 }
277 
generateUpdateQueryForItems(const QList<SqlQueryItem * > & items)278 QString SqlTableModel::generateUpdateQueryForItems(const QList<SqlQueryItem*>& items)
279 {
280     QHash<QString, QVariantList> values = toValuesGroupedByColumns(items);
281 
282     QueryGenerator generator;
283     return generator.generateUpdateOfTable(db, database, table, values);
284 }
285 
generateDeleteQueryForItems(const QList<SqlQueryItem * > & items)286 QString SqlTableModel::generateDeleteQueryForItems(const QList<SqlQueryItem*>& items)
287 {
288     QHash<QString, QVariantList> values = toValuesGroupedByColumns(items);
289 
290     QueryGenerator generator;
291     return generator.generateDeleteFromTable(db, database, table, values);
292 }
293 
updateRowAfterInsert(const QList<SqlQueryItem * > & itemsInRow,const QList<SqlQueryModelColumnPtr> & modelColumns,RowId rowId)294 void SqlTableModel::updateRowAfterInsert(const QList<SqlQueryItem*>& itemsInRow, const QList<SqlQueryModelColumnPtr>& modelColumns, RowId rowId)
295 {
296     // Update cells with data just like it was entered. Only DEFAULT and PRIMARY KEY AUTOINCREMENT will have special values.
297     // If the DEFAULT is not an explicit literal, but an expression and db is SQLite3, we have to read the inserted value from DB.
298     QHash<SqlQueryModelColumnPtr,SqlQueryItem*> columnsToReadFromDb;
299     Parser parser;
300     QHash<SqlQueryItem*,QVariant> values;
301     SqlQueryItem* item = nullptr;
302     int i = 0;
303     for (const SqlQueryModelColumnPtr& modelColumn : modelColumns)
304     {
305         item = itemsInRow[i++];
306         if (processNullValueAfterInsert(item, values[item], modelColumn, columnsToReadFromDb, rowId, parser))
307             continue;
308 
309         values[item] = item->getValue();
310     }
311 
312     // Reading values for DEFAULT values being an expression
313     if (columnsToReadFromDb.size() > 0)
314         processDefaultValueAfterInsert(columnsToReadFromDb, values, rowId);
315 
316     // Reading values for GENERATED columns
317     i = 0;
318     QList<SqlQueryItem*> generatedColumnItems;
319     for (const SqlQueryModelColumnPtr& modelColumn : modelColumns)
320     {
321         if (modelColumn->isGenerated())
322             generatedColumnItems << itemsInRow[i++];
323     }
324     refreshGeneratedColumns(generatedColumnItems, values, rowId);
325 
326     // Update cell data with results
327     int colIdx = 0;
328     for (SqlQueryItem* itemToUpdate : itemsInRow)
329     {
330         updateItem(itemToUpdate, values[itemToUpdate], colIdx, rowId);
331 
332         if (isWithOutRowIdTable && rowId.isEmpty())
333             itemToUpdate->setJustInsertedWithOutRowId(true);
334 
335         colIdx++;
336     }
337 }
338 
processNullValueAfterInsert(SqlQueryItem * item,QVariant & value,const SqlQueryModelColumnPtr & modelColumn,QHash<SqlQueryModelColumnPtr,SqlQueryItem * > & columnsToReadFromDb,RowId rowId,Parser & parser)339 bool SqlTableModel::processNullValueAfterInsert(SqlQueryItem* item, QVariant& value, const SqlQueryModelColumnPtr& modelColumn,
340                                                 QHash<SqlQueryModelColumnPtr, SqlQueryItem*>& columnsToReadFromDb, RowId rowId, Parser& parser)
341 {
342 //    qDebug() << "Item is for column" << item->getColumn()->column << ", column iterated:" << modelColumn->column;
343     if (!item->getValue().isNull())
344         return false;
345 
346     // If this is the PK AUTOINCR column we use RowId as value, because it was skipped when setting values to items
347     if (modelColumn->isPk() && modelColumn->isAutoIncr())
348     {
349         value = rowId["ROWID"];
350         return true;
351     }
352 
353     if (!CFG_UI.General.UseDefaultValueForNull.get() || !modelColumn->isDefault())
354         return false;
355 
356     SqliteExpr* expr = parser.parseExpr(modelColumn->getDefaultConstraint()->defaultValue);
357     if (expr && expr->mode != SqliteExpr::Mode::LITERAL_VALUE)
358     {
359         if (isWithOutRowIdTable && rowId.isEmpty())
360         {
361             qWarning() << "Inserted expression as DEFAULT value for table WITHOUT ROWID and actually no ROWID."
362                        << "This is currently unsupported to refresh such cell value instantly.";
363             value = QVariant();
364         }
365         else
366             columnsToReadFromDb[modelColumn] = item;
367 
368         return true;
369     }
370 
371     if (expr)
372         value = expr->literalValue;
373     else
374         value = modelColumn->getDefaultConstraint()->defaultValue;
375 
376     if (value.userType() == QVariant::String)
377         value = stripString(value.toString());
378 
379     return true;
380 }
381 
processDefaultValueAfterInsert(QHash<SqlQueryModelColumnPtr,SqlQueryItem * > & columnsToReadFromDb,QHash<SqlQueryItem *,QVariant> & values,RowId rowId)382 void SqlTableModel::processDefaultValueAfterInsert(QHash<SqlQueryModelColumnPtr, SqlQueryItem*>& columnsToReadFromDb, QHash<SqlQueryItem*, QVariant>& values, RowId rowId)
383 {
384     // Preparing query
385     static_qstring(limitedColTpl, "substr(%1, 1, %2)");
386     SelectColumnsQueryBuilder queryBuilder;
387     queryBuilder.setTable(wrapObjIfNeeded(table));
388     queryBuilder.setRowId(rowId);
389     QList<SqlQueryModelColumnPtr> columnKeys = columnsToReadFromDb.keys();
390     for (const SqlQueryModelColumnPtr& modelColumn : columnKeys)
391         queryBuilder.addColumn(limitedColTpl.arg(wrapObjIfNeeded(modelColumn->column), QString::number(cellDataLengthLimit)));
392 
393     // Executing query
394     SqlQueryPtr defColValues = db->exec(queryBuilder.build(), queryBuilder.getQueryArgs(), Db::Flag::PRELOAD);
395 
396     // Handling error
397     if (defColValues->isError())
398     {
399         qCritical() << "Could not load inserted values for DEFAULT expression in the table, so filling them with NULL. Error from database was:"
400                     << defColValues->getErrorText();
401 
402         for (const SqlQueryModelColumnPtr& modelColumn : columnKeys)
403             values[columnsToReadFromDb[modelColumn]] = QVariant();
404 
405         return;
406     }
407 
408 
409     if (!defColValues->hasNext())
410     {
411         qCritical() << "Could not load inserted values for DEFAULT expression in the table, so filling them with NULL. There were no result rows.";
412 
413         for (const SqlQueryModelColumnPtr& modelColumn : columnKeys)
414             values[columnsToReadFromDb[modelColumn]] = QVariant();
415 
416         return;
417     }
418 
419 
420     // Reading a row
421     SqlResultsRowPtr row = defColValues->next();
422     if (row->valueList().size() != columnKeys.size())
423     {
424         qCritical() << "Could not load inserted values for DEFAULT expression in the table, so filling them with NULL. Number of columns from results was invalid:"
425                     << row->valueList().size() << ", while expected:" << columnKeys.size();
426 
427         for (const SqlQueryModelColumnPtr& modelColumn : columnKeys)
428             values[columnsToReadFromDb[modelColumn]] = QVariant();
429 
430         return;
431     }
432 
433     int colIdx = 0;
434     for (const SqlQueryModelColumnPtr& modelColumn : columnKeys)
435         values[columnsToReadFromDb[modelColumn]] = row->value(colIdx++);
436 }
437 
getDatabasePrefix()438 QString SqlTableModel::getDatabasePrefix()
439 {
440     if (database.isNull())
441         return ""; // not "main.", because the "main." doesn't work for TEMP tables, such as sqlite_temp_master
442 
443     return wrapObjIfNeeded(database) + ".";
444 }
445 
getDataSource()446 QString SqlTableModel::getDataSource()
447 {
448     return getDatabasePrefix() + wrapObjIfNeeded(table);
449 }
450 
getInsertSql(const QList<SqlQueryModelColumnPtr> & modelColumns,QStringList & colNameList,QStringList & sqlValues,QList<QVariant> & args)451 QString SqlTableModel::getInsertSql(const QList<SqlQueryModelColumnPtr>& modelColumns, QStringList& colNameList,
452                                     QStringList& sqlValues, QList<QVariant>& args)
453 {
454     UNUSED(modelColumns);
455     UNUSED(args);
456     QString sql = "INSERT INTO "+wrapObjIfNeeded(table);
457     if (colNameList.size() == 0)
458     {
459         // There are all null values passed to the query. We need to use Sqlite3 special syntax, or find at least one default value
460         sql += " DEFAULT VALUES";
461     }
462     else
463         sql += " ("+colNameList.join(", ")+") VALUES ("+sqlValues.join(", ")+")";
464 
465     return sql;
466 }
467 
updateColumnsAndValues(const QList<SqlQueryItem * > & itemsInRow,const QList<SqlQueryModelColumnPtr> & modelColumns,QStringList & colNameList,QStringList & sqlValues,QList<QVariant> & args)468 void SqlTableModel::updateColumnsAndValues(const QList<SqlQueryItem*>& itemsInRow, const QList<SqlQueryModelColumnPtr>& modelColumns,
469                                            QStringList& colNameList, QStringList& sqlValues, QList<QVariant>& args)
470 {
471     SqlQueryItem* item = nullptr;
472     int i = 0;
473     for (SqlQueryModelColumnPtr modelColumn : modelColumns)
474     {
475         if (!modelColumn->canEdit())
476             continue;
477 
478         item = itemsInRow[i++];
479         if (item->getValue().isNull())
480         {
481             if (CFG_UI.General.UseDefaultValueForNull.get() && modelColumn->isDefault())
482                 continue;
483 
484             if (modelColumn->isNotNull()) // value is null, but it's NOT NULL, try using DEFAULT, or fail.
485                 continue;
486 
487             if (modelColumn->isPk() && modelColumn->isAutoIncr())
488                 continue;
489         }
490 
491         colNameList << wrapObjIfNeeded(modelColumn->column);
492         sqlValues << ":arg" + QString::number(i);
493         args << item->getFullValue();
494     }
495 }
496 
updateColumnsAndValuesWithDefaultValues(const QList<SqlQueryModelColumnPtr> & modelColumns,QStringList & colNameList,QStringList & sqlValues,QList<QVariant> & args)497 void SqlTableModel::updateColumnsAndValuesWithDefaultValues(const QList<SqlQueryModelColumnPtr>& modelColumns, QStringList& colNameList,
498                                                             QStringList& sqlValues, QList<QVariant>& args)
499 {
500     // First try to find the one with DEFAULT value
501     for (SqlQueryModelColumnPtr modelColumn : modelColumns)
502     {
503         if (modelColumn->isDefault())
504         {
505             colNameList << wrapObjIfNeeded(modelColumn->column);
506             sqlValues << ":defValue";
507             args << modelColumn->getDefaultConstraint()->defaultValue;
508             return;
509         }
510     }
511 
512     // No DEFAULT, try with AUTOINCR
513     for (SqlQueryModelColumnPtr modelColumn : modelColumns)
514     {
515         if (modelColumn->isPk() && modelColumn->isAutoIncr())
516         {
517             QString colName = wrapObjIfNeeded(modelColumn->column);
518             QString tableName = wrapObjIfNeeded(table);
519             SqlQueryPtr results = db->exec("SELECT max("+colName+") FROM "+tableName);
520             qint64 rowid = 0;
521             QVariant cellValue = results->getSingleCell();
522             if (!cellValue.isNull())
523                 rowid = cellValue.toLongLong();
524 
525             colNameList << wrapObjIfNeeded(modelColumn->column);
526             sqlValues << ":defValue";
527             args << rowid;
528             return;
529         }
530     }
531 
532     // No luck with AUTOINCR either, put NULL and if there's a NOT NULL in any column,
533     // user will get the proper error message from Sqlite.
534     colNameList << wrapObjIfNeeded(modelColumns[0]->column);
535     sqlValues << ":defValue";
536     args << QVariant();
537 }
538 
build()539 QString SqlTableModel::CommitDeleteQueryBuilder::build()
540 {
541     QString dbAndTable;
542     if (!database.isNull())
543         dbAndTable += database+".";
544 
545     dbAndTable += table;
546     QString conditions = RowIdConditionBuilder::build();
547 
548     static_qstring(sql, "DELETE FROM %1 WHERE %2;");
549     return sql.arg(dbAndTable, conditions);
550 }
551 
552 
build()553 QString SqlTableModel::SelectColumnsQueryBuilder::build()
554 {
555     QString dbAndTable;
556     if (!database.isNull())
557         dbAndTable += database+".";
558 
559     dbAndTable += table;
560     QString conditions = RowIdConditionBuilder::build();
561 
562     static_qstring(sql, "SELECT %1 FROM %2 WHERE %3 LIMIT 1;");
563     return sql.arg(columns.join(", "), dbAndTable, conditions);
564 }
565 
addColumn(const QString & col)566 void SqlTableModel::SelectColumnsQueryBuilder::addColumn(const QString& col)
567 {
568     columns << col;
569 }
570