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, ®ExpFilterValueProcessor);
248 }
249
applyRegExpFilter(const QStringList & values)250 void SqlTableModel::applyRegExpFilter(const QStringList& values)
251 {
252 applyFilter(values, ®ExpFilterValueProcessor);
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