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 ®,
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