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 "MysqlFeatureDbi.h"
23 
24 #include <QQueue>
25 
26 #include <U2Core/U2SafePoints.h>
27 
28 #include "MysqlObjectDbi.h"
29 #include "util/MysqlDbiUtils.h"
30 #include "util/MysqlHelpers.h"
31 
32 namespace U2 {
33 
MysqlFeatureDbi(MysqlDbi * dbi)34 MysqlFeatureDbi::MysqlFeatureDbi(MysqlDbi *dbi)
35     : U2FeatureDbi(dbi), MysqlChildDbiCommon(dbi) {
36 }
37 
initSqlSchema(U2OpStatus & os)38 void MysqlFeatureDbi::initSqlSchema(U2OpStatus &os) {
39     MysqlTransaction t(db, os);
40 
41     // nameHash is used for better indexing
42     U2SqlQuery("CREATE TABLE Feature (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
43                "class INTEGER NOT NULL, type INTEGER NOT NULL, parent BIGINT, root BIGINT, nameHash INTEGER, name TEXT, "
44                "sequence BIGINT, strand INTEGER NOT NULL DEFAULT 0, start BIGINT NOT NULL DEFAULT 0, "
45                "len BIGINT NOT NULL DEFAULT 0, end BIGINT NOT NULL DEFAULT 0) ENGINE=InnoDB DEFAULT CHARSET=utf8",
46                db,
47                os)
48         .execute();
49 
50     U2SqlQuery("CREATE TABLE FeatureKey (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, feature BIGINT NOT NULL, "
51                " name TEXT NOT NULL, value TEXT, "
52                " FOREIGN KEY(feature) REFERENCES Feature(id) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8",
53                db,
54                os)
55         .execute();
56 
57     U2SqlQuery("CREATE TABLE AnnotationTable (object BIGINT PRIMARY KEY, rootId BIGINT NOT NULL, "
58                "FOREIGN KEY(object) REFERENCES Object(id) ON DELETE CASCADE, "
59                "FOREIGN KEY(rootId) REFERENCES Feature(id) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8",
60                db,
61                os)
62         .execute();
63 
64     U2SqlQuery("CREATE INDEX FeatureRootIndex ON Feature(root, class)", db, os).execute();
65     U2SqlQuery("CREATE INDEX FeatureParentIndex ON Feature(parent)", db, os).execute();
66     U2SqlQuery("CREATE INDEX FeatureLocationIndex ON Feature(start, end)", db, os).execute();
67     U2SqlQuery("CREATE INDEX FeatureNameIndex ON Feature(root, nameHash)", db, os).execute();
68 
69     // FeatureKey index
70     U2SqlQuery("CREATE INDEX FeatureKeyIndex ON FeatureKey(feature)", db, os).execute();
71 }
72 
73 namespace {
74 
getFeatureFields(const QString & featureAlias="f")75 inline QString getFeatureFields(const QString &featureAlias = "f") {
76     return QString("%1.id, %1.class, %1.type, %1.parent, %1.root, %1.name, %1.sequence, "
77                    "%1.strand, %1.start, %1.len ")
78         .arg(featureAlias);
79 }
80 
81 }  // namespace
82 
83 class MysqlFeatureRSLoader : public MysqlRSLoader<U2Feature> {
84 public:
load(U2SqlQuery * q)85     U2Feature load(U2SqlQuery *q) {
86         return loadStatic(q);
87     }
88 
loadStatic(U2SqlQuery * q)89     static U2Feature loadStatic(U2SqlQuery *q) {
90         U2Feature res;
91         // class, type, parent, root, name, sequence, strand, start, len
92         res.id = q->getDataId(0, U2Type::Feature);
93         res.featureClass = static_cast<U2Feature::FeatureClass>(q->getInt32(1));
94         res.featureType = static_cast<U2FeatureType>(q->getInt32(2));
95         res.parentFeatureId = q->getDataId(3, U2Type::Feature);
96         res.rootFeatureId = q->getDataId(4, U2Type::Feature);
97         res.name = q->getString(5);
98         res.sequenceId = q->getDataId(6, U2Type::Sequence);
99         res.location.strand = U2Strand(U2Strand::Direction(q->getInt32(7)));
100         res.location.region.startPos = q->getInt64(8);
101         res.location.region.length = q->getInt64(9);
102         return res;
103     }
104 };
105 
106 class MysqlFeatureFilter : public MysqlRSFilter<U2Feature> {
107 public:
MysqlFeatureFilter(const QString & name,const U2DataId & _seqId)108     MysqlFeatureFilter(const QString &name, const U2DataId &_seqId) {
109         nameToFilter = name;
110         seqId = _seqId;
111     }
filter(const U2Feature & f)112     bool filter(const U2Feature &f) {
113         if ((nameToFilter.isEmpty() || f.name == nameToFilter) && (seqId.isEmpty() || seqId == f.sequenceId)) {
114             return true;
115         }
116         return false;
117     }
118 
119 private:
120     QString nameToFilter;
121     U2DataId seqId;
122 };
123 
createAnnotationTableObject(U2AnnotationTable & table,const QString & folder,U2OpStatus & os)124 void MysqlFeatureDbi::createAnnotationTableObject(U2AnnotationTable &table,
125                                                   const QString &folder,
126                                                   U2OpStatus &os) {
127     MysqlTransaction t(db, os);
128 
129     dbi->getMysqlObjectDbi()->createObject(table, folder, U2DbiObjectRank_TopLevel, os);
130     CHECK_OP(os, );
131 
132     static const QString queryString("INSERT INTO AnnotationTable (object, rootId) VALUES(:id, :rootId)");
133     U2SqlQuery q(queryString, db, os);
134     q.bindDataId(":id", table.id);
135     q.bindDataId(":rootId", table.rootFeature);
136     q.insert();
137 }
138 
getAnnotationTableObject(const U2DataId & tableId,U2OpStatus & os)139 U2AnnotationTable MysqlFeatureDbi::getAnnotationTableObject(const U2DataId &tableId,
140                                                             U2OpStatus &os) {
141     U2AnnotationTable result;
142     DBI_TYPE_CHECK(tableId, U2Type::AnnotationTable, os, result);
143 
144     static const QString queryString = "SELECT rootId, name FROM AnnotationTable, Object WHERE object = :id1 AND id = :id2";
145     U2SqlQuery q(queryString, db, os);
146     q.bindDataId(":id1", tableId);
147     q.bindDataId(":id2", tableId);
148     if (q.step()) {
149         result.rootFeature = q.getDataId(0, U2Type::Feature);
150         result.visualName = q.getString(1);
151         q.ensureDone();
152     } else if (!os.hasError()) {
153         os.setError(U2DbiL10n::tr("Annotation table object is not found."));
154     }
155     result.id = tableId;
156 
157     return result;
158 }
159 
removeAnnotationTableData(const U2DataId & tableId,U2OpStatus & os)160 void MysqlFeatureDbi::removeAnnotationTableData(const U2DataId &tableId, U2OpStatus &os) {
161     DBI_TYPE_CHECK(tableId, U2Type::AnnotationTable, os, );
162 
163     MysqlTransaction t(db, os);
164 
165     U2SqlQuery removeFeaturesQuery("DELETE F.* FROM Feature AS F INNER JOIN AnnotationTable AS A "
166                                    "ON A.rootId = F.root OR A.rootId = F.id WHERE A.object = :object",
167                                    db,
168                                    os);
169     removeFeaturesQuery.bindDataId(":object", tableId);
170     removeFeaturesQuery.execute();
171 }
172 
getFeature(const U2DataId & featureId,U2OpStatus & os)173 U2Feature MysqlFeatureDbi::getFeature(const U2DataId &featureId, U2OpStatus &os) {
174     U2Feature res;
175     DBI_TYPE_CHECK(featureId, U2Type::Feature, os, res);
176 
177     const QString queryString("SELECT " + getFeatureFields() + " FROM Feature AS f WHERE id = :id");
178     U2SqlQuery q(queryString, db, os);
179     q.bindDataId(":id", featureId);
180     if (q.step()) {
181         res = MysqlFeatureRSLoader::loadStatic(&q);
182     } else if (!os.hasError()) {
183         os.setError(U2DbiL10n::tr("Feature is not found."));
184     }
185 
186     return res;
187 }
188 
189 namespace {
190 
add(QString & buf,const QString & str,const QString & op,const QString & placeholder,int & n)191 void add(QString &buf, const QString &str, const QString &op, const QString &placeholder, int &n) {
192     if (!buf.isEmpty()) {
193         buf += " AND ";
194     }
195     buf += str;
196     if (!op.isEmpty()) {
197         n++;
198         buf += op + ":" + placeholder + QString::number(n);
199     }
200 }
201 
toSqlCompareOp(ComparisonOp op)202 QString toSqlCompareOp(ComparisonOp op) {
203     QString res;
204     switch (op) {
205         case ComparisonOp_EQ:
206             res = "=";
207             break;
208         case ComparisonOp_NEQ:
209             res = "!=";
210             break;
211         case ComparisonOp_GT:
212             res = ">";
213             break;
214         case ComparisonOp_GET:
215             res = ">=";
216             break;
217         case ComparisonOp_LT:
218             res = "<";
219             break;
220         case ComparisonOp_LET:
221             res = "<=";
222             break;
223         default:
224             res = "?";
225             break;
226     }
227     return res;
228 }
229 
toSqlOrderOp(OrderOp op)230 QString toSqlOrderOp(OrderOp op) {
231     QString res;
232     switch (op) {
233         case OrderOp_Asc:
234             res = "ASC";
235             break;
236         case OrderOp_Desc:
237             res = "DESC";
238             break;
239         default:
240             break;
241     }
242     return res;
243 }
244 
toSqlOrderOpFromCompareOp(ComparisonOp op)245 QString toSqlOrderOpFromCompareOp(ComparisonOp op) {
246     QString res;
247     switch (op) {
248         case ComparisonOp_EQ:
249             break;
250         case ComparisonOp_NEQ:
251             break;
252         case ComparisonOp_GT:
253             res = "ASC";
254             break;
255         case ComparisonOp_GET:
256             res = "ASC";
257             break;
258         case ComparisonOp_LT:
259             res = "DESC";
260             break;
261         case ComparisonOp_LET:
262             res = "DESC";
263             break;
264         default:
265             break;
266     }
267     return res;
268 }
269 
getWhereQueryPartFromType(const QString & featurePlaceholder,const FeatureFlags & types)270 QString getWhereQueryPartFromType(const QString &featurePlaceholder, const FeatureFlags &types) {
271     QString result;
272     if (types.testFlag(U2Feature::Annotation)) {
273         result += featurePlaceholder + ".class = " + QString::number(U2Feature::Annotation);
274     }
275     if (types.testFlag(U2Feature::Group)) {
276         if (!result.isEmpty()) {
277             result += " OR ";
278         }
279         result += featurePlaceholder + ".class = " + QString::number(U2Feature::Group);
280     }
281 
282     if (!result.isEmpty()) {
283         result = " AND (" + result + ") ";
284     }
285     return result;
286 }
287 
288 }  // unnamed namespace
289 
createFeatureQuery(const QString & selectPart,const FeatureQuery & fq,bool useOrder,U2OpStatus & os)290 QSharedPointer<U2SqlQuery> MysqlFeatureDbi::createFeatureQuery(const QString &selectPart, const FeatureQuery &fq, bool useOrder, U2OpStatus &os) {
291     QString wherePart;
292     int n = 0;
293 
294     bool useParent = !fq.parentFeatureId.isEmpty();
295     if (useParent) {
296         DBI_TYPE_CHECK(fq.parentFeatureId, U2Type::Feature, os, QSharedPointer<U2SqlQuery>());
297         add(wherePart, "f.parent", "=", "parent", n);
298     } else if (fq.topLevelOnly) {
299         add(wherePart, "f.parent", "=", "parent", n);
300     }
301 
302     bool useRoot = !fq.rootFeatureId.isEmpty();
303     if (useRoot) {
304         DBI_TYPE_CHECK(fq.rootFeatureId, U2Type::Feature, os, QSharedPointer<U2SqlQuery>());
305         add(wherePart, "f.root", "=", "root", n);
306     }
307 
308     bool useClass = (U2Feature::Invalid != fq.featureClass);
309     if (useClass) {
310         add(wherePart, "f.class", "=", "class", n);
311     }
312 
313     bool useType = (U2FeatureTypes::Invalid != fq.featureType);
314     if (useType) {
315         add(wherePart, "f.type", "=", "type", n);
316     }
317 
318     bool useName = !fq.featureName.isEmpty();
319     if (useName) {
320         add(wherePart, "f.nameHash", "=", "nameHash", n);
321     }
322 
323     bool useSequence = !fq.sequenceId.isEmpty();
324     if (useSequence) {
325         DBI_TYPE_CHECK(fq.sequenceId, U2Type::Sequence, os, QSharedPointer<U2SqlQuery>());
326         add(wherePart, "f.sequence", "=", "sequence", n);
327     }
328 
329     bool useRegion = (0 < fq.intersectRegion.length);
330     bool oneClosestFeature = (ComparisonOp_Invalid != fq.closestFeature);
331     if (useRegion) {
332         if (!oneClosestFeature) {  // check if intersects
333             if (1 != fq.intersectRegion.length) {
334                 add(wherePart, QString("f.start < :endPos%2 AND f.end > :startPos%1").arg(n + 1).arg(n + 2), "", "", n);
335             } else {
336                 add(wherePart, QString("f.start < :endPos%2 AND f.start + f.len > :startPos%1").arg(n + 1).arg(n + 2), "", "", n);
337             }
338             n += 2;
339         } else {  // check if close
340             add(wherePart, QString("f.start %1 :startPos%2").arg(toSqlCompareOp(fq.closestFeature)).arg(n + 1), "", "", n);
341             n++;
342         }
343     }
344 
345     bool useStrand = (Strand_Both != fq.strandQuery);
346     if (useStrand) {
347         add(wherePart, "f.strand", "=", "strand", n);
348     }
349 
350     bool useKeyName = !fq.keyName.isEmpty();
351     if (useKeyName) {
352         add(wherePart, "fk.name", "=", "name", n);
353     }
354 
355     bool useKeyValue = !fq.keyValue.isEmpty();
356     if (useKeyValue) {
357         add(wherePart, "fk.value", toSqlCompareOp(fq.keyValueCompareOp), "value", n);
358     }
359 
360     bool useKeyTable = (useKeyName || useKeyValue);
361     if (useKeyTable) {
362         wherePart += " AND fk.feature = f.id ";
363     }
364 
365     if (useOrder) {
366         if (OrderOp_None != fq.keyNameOrderOp) {
367             wherePart += " ORDER BY fk.name " + toSqlOrderOp(fq.keyNameOrderOp);
368         } else if (OrderOp_None != fq.keyValueOrderOp) {
369             wherePart += " ORDER BY fk.value";
370         } else if (OrderOp_None != fq.startPosOrderOp && useRegion) {
371             if (!oneClosestFeature) {
372                 wherePart += " ORDER BY f.start ";
373             } else {
374                 wherePart += QString(" ORDER BY f.start %1 ")
375                                  .arg(toSqlOrderOpFromCompareOp(fq.closestFeature));
376             }
377         }
378     }
379 
380     QString tablesPart = "Feature AS f";
381     if (useKeyTable) {
382         tablesPart += ", FeatureKey AS fk";
383     }
384 
385     QString fullQuery = selectPart + " FROM " + tablesPart;
386     if (!wherePart.isEmpty()) {
387         fullQuery += " WHERE " + wherePart;
388     }
389     if (oneClosestFeature) {
390         fullQuery += " LIMIT 1 ";
391     }
392 
393     QSharedPointer<U2SqlQuery> q;
394     q = QSharedPointer<U2SqlQuery>(new U2SqlQuery(fullQuery, db, os));
395     int m = 0;
396 
397     if (useParent) {
398         q->bindDataId(":parent" + QString::number(++m), fq.parentFeatureId);
399     } else if (fq.topLevelOnly) {
400         q->bindDataId(":parent" + QString::number(++m), U2DataId());
401     }
402     if (useRoot) {
403         q->bindDataId(":root" + QString::number(++m), fq.rootFeatureId);
404     }
405     if (useClass) {
406         q->bindInt32(":class" + QString::number(++m), fq.featureClass);
407     }
408     if (useType) {
409         q->bindInt32(":type" + QString::number(++m), fq.featureType);
410     }
411     if (useName) {
412         q->bindInt32(":nameHash" + QString::number(++m), qHash(fq.featureName));
413     }
414     if (useSequence) {
415         q->bindDataId(":sequence" + QString::number(++m), fq.sequenceId);
416     }
417     if (useRegion) {
418         if (!oneClosestFeature) {
419             q->bindInt64(":startPos" + QString::number(++m), fq.intersectRegion.startPos);
420             q->bindInt64(":endPos" + QString::number(++m), fq.intersectRegion.endPos());
421         } else {
422             q->bindInt64(":startPos" + QString::number(++m), fq.intersectRegion.startPos);
423         }
424     }
425     if (useStrand) {
426         U2Strand::Direction direction = U2Strand::Direct;
427         if (Strand_Direct == fq.strandQuery) {
428             direction = U2Strand::Direct;
429         } else if (Strand_Compl == fq.strandQuery) {
430             direction = U2Strand::Complementary;
431         }
432         q->bindInt32(":strand" + QString::number(++m), direction);
433     }
434 
435     if (useKeyName) {
436         q->bindString(":name" + QString::number(++m), fq.keyName);
437     }
438     if (useKeyValue) {
439         q->bindString(":value" + QString::number(++m), fq.keyValue);
440     }
441 
442     return q;
443 }
444 
countFeatures(const FeatureQuery & fq,U2OpStatus & os)445 qint64 MysqlFeatureDbi::countFeatures(const FeatureQuery &fq, U2OpStatus &os) {
446     QSharedPointer<U2SqlQuery> q(createFeatureQuery("SELECT COUNT(*)", fq, false, os));
447     CHECK_OP(os, -1);
448 
449     return q->selectInt64();
450 }
451 
getFeatures(const FeatureQuery & fq,U2OpStatus & os)452 U2DbiIterator<U2Feature> *MysqlFeatureDbi::getFeatures(const FeatureQuery &fq, U2OpStatus &os) {
453     QSharedPointer<U2SqlQuery> q = createFeatureQuery("SELECT " + getFeatureFields(), fq, true, os);
454     CHECK_OP(os, nullptr);
455 
456     return new MysqlRSIterator<U2Feature>(q, new MysqlFeatureRSLoader(), nullptr, U2Feature(), os);
457 }
458 
getFeatureKeys(const U2DataId & featureId,U2OpStatus & os)459 QList<U2FeatureKey> MysqlFeatureDbi::getFeatureKeys(const U2DataId &featureId, U2OpStatus &os) {
460     static const QString queryString("SELECT name, value FROM FeatureKey WHERE feature = :id");
461     U2SqlQuery q(queryString, db, os);
462     q.bindDataId(":id", featureId);
463 
464     QList<U2FeatureKey> result;
465     while (q.step()) {
466         U2FeatureKey key;
467         key.name = q.getCString(0);
468         key.value = q.getCString(1);
469         result.append(key);
470     }
471 
472     return result;
473 }
474 
475 namespace {
476 
addKeyCommon(U2SqlQuery & qk,const U2DataId & featureId,const U2FeatureKey & key)477 void addKeyCommon(U2SqlQuery &qk, const U2DataId &featureId, const U2FeatureKey &key) {
478     qk.bindDataId(":feature", featureId);
479     qk.bindString(":name", key.name);
480     qk.bindString(":value", key.value);
481     qk.insert();
482 }
483 
getFeatureKeyInsertQuery(int keyCount)484 QString getFeatureKeyInsertQuery(int keyCount) {
485     SAFE_POINT(keyCount > 0, "Unexpected feature keys number", QString());
486 
487     QString queryStringk("INSERT INTO FeatureKey(feature, name, value) VALUES");
488     for (int i = 1, n = 3 * keyCount; i <= n; i += 3) {
489         queryStringk += QString("(:%1, :%2, :%3),").arg(i).arg(i + 1).arg(i + 2);
490     }
491     queryStringk.chop(1);  // remove last comma
492     return queryStringk;
493 }
494 
addFeatureKeys(const QList<U2FeatureKey> & keys,const U2DataId & featureId,MysqlDbRef * db,U2OpStatus & os)495 void addFeatureKeys(const QList<U2FeatureKey> &keys, const U2DataId &featureId, MysqlDbRef *db, U2OpStatus &os) {
496     CHECK(!keys.isEmpty(), );
497 
498     MysqlTransaction t(db, os);
499 
500     const QString insertQueryStr = getFeatureKeyInsertQuery(keys.size());
501 
502     U2SqlQuery query(insertQueryStr, db, os);
503 
504     for (int i = 1, n = 3 * keys.size(); i <= n && !os.isCoR(); i += 3) {
505         const U2FeatureKey &key = keys[(i - 1) / 3];
506         query.bindDataId(QString(":%1").arg(i), featureId);
507         query.bindString(QString(":%1").arg(i + 1), key.name);
508         query.bindString(QString(":%1").arg(i + 2), key.value);
509     }
510     query.insert();
511 }
512 
513 }  // unnamed namespace
514 
createFeature(U2Feature & feature,const QList<U2FeatureKey> & keys,U2OpStatus & os)515 void MysqlFeatureDbi::createFeature(U2Feature &feature, const QList<U2FeatureKey> &keys, U2OpStatus &os) {
516     MysqlTransaction t(db, os);
517 
518     static const QString queryStringf("INSERT INTO Feature(class, type, parent, root, name, sequence, strand, start, len, end, nameHash) "
519                                       "VALUES(:class, :type, :parent, :root, :name, :sequence, :strand, :start, :len, :end, :nameHash)");
520     U2SqlQuery qf(queryStringf, db, os);
521     qf.bindInt32(":class", feature.featureClass);
522     qf.bindInt32(":type", feature.featureType);
523     qf.bindDataId(":parent", feature.parentFeatureId);
524     qf.bindDataId(":root", feature.rootFeatureId);
525     qf.bindString(":name", feature.name);
526     qf.bindDataId(":sequence", feature.sequenceId);
527     qf.bindInt32(":strand", feature.location.strand.getDirectionValue());
528     qf.bindInt64(":start", feature.location.region.startPos);
529     qf.bindInt64(":len", feature.location.region.length);
530     qf.bindInt64(":end", feature.location.region.endPos());
531     qf.bindInt32(":nameHash", qHash(feature.name));
532     feature.id = qf.insert(U2Type::Feature);
533     CHECK_OP(os, );
534 
535     addFeatureKeys(keys, feature.id, db, os);
536 }
537 
addKey(const U2DataId & featureId,const U2FeatureKey & key,U2OpStatus & os)538 void MysqlFeatureDbi::addKey(const U2DataId &featureId, const U2FeatureKey &key, U2OpStatus &os) {
539     MysqlTransaction t(db, os);
540 
541     static const QString queryString = "INSERT INTO FeatureKey(feature, name, value) VALUES(:feature, :name, :value)";
542     U2SqlQuery qk(queryString, db, os);
543     addKeyCommon(qk, featureId, key);
544 }
545 
removeAllKeys(const U2DataId & featureId,const QString & keyName,U2OpStatus & os)546 void MysqlFeatureDbi::removeAllKeys(const U2DataId &featureId, const QString &keyName, U2OpStatus &os) {
547     DBI_TYPE_CHECK(featureId, U2Type::Feature, os, );
548 
549     MysqlTransaction t(db, os);
550 
551     static const QString queryString = "DELETE FROM FeatureKey WHERE feature = :feature AND name = :name";
552     U2SqlQuery q(queryString, db, os);
553     q.bindDataId(":feature", featureId);
554     q.bindString(":name", keyName);
555     q.execute();
556 }
557 
removeKey(const U2DataId & featureId,const U2FeatureKey & key,U2OpStatus & os)558 void MysqlFeatureDbi::removeKey(const U2DataId &featureId, const U2FeatureKey &key, U2OpStatus &os) {
559     DBI_TYPE_CHECK(featureId, U2Type::Feature, os, );
560 
561     MysqlTransaction t(db, os);
562 
563     static const QString queryString = "DELETE FROM FeatureKey WHERE feature = :feature AND name = :name AND value = :value LIMIT 1";
564     U2SqlQuery q(queryString, db, os);
565     q.bindDataId(":feature", featureId);
566     q.bindString(":name", key.name);
567     q.bindString(":value", key.value);
568     q.execute();
569 }
570 
updateName(const U2DataId & featureId,const QString & newName,U2OpStatus & os)571 void MysqlFeatureDbi::updateName(const U2DataId &featureId, const QString &newName, U2OpStatus &os) {
572     DBI_TYPE_CHECK(featureId, U2Type::Feature, os, );
573 
574     MysqlTransaction t(db, os);
575     Q_UNUSED(t);
576 
577     static const QString queryString = "UPDATE Feature SET name = :name, nameHash = :nameHash WHERE id = :id";
578     U2SqlQuery qf(queryString, db, os);
579     qf.bindDataId(":id", featureId);
580     qf.bindInt32(":nameHash", qHash(newName));
581     qf.bindString(":name", newName);
582     qf.execute();
583 }
584 
updateParentId(const U2DataId & featureId,const U2DataId & parentId,U2OpStatus & os)585 void MysqlFeatureDbi::updateParentId(const U2DataId &featureId, const U2DataId &parentId, U2OpStatus &os) {
586     DBI_TYPE_CHECK(featureId, U2Type::Feature, os, );
587     DBI_TYPE_CHECK(parentId, U2Type::Feature, os, );
588 
589     MysqlTransaction t(db, os);
590 
591     static const QString queryString = "UPDATE Feature SET parent = :parent WHERE id = :id";
592     U2SqlQuery qf(queryString, db, os);
593     qf.bindDataId(":parent", parentId);
594     qf.bindDataId(":id", featureId);
595     qf.execute();
596 }
597 
updateSequenceId(const U2DataId & featureId,const U2DataId & seqId,U2OpStatus & os)598 void MysqlFeatureDbi::updateSequenceId(const U2DataId &featureId, const U2DataId &seqId, U2OpStatus &os) {
599     DBI_TYPE_CHECK(featureId, U2Type::Feature, os, );
600     DBI_TYPE_CHECK(seqId, U2Type::Sequence, os, );
601 
602     MysqlTransaction t(db, os);
603 
604     static const QString queryString = "UPDATE Feature SET sequence = :sequence WHERE id = :id";
605     U2SqlQuery qf(queryString, db, os);
606     qf.bindDataId(":sequence", seqId);
607     qf.bindDataId(":id", featureId);
608     qf.execute();
609 }
610 
updateKeyValue(const U2DataId & featureId,const U2FeatureKey & key,U2OpStatus & os)611 void MysqlFeatureDbi::updateKeyValue(const U2DataId &featureId, const U2FeatureKey &key, U2OpStatus &os) {
612     DBI_TYPE_CHECK(featureId, U2Type::Feature, os, );
613 
614     MysqlTransaction t(db, os);
615 
616     static const QString queryString = "UPDATE FeatureKey SET value = :value WHERE feature = :feature AND name = :name";
617     U2SqlQuery q(queryString, db, os);
618     q.bindDataId(":feature", featureId);
619     q.bindString(":name", key.name);
620     q.bindString(":value", key.value);
621     q.execute();
622 }
623 
getKeyValue(const U2DataId & featureId,U2FeatureKey & key,U2OpStatus & os)624 bool MysqlFeatureDbi::getKeyValue(const U2DataId &featureId, U2FeatureKey &key, U2OpStatus &os) {
625     DBI_TYPE_CHECK(featureId, U2Type::Feature, os, false);
626 
627     static const QString queryString("SELECT value FROM FeatureKey WHERE feature = :feature AND name = :name");
628     U2SqlQuery q(queryString, db, os);
629     CHECK_OP(os, false);
630 
631     q.bindDataId(":feature", featureId);
632     q.bindString(":name", key.name);
633 
634     if (q.step()) {
635         key.value = q.getCString(0);
636         return true;
637     } else {
638         return false;
639     }
640 }
641 
updateLocation(const U2DataId & featureId,const U2FeatureLocation & location,U2OpStatus & os)642 void MysqlFeatureDbi::updateLocation(const U2DataId &featureId, const U2FeatureLocation &location, U2OpStatus &os) {
643     DBI_TYPE_CHECK(featureId, U2Type::Feature, os, );
644     MysqlTransaction t(db, os);
645 
646     static const QString updFeatureString = "UPDATE Feature SET strand = :strand, start = :start, len = :len, end = :end WHERE id = :id";
647     U2SqlQuery qf(updFeatureString, db, os);
648     qf.bindInt32(":strand", location.strand.getDirectionValue());
649     qf.bindInt64(":start", location.region.startPos);
650     qf.bindInt64(":len", location.region.length);
651     qf.bindInt64(":end", location.region.endPos());
652     qf.bindDataId(":id", featureId);
653     qf.execute();
654 }
655 
updateType(const U2DataId & featureId,U2FeatureType newType,U2OpStatus & os)656 void MysqlFeatureDbi::updateType(const U2DataId &featureId, U2FeatureType newType, U2OpStatus &os) {
657     DBI_TYPE_CHECK(featureId, U2Type::Feature, os, );
658 
659     MysqlTransaction t(db, os);
660 
661     static const QString queryString = "UPDATE Feature SET type = :type WHERE id = :id";
662     U2SqlQuery qf(queryString, db, os);
663     qf.bindDataId(":id", featureId);
664     qf.bindInt32(":type", newType);
665     qf.execute();
666 }
667 
removeFeature(const U2DataId & featureId,U2OpStatus & os)668 void MysqlFeatureDbi::removeFeature(const U2DataId &featureId, U2OpStatus &os) {
669     DBI_TYPE_CHECK(featureId, U2Type::Feature, os, );
670     MysqlTransaction t(db, os);
671 
672     QSharedPointer<U2DbiIterator<U2Feature>> subfeaturesIter(getFeaturesByParent(featureId,
673                                                                                  QString(),
674                                                                                  U2DataId(),
675                                                                                  os,
676                                                                                  NotSelectParentFeature));
677     while (subfeaturesIter->hasNext()) {
678         removeFeature(subfeaturesIter->next().id, os);
679         CHECK_OP(os, );
680     }
681 
682     static const QString featureQueryString = "DELETE FROM Feature WHERE id = :id";
683     U2SqlQuery qf(featureQueryString, db, os);
684     qf.bindDataId(":id", featureId);
685     qf.execute();
686 }
687 
removeFeaturesByParent(const U2DataId & parentId,U2OpStatus & os,SubfeatureSelectionMode mode)688 void MysqlFeatureDbi::removeFeaturesByParent(const U2DataId &parentId, U2OpStatus &os, SubfeatureSelectionMode mode) {
689     DBI_TYPE_CHECK(parentId, U2Type::Feature, os, );
690 
691     const bool includeParent = SelectParentFeature == mode;
692 
693     MysqlTransaction t(db, os);
694 
695     U2SqlQuery qf("DELETE FROM Feature WHERE parent = :parent" + (includeParent ? QString(" OR id = :id") : ""), db, os);
696     qf.bindDataId(":parent", parentId);
697     if (includeParent) {
698         qf.bindDataId(":id", parentId);
699     }
700     qf.execute();
701 }
702 
703 namespace {
704 
executeDeleteFeaturesByParentsQuery(const QList<U2DataId> & parentIds,MysqlDbRef * db,U2OpStatus & os)705 void executeDeleteFeaturesByParentsQuery(const QList<U2DataId> &parentIds, MysqlDbRef *db, U2OpStatus &os) {
706     SAFE_POINT(nullptr != db, "Invalid database handler", );
707 
708     QString idsList = "(";
709     for (int i = 1, n = parentIds.count(); i <= n; i++) {
710         idsList += QString(":%1,").arg(i);
711     }
712     idsList.chop(1);
713     idsList += ")";
714 
715     U2SqlQuery qf(QString("DELETE FROM Feature WHERE parent IN %1 OR id IN %1").arg(idsList), db, os);
716     for (int i = 1, n = parentIds.count(); i <= n; i++) {
717         qf.bindDataId(QString(":%1").arg(i), parentIds.at(i - 1));
718     }
719     qf.execute();
720 }
721 
722 }  // namespace
723 
removeFeaturesByParents(const QList<U2DataId> & parentIds,U2OpStatus & os)724 void MysqlFeatureDbi::removeFeaturesByParents(const QList<U2DataId> &parentIds, U2OpStatus &os) {
725     MysqlTransaction t(db, os);
726 
727     int parentsNumber = parentIds.count();
728     if (parentsNumber <= MysqlDbi::BIND_PARAMETERS_LIMIT) {
729         executeDeleteFeaturesByParentsQuery(parentIds, db, os);
730     } else {
731         int deletedFeaturesNumber = 0;
732         while (parentsNumber - deletedFeaturesNumber > 0) {
733             int numDeletions = parentsNumber - deletedFeaturesNumber >= MysqlDbi::BIND_PARAMETERS_LIMIT ? MysqlDbi::BIND_PARAMETERS_LIMIT : -1;
734             QList<U2DataId> copiedAnnotations = parentIds.mid(deletedFeaturesNumber, numDeletions);
735             executeDeleteFeaturesByParentsQuery(copiedAnnotations, db, os);
736             deletedFeaturesNumber += MysqlDbi::BIND_PARAMETERS_LIMIT;
737         }
738     }
739 }
740 
removeFeaturesByRoot(const U2DataId & rootId,U2OpStatus & os,SubfeatureSelectionMode mode)741 void MysqlFeatureDbi::removeFeaturesByRoot(const U2DataId &rootId, U2OpStatus &os, SubfeatureSelectionMode mode) {
742     DBI_TYPE_CHECK(rootId, U2Type::Feature, os, );
743 
744     const bool includeParent = SelectParentFeature == mode;
745 
746     MysqlTransaction t(db, os);
747 
748     U2SqlQuery qf("DELETE FROM Feature WHERE root = :root" + (includeParent ? QString(" OR id = :id") : ""), db, os);
749     qf.bindDataId(":root", rootId);
750     if (includeParent) {
751         qf.bindDataId(":id", rootId);
752     }
753     qf.execute();
754 }
755 
getFeaturesByRegion(const U2Region & reg,const U2DataId & rootId,const QString & featureName,const U2DataId & seqId,U2OpStatus & os,bool contains)756 U2DbiIterator<U2Feature> *MysqlFeatureDbi::getFeaturesByRegion(const U2Region &reg,
757                                                                const U2DataId &rootId,
758                                                                const QString &featureName,
759                                                                const U2DataId &seqId,
760                                                                U2OpStatus &os,
761                                                                bool contains) {
762     const bool selectByRoot = !rootId.isEmpty();
763     const QString queryByRegion = "SELECT " + getFeatureFields() + " FROM Feature AS f WHERE " + (selectByRoot ? QString("f.root = :root AND ") : QString()) + (contains ? QString("f.start >= %1 AND f.end <= %2").arg(reg.startPos).arg(reg.endPos() - 1) : QString("f.start <= %1 AND f.end >= %2").arg(reg.endPos() - 1).arg(reg.startPos));
764 
765     QSharedPointer<U2SqlQuery> q(new U2SqlQuery(queryByRegion, db, os));
766 
767     if (selectByRoot) {
768         q->bindDataId(":root", rootId);
769     }
770 
771     return new MysqlRSIterator<U2Feature>(q, new MysqlFeatureRSLoader(), new MysqlFeatureFilter(featureName, seqId), U2Feature(), os);
772 }
773 
getFeaturesBySequence(const QString & featureName,const U2DataId & seqId,U2OpStatus & os)774 U2DbiIterator<U2Feature> *MysqlFeatureDbi::getFeaturesBySequence(const QString &featureName,
775                                                                  const U2DataId &seqId,
776                                                                  U2OpStatus &os) {
777     static const QString queryStringk("SELECT " + getFeatureFields() + " FROM Feature AS f "
778                                                                        "WHERE f.sequence = :sequence and f.name = :name ORDER BY f.start");
779     QSharedPointer<U2SqlQuery> q(new U2SqlQuery(queryStringk, db, os));
780 
781     q->bindDataId(":sequence", seqId);
782     q->bindString(":name", featureName);
783 
784     return new MysqlRSIterator<U2Feature>(q, new MysqlFeatureRSLoader(), nullptr, U2Feature(), os);
785 }
786 
getFeaturesByParent(const U2DataId & parentId,const QString & featureName,const U2DataId & seqId,U2OpStatus & os,SubfeatureSelectionMode mode)787 U2DbiIterator<U2Feature> *MysqlFeatureDbi::getFeaturesByParent(const U2DataId &parentId,
788                                                                const QString &featureName,
789                                                                const U2DataId &seqId,
790                                                                U2OpStatus &os,
791                                                                SubfeatureSelectionMode mode) {
792     const bool includeParent = SelectParentFeature == mode;
793     const QString queryStringk("SELECT " + getFeatureFields() + " FROM Feature AS f "
794                                                                 "WHERE f.parent = :parent" +
795                                (includeParent ? " OR f.id = :id" : "") + " ORDER BY f.start");
796     QSharedPointer<U2SqlQuery> q(new U2SqlQuery(queryStringk, db, os));
797 
798     q->bindDataId(":parent", parentId);
799     if (includeParent) {
800         q->bindDataId(":id", parentId);
801     }
802 
803     return new MysqlRSIterator<U2Feature>(q, new MysqlFeatureRSLoader(), new MysqlFeatureFilter(featureName, seqId), U2Feature(), os);
804 }
805 
getFeaturesByRoot(const U2DataId & rootId,const FeatureFlags & types,U2OpStatus & os)806 U2DbiIterator<U2Feature> *MysqlFeatureDbi::getFeaturesByRoot(const U2DataId &rootId, const FeatureFlags &types, U2OpStatus &os) {
807     const QString queryStringk("SELECT " + getFeatureFields() + " FROM Feature AS f "
808                                                                 "WHERE f.root = :root" +
809                                getWhereQueryPartFromType("f", types) + "ORDER BY f.start");
810     QSharedPointer<U2SqlQuery> q(new U2SqlQuery(queryStringk, db, os));
811 
812     q->bindDataId(":root", rootId);
813     return new MysqlRSIterator<U2Feature>(q, new MysqlFeatureRSLoader(), new MysqlFeatureFilter(QString(), U2DataId()), U2Feature(), os);
814 }
815 
getFeaturesByName(const U2DataId & rootId,const QString & name,const FeatureFlags & types,U2OpStatus & os)816 U2DbiIterator<U2Feature> *MysqlFeatureDbi::getFeaturesByName(const U2DataId &rootId, const QString &name, const FeatureFlags &types, U2OpStatus &os) {
817     const QString queryStringk("SELECT " + getFeatureFields() + " FROM Feature AS f "
818                                                                 "WHERE f.root = :root" +
819                                getWhereQueryPartFromType("f", types) + " AND nameHash = :nameHash ORDER BY f.start");
820     QSharedPointer<U2SqlQuery> q(new U2SqlQuery(queryStringk, db, os));
821 
822     q->bindDataId(":root", rootId);
823     q->bindInt32(":nameHash", qHash(name));
824     CHECK_OP(os, nullptr);
825     return new MysqlRSIterator<U2Feature>(q, new MysqlFeatureRSLoader(), new MysqlFeatureFilter(QString(), U2DataId()), U2Feature(), os);
826 }
827 
getFeatureTable(const U2DataId & rootFeatureId,U2OpStatus & os)828 QList<FeatureAndKey> MysqlFeatureDbi::getFeatureTable(const U2DataId &rootFeatureId, U2OpStatus &os) {
829     static const QString queryStringk("SELECT " + getFeatureFields() + ", fk.name, fk.value FROM Feature AS f "
830                                                                        "LEFT OUTER JOIN FeatureKey AS fk ON f.id = fk.feature WHERE f.root = :root ORDER BY f.class DESC, f.start, f.len");
831     U2SqlQuery q(queryStringk, db, os);
832 
833     q.bindDataId(":root", rootFeatureId);
834 
835     QList<FeatureAndKey> result;
836     while (q.step()) {
837         FeatureAndKey fnk;
838         fnk.feature = MysqlFeatureRSLoader::loadStatic(&q);
839         fnk.key.name = q.getCString(10);
840         fnk.key.value = q.getCString(11);
841         result.append(fnk);
842     }
843 
844     return result;
845 }
846 
getAnnotationTablesByFeatureKey(const QStringList & values,U2OpStatus & os)847 QMap<U2DataId, QStringList> MysqlFeatureDbi::getAnnotationTablesByFeatureKey(const QStringList &values, U2OpStatus &os) {
848     QMap<U2DataId, QStringList> result;
849     CHECK(!values.isEmpty(), result);
850     // Pay attention here if there is the need of processing more search terms
851     CHECK_EXT(values.size() < MysqlDbi::BIND_PARAMETERS_LIMIT, os.setError("Too many search terms provided"), result);
852 
853     QString queryStringk("SELECT DISTINCT A.object, F.name FROM AnnotationTable AS A, Feature AS F, FeatureKey AS FK "
854                          "WHERE A.rootId = F.root AND F.id = FK.feature");
855 
856     for (int i = 1, n = values.size(); i <= n; ++i) {
857         queryStringk.append(QString(" AND FK.value LIKE :%1").arg(i));
858     }
859 
860     U2SqlQuery q(queryStringk, db, os);
861 
862     for (int i = 1, n = values.size(); i <= n; ++i) {
863         q.bindString(QString(":%1").arg(i), QString("%%1%").arg(values[i - 1]));
864         CHECK_OP(os, result);
865     }
866 
867     while (q.step()) {
868         result[q.getDataId(0, U2Type::AnnotationTable)].append(q.getString(1));
869     }
870 
871     return result;
872 }
873 
874 }  // namespace U2
875