1 /**
2  * UGENE - Integrated Bioinformatics Tools.
3  * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4  * http://ugene.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 #include "SQLiteModDbi.h"
23 
24 #include <QCoreApplication>
25 
26 #include <U2Core/U2OpStatusUtils.h>
27 #include <U2Core/U2SafePoints.h>
28 #include <U2Core/U2SqlHelpers.h>
29 
30 #include <U2Formats/SQLiteObjectDbi.h>
31 
32 namespace U2 {
33 
34 /************************************************************************/
35 /* U2UseCommonMultiModStep                                              */
36 /************************************************************************/
U2UseCommonMultiModStep(SQLiteDbi * _sqliteDbi,const U2DataId & _masterObjId,U2OpStatus & os)37 U2UseCommonMultiModStep::U2UseCommonMultiModStep(SQLiteDbi *_sqliteDbi, const U2DataId &_masterObjId, U2OpStatus &os)
38     : sqliteDbi(_sqliteDbi),
39       valid(false),
40       masterObjId(_masterObjId) {
41     SAFE_POINT(nullptr != sqliteDbi, "NULL sqliteDbi!", );
42     QMutexLocker m(&sqliteDbi->getDbRef()->lock);
43 
44     sqliteDbi->getSQLiteModDbi()->startCommonMultiModStep(masterObjId, os);
45     if (!os.hasError()) {
46         valid = true;
47     }
48 }
49 
~U2UseCommonMultiModStep()50 U2UseCommonMultiModStep::~U2UseCommonMultiModStep() {
51     SAFE_POINT(nullptr != sqliteDbi, "NULL sqliteDbi!", );
52     QMutexLocker m(&sqliteDbi->getDbRef()->lock);
53     if (valid) {
54         U2OpStatus2Log os;
55         sqliteDbi->getSQLiteModDbi()->endCommonMultiModStep(masterObjId, os);
56     }
57 }
58 
59 /************************************************************************/
60 /* ModStepsDescriptor                                                   */
61 /************************************************************************/
62 
ModStepsDescriptor()63 ModStepsDescriptor::ModStepsDescriptor()
64     : userModStepId(-1),
65       multiModStepId(-1),
66       removeUserStepWithMulti(false) {
67 }
68 
69 /************************************************************************/
70 /* SQLiteModDbi                                                         */
71 /************************************************************************/
72 QMap<U2DataId, ModStepsDescriptor> SQLiteModDbi::modStepsByObject;
73 
SQLiteModDbi(SQLiteDbi * dbi)74 SQLiteModDbi::SQLiteModDbi(SQLiteDbi *dbi)
75     : U2ModDbi(dbi), SQLiteChildDBICommon(dbi) {
76 }
77 
initSqlSchema(U2OpStatus & os)78 void SQLiteModDbi::initSqlSchema(U2OpStatus &os) {
79     if (os.hasError()) {
80         return;
81     }
82 
83     // UserModStep - user modification steps
84     //   id                     - id of the user modifications step
85     //   object, otype, oextra  - data id of the master object (i.e. object for which "undo/redo" was initiated)
86     //   version                - master object was modified from this version
87     SQLiteWriteQuery("CREATE TABLE UserModStep (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
88                      " object INTEGER NOT NULL,"
89                      " otype INTEGER NOT NULL,"
90                      " oextra BLOB NOT NULL,"
91                      " version INTEGER NOT NULL, "
92                      " FOREIGN KEY(object) REFERENCES Object(id) ON DELETE CASCADE)",
93                      db,
94                      os)
95         .execute();
96 
97     // MultiModStep - multiple modifications step with reference to a user modifications step
98     //   id          - id of the multiple modifications step
99     //   userStepId  - id of the user modifications step
100     SQLiteWriteQuery("CREATE TABLE MultiModStep (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
101                      " userStepId INTEGER NOT NULL,"
102                      " FOREIGN KEY(userStepId) REFERENCES UserModStep(id) ON DELETE CASCADE)",
103                      db,
104                      os)
105         .execute();
106 
107     // SingleModStep - single modification of a dbi object
108     //   id                    - id of the modification
109     //   object, otype, oextra - data id of the object that was modified
110     //   version               - this is a modification from 'version' to 'version + 1' of the object
111     //   modType               - type of the object modification
112     //   details               - detailed description of the object modification
113     //   multiStepId           - id of the multiModStep
114     SQLiteWriteQuery("CREATE TABLE SingleModStep (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
115                      " object INTEGER NOT NULL,"
116                      " otype INTEGER NOT NULL,"
117                      " oextra BLOB NOT NULL,"
118                      " version INTEGER NOT NULL,"
119                      " modType INTEGER NOT NULL,"
120                      " details TEXT NOT NULL,"
121                      " multiStepId INTEGER NOT NULL, "
122                      " FOREIGN KEY(object) REFERENCES Object(id) ON DELETE CASCADE, "
123                      " FOREIGN KEY(multiStepId) REFERENCES MultiModStep(id) ON DELETE CASCADE)",
124                      db,
125                      os)
126         .execute();
127     SQLiteWriteQuery("CREATE INDEX SingleModStep_object ON SingleModStep(object)", db, os).execute();
128     SQLiteWriteQuery("CREATE INDEX SingleModStep_object_version ON SingleModStep(object, version)", db, os).execute();
129 }
130 
getModStep(const U2DataId & objectId,qint64 trackVersion,U2OpStatus & os)131 U2SingleModStep SQLiteModDbi::getModStep(const U2DataId &objectId, qint64 trackVersion, U2OpStatus &os) {
132     U2SingleModStep res;
133     SQLiteReadQuery q("SELECT id, object, otype, oextra, version, modType, details, multiStepId FROM SingleModStep WHERE object = ?1 AND version = ?2 ORDER BY id", db, os);
134     SAFE_POINT_OP(os, res);
135 
136     q.bindDataId(1, objectId);
137     q.bindInt64(2, trackVersion);
138 
139     if (q.step()) {
140         res.id = q.getInt64(0);
141         res.objectId = q.getDataIdExt(1);
142         res.version = q.getInt64(4);
143         res.modType = q.getInt64(5);
144         res.details = q.getBlob(6);
145         q.ensureDone();
146     } else if (!os.hasError()) {
147         os.setError(U2DbiL10n::tr("An object single modification step not found!"));
148     }
149 
150     return res;
151 }
152 
getNearestUserModStepVersion(const U2DataId & masterObjId,qint64 version,U2OpStatus & os)153 qint64 SQLiteModDbi::getNearestUserModStepVersion(const U2DataId &masterObjId, qint64 version, U2OpStatus &os) {
154     SQLiteReadQuery qVersion("SELECT MAX(version) FROM UserModStep WHERE object = ?1 AND version <= ?2", db, os);
155     qVersion.bindDataId(1, masterObjId);
156     qVersion.bindInt64(2, version);
157 
158     qint64 userStepVersion = version;
159     if (qVersion.step()) {
160         userStepVersion = qVersion.getInt64(0);
161     }
162     SAFE_POINT_OP(os, userStepVersion);
163     return userStepVersion;
164 }
165 
getModSteps(const U2DataId & masterObjId,qint64 version,U2OpStatus & os)166 QList<QList<U2SingleModStep>> SQLiteModDbi::getModSteps(const U2DataId &masterObjId, qint64 version, U2OpStatus &os) {
167     QList<QList<U2SingleModStep>> steps;
168     SQLiteTransaction t(db, os);
169 
170     qint64 userStepId = -1;
171     SQLiteWriteQuery qGetUserStepId("SELECT id FROM UserModStep WHERE object = ?1 AND version = ?2", db, os);
172     SAFE_POINT_OP(os, QList<QList<U2SingleModStep>>());
173 
174     qGetUserStepId.bindDataId(1, masterObjId);
175     qGetUserStepId.bindInt64(2, version);
176 
177     if (qGetUserStepId.step()) {
178         userStepId = qGetUserStepId.getInt64(0);
179         qGetUserStepId.ensureDone();
180     } else if (!os.hasError()) {
181         os.setError("Failed to find user step ID!");
182         return steps;
183     }
184 
185     SQLiteReadQuery qMultiStepId("SELECT id FROM MultiModStep WHERE userStepId = ?1", db, os);
186     qMultiStepId.bindInt64(1, userStepId);
187 
188     SQLiteReadQuery qSingleStep("SELECT id, object, otype, oextra, version, modType, details, multiStepId FROM SingleModStep WHERE multiStepId = ?1", db, os);
189     while (qMultiStepId.step()) {
190         qint64 multiStepId = qMultiStepId.getInt64(0);
191 
192         qSingleStep.reset();
193         qSingleStep.bindInt64(1, multiStepId);
194 
195         QList<U2SingleModStep> currentMultiStepSingleSteps;
196 
197         while (qSingleStep.step()) {
198             U2SingleModStep step;
199             step.id = qSingleStep.getInt64(0);
200             step.objectId = qSingleStep.getDataIdExt(1);
201             step.version = qSingleStep.getInt64(4);
202             step.modType = qSingleStep.getInt64(5);
203             step.details = qSingleStep.getBlob(6);
204 
205             SAFE_POINT_OP(os, QList<QList<U2SingleModStep>>());
206             currentMultiStepSingleSteps.append(step);
207         }
208         steps.append(currentMultiStepSingleSteps);
209     }
210     return steps;
211 }
212 
createModStep(const U2DataId & masterObjId,U2SingleModStep & step,U2OpStatus & os)213 void SQLiteModDbi::createModStep(const U2DataId &masterObjId, U2SingleModStep &step, U2OpStatus &os) {
214     SQLiteTransaction t(db, os);
215     bool closeMultiStep = false;
216     if (!isMultiStepStarted(masterObjId)) {
217         startCommonMultiModStep(masterObjId, os);
218         SAFE_POINT_OP(os, );
219         SAFE_POINT(isMultiStepStarted(masterObjId), "A multiple modifications step must have been started!", );
220         closeMultiStep = true;
221     }
222 
223     SQLiteWriteQuery qSingle("INSERT INTO SingleModStep(object, otype, oextra, version, modType, details, multiStepId) VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7) ", db, os);
224     SAFE_POINT_OP(os, );
225 
226     qSingle.bindDataId(1, step.objectId);
227     qSingle.bindType(2, U2DbiUtils::toType(step.objectId));
228     qSingle.bindBlob(3, U2DbiUtils::toDbExtra(step.objectId));
229     qSingle.bindInt64(4, step.version);
230     qSingle.bindInt64(5, step.modType);
231     qSingle.bindBlob(6, step.details);
232     qSingle.bindInt64(7, modStepsByObject[masterObjId].multiModStepId);
233 
234     step.id = qSingle.insert();
235     step.multiStepId = modStepsByObject[masterObjId].multiModStepId;
236 
237     if (closeMultiStep) {
238         endCommonMultiModStep(masterObjId, os);
239     }
240 }
241 
removeModsWithGreaterVersion(const U2DataId & masterObjId,qint64 masterObjVersion,U2OpStatus & os)242 void SQLiteModDbi::removeModsWithGreaterVersion(const U2DataId &masterObjId, qint64 masterObjVersion, U2OpStatus &os) {
243     SQLiteTransaction t(db, os);
244 
245     // Get user step IDs
246     QList<qint64> userStepIds;
247     SQLiteReadQuery qSelectUserSteps("SELECT id FROM UserModStep WHERE object = ?1 AND version >= ?2", db, os);
248     SAFE_POINT_OP(os, );
249 
250     qSelectUserSteps.bindDataId(1, masterObjId);
251     qSelectUserSteps.bindInt64(2, masterObjVersion);
252 
253     while (qSelectUserSteps.step()) {
254         qint64 userStepId = qSelectUserSteps.getInt64(0);
255         userStepIds.append(userStepId);
256     }
257     SAFE_POINT_OP(os, );
258 
259     // Remove all affected steps (user, multi, single)
260     removeSteps(userStepIds, os);
261     SAFE_POINT_OP(os, );
262 }
263 
removeDuplicateUserStep(const U2DataId & masterObjId,qint64 masterObjVersion,U2OpStatus & os)264 void SQLiteModDbi::removeDuplicateUserStep(const U2DataId &masterObjId, qint64 masterObjVersion, U2OpStatus &os) {
265     SQLiteTransaction t(db, os);
266 
267     // Get user step IDs
268     QList<qint64> userStepIds;
269     SQLiteReadQuery qSelect("SELECT id FROM UserModStep WHERE object = ?1 AND version = ?2", db, os);
270     SAFE_POINT_OP(os, );
271 
272     qSelect.bindDataId(1, masterObjId);
273     qSelect.bindInt64(2, masterObjVersion);
274 
275     while (qSelect.step()) {
276         qint64 id = qSelect.getInt64(0);
277         userStepIds.append(id);
278     }
279     SAFE_POINT_OP(os, );
280 
281     if (userStepIds.count() < 2) {
282         return;
283     }
284 
285     assert(2 == userStepIds.count());
286 
287     // Don't take into account user step with the greatest id
288     userStepIds.removeLast();
289 
290     // Remove user step with lower ID
291     removeSteps(userStepIds, os);
292 }
293 
removeSteps(QList<qint64> userStepIds,U2OpStatus & os)294 void SQLiteModDbi::removeSteps(QList<qint64> userStepIds, U2OpStatus &os) {
295     if (userStepIds.isEmpty()) {
296         return;
297     }
298 
299     SQLiteTransaction t(db, os);
300 
301     // Get multiple steps IDs
302     QList<qint64> multiStepIds;
303     SQLiteReadQuery qSelectMultiSteps("SELECT id FROM MultiModStep WHERE userStepId = ?1", db, os);
304     SAFE_POINT_OP(os, );
305     foreach (qint64 userStepId, userStepIds) {
306         qSelectMultiSteps.reset();
307         qSelectMultiSteps.bindInt64(1, userStepId);
308 
309         while (qSelectMultiSteps.step()) {
310             qint64 multiStepId = qSelectMultiSteps.getInt64(0);
311             multiStepIds.append(multiStepId);
312         }
313     }
314 
315     // Remove single steps
316     SQLiteWriteQuery qDeleteSingleSteps("DELETE FROM SingleModStep WHERE multiStepId = ?1", db, os);
317     SAFE_POINT_OP(os, );
318     foreach (qint64 multiStepId, multiStepIds) {
319         qDeleteSingleSteps.reset();
320         qDeleteSingleSteps.bindInt64(1, multiStepId);
321         qDeleteSingleSteps.execute();
322     }
323     SAFE_POINT_OP(os, );
324 
325     // Remove multiple steps
326     SQLiteWriteQuery qDeleteMultiSteps("DELETE FROM MultiModStep WHERE id = ?1", db, os);
327     SAFE_POINT_OP(os, );
328     foreach (qint64 multiStepId, multiStepIds) {
329         qDeleteMultiSteps.reset();
330         qDeleteMultiSteps.bindInt64(1, multiStepId);
331         qDeleteMultiSteps.execute();
332     }
333 
334     // Remove user steps
335     SQLiteWriteQuery qDeleteUserSteps("DELETE FROM UserModStep WHERE id = ?1", db, os);
336     foreach (qint64 userStepId, userStepIds) {
337         qDeleteUserSteps.reset();
338         qDeleteUserSteps.bindInt64(1, userStepId);
339         qDeleteUserSteps.execute();
340     }
341 }
342 
removeObjectMods(const U2DataId & objectId,U2OpStatus & os)343 void SQLiteModDbi::removeObjectMods(const U2DataId &objectId, U2OpStatus &os) {
344     SQLiteTransaction t(db, os);
345 
346     // Get user step IDs
347     QList<qint64> userStepIds;
348     SQLiteReadQuery qSelectUserSteps("SELECT id FROM UserModStep WHERE object = ?1", db, os);
349     SAFE_POINT_OP(os, );
350 
351     qSelectUserSteps.bindDataId(1, objectId);
352 
353     while (qSelectUserSteps.step()) {
354         qint64 userStepId = qSelectUserSteps.getInt64(0);
355         userStepIds.append(userStepId);
356     }
357     SAFE_POINT_OP(os, );
358 
359     // Remove all affected steps (user, multi, single)
360     removeSteps(userStepIds, os);
361     SAFE_POINT_OP(os, );
362 }
363 
cleanUpAllStepsOnError()364 void SQLiteModDbi::cleanUpAllStepsOnError() {
365     U2OpStatus2Log os;
366     SQLiteTransaction t(db, os);
367 
368     SQLiteWriteQuery("DELETE FROM SingleModStep", db, os).execute();
369     SQLiteWriteQuery("DELETE FROM MultiModStep", db, os).execute();
370     SQLiteWriteQuery("DELETE FROM UserModStep", db, os).execute();
371 }
372 
checkMainThread(U2OpStatus & os)373 static void checkMainThread(U2OpStatus &os) {
374     QThread *mainThread = QCoreApplication::instance()->thread();
375     QThread *thisThread = QThread::currentThread();
376 
377     if (mainThread != thisThread) {
378         os.setError("Not main thread");
379     }
380 }
381 
startCommonUserModStep(const U2DataId & masterObjId,U2OpStatus & os)382 void SQLiteModDbi::startCommonUserModStep(const U2DataId &masterObjId, U2OpStatus &os) {
383     checkMainThread(os);
384     CHECK_OP(os, );
385     SQLiteTransaction t(db, os);
386 
387     // Only one common step at a time
388     if (isUserStepStarted(masterObjId)) {
389         os.setError("Can't create a common user modifications step, previous one is not complete!");
390         return;
391     }
392 
393     if (!modStepsByObject.contains(masterObjId)) {
394         modStepsByObject[masterObjId] = ModStepsDescriptor();
395     }
396 
397     // Create a new user modifications step in the database
398     createUserModStep(masterObjId, os);
399     SAFE_POINT_OP(os, );
400 }
401 
endCommonUserModStep(const U2DataId & userMasterObjId,U2OpStatus & os)402 void SQLiteModDbi::endCommonUserModStep(const U2DataId &userMasterObjId, U2OpStatus &os) {
403     checkMainThread(os);
404     CHECK_OP(os, );
405     SAFE_POINT(modStepsByObject.contains(userMasterObjId), QString("There are not modification steps for object with id %1").arg(userMasterObjId.toLong()), );
406 
407     qint64 userModStepId = modStepsByObject[userMasterObjId].userModStepId;
408     qint64 multiModStepId = modStepsByObject[userMasterObjId].multiModStepId;
409 
410     modStepsByObject.remove(userMasterObjId);
411 
412     if (-1 == multiModStepId) {
413         SQLiteTransaction t(db, os);
414 
415         // Get multiple steps IDs
416         SQLiteReadQuery qSelectMultiSteps("SELECT id FROM MultiModStep WHERE userStepId = ?1", db, os);
417         SAFE_POINT_OP(os, );
418 
419         qSelectMultiSteps.bindInt64(1, userModStepId);
420 
421         // If user modification step doesn't contain any multi modification steps
422         if (!qSelectMultiSteps.step()) {
423             SQLiteWriteQuery qDeleteUserSteps("DELETE FROM UserModStep WHERE id = ?1", db, os);
424             qDeleteUserSteps.bindInt64(1, userModStepId);
425             qDeleteUserSteps.execute();
426             SAFE_POINT_OP(os, );
427         }
428     }
429 }
430 
startCommonMultiModStep(const U2DataId & userMasterObjId,U2OpStatus & os)431 void SQLiteModDbi::startCommonMultiModStep(const U2DataId &userMasterObjId, U2OpStatus &os) {
432     SQLiteTransaction t(db, os);
433     if (!modStepsByObject.contains(userMasterObjId)) {
434         modStepsByObject[userMasterObjId] = ModStepsDescriptor();
435     }
436     if (!isUserStepStarted(userMasterObjId)) {
437         startCommonUserModStep(userMasterObjId, os);
438         SAFE_POINT_OP(os, );
439         SAFE_POINT(isUserStepStarted(userMasterObjId), "A user modifications step must have been started!", );
440         modStepsByObject[userMasterObjId].removeUserStepWithMulti = true;
441     } else {
442         modStepsByObject[userMasterObjId].removeUserStepWithMulti = false;
443     }
444 
445     if (isMultiStepStarted(userMasterObjId)) {
446         os.setError("Can't create a common multiple modifications step, previous one is not complete!");
447         U2OpStatus2Log innerOs;
448         endCommonUserModStep(userMasterObjId, innerOs);
449         return;
450     }
451 
452     // Create a new multiple modifications step in the database
453     createMultiModStep(userMasterObjId, os);
454     SAFE_POINT_OP(os, );
455 }
456 
endCommonMultiModStep(const U2DataId & masterObjId,U2OpStatus & os)457 void SQLiteModDbi::endCommonMultiModStep(const U2DataId &masterObjId, U2OpStatus &os) {
458     if (modStepsByObject[masterObjId].removeUserStepWithMulti) {
459         endCommonUserModStep(masterObjId, os);
460     } else {
461         modStepsByObject[masterObjId].multiModStepId = -1;
462     }
463 }
464 
createUserModStep(const U2DataId & masterObjId,U2OpStatus & os)465 void SQLiteModDbi::createUserModStep(const U2DataId &masterObjId, U2OpStatus &os) {
466     qint64 masterObjVersion = dbi->getSQLiteObjectDbi()->getObjectVersion(masterObjId, os);
467     SAFE_POINT_OP(os, );
468 
469     SQLiteWriteQuery qUser("INSERT INTO UserModStep(object, otype, oextra, version) VALUES(?1, ?2, ?3, ?4)", db, os);
470     SAFE_POINT_OP(os, );
471 
472     qUser.bindDataId(1, masterObjId);
473     qUser.bindType(2, U2DbiUtils::toType(masterObjId));
474     qUser.bindBlob(3, U2DbiUtils::toDbExtra(masterObjId));
475     qUser.bindInt64(4, masterObjVersion);
476 
477     qint64 curUserModStepId = qUser.insert();
478 
479     if (-1 == curUserModStepId) {
480         os.setError("Failed to create a common user modifications step!");
481         return;
482     } else {
483         modStepsByObject[masterObjId].userModStepId = curUserModStepId;
484     }
485 }
486 
createMultiModStep(const U2DataId & masterObjId,U2OpStatus & os)487 void SQLiteModDbi::createMultiModStep(const U2DataId &masterObjId, U2OpStatus &os) {
488     SAFE_POINT(isUserStepStarted(masterObjId), "A user modifications step must have been started!", );
489 
490     SQLiteWriteQuery qMulti("INSERT INTO MultiModStep(userStepId) VALUES(?1)", db, os);
491     SAFE_POINT_OP(os, );
492 
493     qMulti.bindInt64(1, modStepsByObject[masterObjId].userModStepId);
494 
495     qint64 curMultiModStepId = qMulti.insert();
496 
497     if (-1 == curMultiModStepId) {
498         os.setError("Failed to create a common multiple modifications step!");
499         return;
500     } else {
501         modStepsByObject[masterObjId].multiModStepId = curMultiModStepId;
502     }
503 }
504 
isUserStepStarted(const U2DataId & userMasterObjId)505 bool SQLiteModDbi::isUserStepStarted(const U2DataId &userMasterObjId) {
506     if (!modStepsByObject.contains(userMasterObjId)) {
507         return false;
508     }
509     return modStepsByObject[userMasterObjId].userModStepId != -1;
510 }
isMultiStepStarted(const U2DataId & userMasterObjId)511 bool SQLiteModDbi::isMultiStepStarted(const U2DataId &userMasterObjId) {
512     if (!modStepsByObject.contains(userMasterObjId)) {
513         return false;
514     }
515     return modStepsByObject[userMasterObjId].multiModStepId != -1;
516 }
517 
canUndo(const U2DataId & objectId,U2OpStatus & os)518 bool SQLiteModDbi::canUndo(const U2DataId &objectId, U2OpStatus &os) {
519     SQLiteTransaction t(db, os);
520 
521     // Get current object version
522     qint64 objVersion = dbi->getSQLiteObjectDbi()->getObjectVersion(objectId, os);
523     SAFE_POINT_OP(os, false);
524 
525     // Verify if there are steps
526     SQLiteReadQuery q("SELECT id FROM UserModStep WHERE object = ?1 AND version < ?2", db, os);
527     SAFE_POINT_OP(os, false);
528 
529     q.bindDataId(1, objectId);
530     q.bindInt64(2, objVersion);
531 
532     if (q.step()) {
533         return true;
534     }
535 
536     return false;
537 }
538 
canRedo(const U2DataId & objectId,U2OpStatus & os)539 bool SQLiteModDbi::canRedo(const U2DataId &objectId, U2OpStatus &os) {
540     SQLiteTransaction t(db, os);
541 
542     // Get current object version
543     qint64 objVersion = dbi->getSQLiteObjectDbi()->getObjectVersion(objectId, os);
544     SAFE_POINT_OP(os, false);
545 
546     // Verify if there are steps
547     SQLiteReadQuery q("SELECT id FROM UserModStep WHERE object = ?1 AND version >= ?2", db, os);
548     SAFE_POINT_OP(os, false);
549 
550     q.bindDataId(1, objectId);
551     q.bindInt64(2, objVersion);
552 
553     if (q.step()) {
554         return true;
555     }
556 
557     return false;
558 }
559 
560 }  // namespace U2
561