1 /** 2 * Orthanc - A Lightweight, RESTful DICOM Store 3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics 4 * Department, University Hospital of Liege, Belgium 5 * Copyright (C) 2017-2020 Osimis S.A., Belgium 6 * 7 * This program is free software: you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License as 9 * published by the Free Software Foundation, either version 3 of the 10 * License, or (at your option) any later version. 11 * 12 * In addition, as a special exception, the copyright holders of this 13 * program give permission to link the code of its release with the 14 * OpenSSL project's "OpenSSL" library (or with modified versions of it 15 * that use the same license as the "OpenSSL" library), and distribute 16 * the linked executables. You must obey the GNU General Public License 17 * in all respects for all of the code used other than "OpenSSL". If you 18 * modify file(s) with this exception, you may extend this exception to 19 * your version of the file(s), but you are not obligated to do so. If 20 * you do not wish to do so, delete this exception statement from your 21 * version. If you delete this exception statement from all source files 22 * in the program, then also delete it here. 23 * 24 * This program is distributed in the hope that it will be useful, but 25 * WITHOUT ANY WARRANTY; without even the implied warranty of 26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 27 * General Public License for more details. 28 * 29 * You should have received a copy of the GNU General Public License 30 * along with this program. If not, see <http://www.gnu.org/licenses/>. 31 **/ 32 33 34 #include "../PrecompiledHeadersServer.h" 35 #include "SQLiteDatabaseWrapper.h" 36 37 #include "../../../OrthancFramework/Sources/DicomFormat/DicomArray.h" 38 #include "../../../OrthancFramework/Sources/Logging.h" 39 #include "../../../OrthancFramework/Sources/SQLite/Transaction.h" 40 #include "../Search/ISqlLookupFormatter.h" 41 #include "../ServerToolbox.h" 42 43 #include <OrthancServerResources.h> 44 45 #include <stdio.h> 46 #include <boost/lexical_cast.hpp> 47 48 namespace Orthanc 49 { 50 namespace Internals 51 { 52 class SignalFileDeleted : public SQLite::IScalarFunction 53 { 54 private: 55 IDatabaseListener& listener_; 56 57 public: SignalFileDeleted(IDatabaseListener & listener)58 SignalFileDeleted(IDatabaseListener& listener) : 59 listener_(listener) 60 { 61 } 62 GetName() const63 virtual const char* GetName() const 64 { 65 return "SignalFileDeleted"; 66 } 67 GetCardinality() const68 virtual unsigned int GetCardinality() const 69 { 70 return 7; 71 } 72 Compute(SQLite::FunctionContext & context)73 virtual void Compute(SQLite::FunctionContext& context) 74 { 75 std::string uncompressedMD5, compressedMD5; 76 77 if (!context.IsNullValue(5)) 78 { 79 uncompressedMD5 = context.GetStringValue(5); 80 } 81 82 if (!context.IsNullValue(6)) 83 { 84 compressedMD5 = context.GetStringValue(6); 85 } 86 87 FileInfo info(context.GetStringValue(0), 88 static_cast<FileContentType>(context.GetIntValue(1)), 89 static_cast<uint64_t>(context.GetInt64Value(2)), 90 uncompressedMD5, 91 static_cast<CompressionType>(context.GetIntValue(3)), 92 static_cast<uint64_t>(context.GetInt64Value(4)), 93 compressedMD5); 94 95 listener_.SignalFileDeleted(info); 96 } 97 }; 98 99 class SignalResourceDeleted : public SQLite::IScalarFunction 100 { 101 private: 102 IDatabaseListener& listener_; 103 104 public: SignalResourceDeleted(IDatabaseListener & listener)105 SignalResourceDeleted(IDatabaseListener& listener) : 106 listener_(listener) 107 { 108 } 109 GetName() const110 virtual const char* GetName() const 111 { 112 return "SignalResourceDeleted"; 113 } 114 GetCardinality() const115 virtual unsigned int GetCardinality() const 116 { 117 return 2; 118 } 119 Compute(SQLite::FunctionContext & context)120 virtual void Compute(SQLite::FunctionContext& context) 121 { 122 ResourceType type = static_cast<ResourceType>(context.GetIntValue(1)); 123 ServerIndexChange change(ChangeType_Deleted, type, context.GetStringValue(0)); 124 listener_.SignalChange(change); 125 } 126 }; 127 128 class SignalRemainingAncestor : public SQLite::IScalarFunction 129 { 130 private: 131 bool hasRemainingAncestor_; 132 std::string remainingPublicId_; 133 ResourceType remainingType_; 134 135 public: SignalRemainingAncestor()136 SignalRemainingAncestor() : 137 hasRemainingAncestor_(false) 138 { 139 } 140 Reset()141 void Reset() 142 { 143 hasRemainingAncestor_ = false; 144 } 145 GetName() const146 virtual const char* GetName() const 147 { 148 return "SignalRemainingAncestor"; 149 } 150 GetCardinality() const151 virtual unsigned int GetCardinality() const 152 { 153 return 2; 154 } 155 Compute(SQLite::FunctionContext & context)156 virtual void Compute(SQLite::FunctionContext& context) 157 { 158 CLOG(TRACE, SQLITE) << "There exists a remaining ancestor with public ID \"" 159 << context.GetStringValue(0) << "\" of type " 160 << context.GetIntValue(1); 161 162 if (!hasRemainingAncestor_ || 163 remainingType_ >= context.GetIntValue(1)) 164 { 165 hasRemainingAncestor_ = true; 166 remainingPublicId_ = context.GetStringValue(0); 167 remainingType_ = static_cast<ResourceType>(context.GetIntValue(1)); 168 } 169 } 170 HasRemainingAncestor() const171 bool HasRemainingAncestor() const 172 { 173 return hasRemainingAncestor_; 174 } 175 GetRemainingAncestorId() const176 const std::string& GetRemainingAncestorId() const 177 { 178 assert(hasRemainingAncestor_); 179 return remainingPublicId_; 180 } 181 GetRemainingAncestorType() const182 ResourceType GetRemainingAncestorType() const 183 { 184 assert(hasRemainingAncestor_); 185 return remainingType_; 186 } 187 }; 188 } 189 190 GetChangesInternal(std::list<ServerIndexChange> & target,bool & done,SQLite::Statement & s,uint32_t maxResults)191 void SQLiteDatabaseWrapper::GetChangesInternal(std::list<ServerIndexChange>& target, 192 bool& done, 193 SQLite::Statement& s, 194 uint32_t maxResults) 195 { 196 target.clear(); 197 198 while (target.size() < maxResults && s.Step()) 199 { 200 int64_t seq = s.ColumnInt64(0); 201 ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1)); 202 ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3)); 203 const std::string& date = s.ColumnString(4); 204 205 int64_t internalId = s.ColumnInt64(2); 206 std::string publicId = GetPublicId(internalId); 207 208 target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date)); 209 } 210 211 done = !(target.size() == maxResults && s.Step()); 212 } 213 214 GetExportedResourcesInternal(std::list<ExportedResource> & target,bool & done,SQLite::Statement & s,uint32_t maxResults)215 void SQLiteDatabaseWrapper::GetExportedResourcesInternal(std::list<ExportedResource>& target, 216 bool& done, 217 SQLite::Statement& s, 218 uint32_t maxResults) 219 { 220 target.clear(); 221 222 while (target.size() < maxResults && s.Step()) 223 { 224 int64_t seq = s.ColumnInt64(0); 225 ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1)); 226 std::string publicId = s.ColumnString(2); 227 228 ExportedResource resource(seq, 229 resourceType, 230 publicId, 231 s.ColumnString(3), // modality 232 s.ColumnString(8), // date 233 s.ColumnString(4), // patient ID 234 s.ColumnString(5), // study instance UID 235 s.ColumnString(6), // series instance UID 236 s.ColumnString(7)); // sop instance UID 237 238 target.push_back(resource); 239 } 240 241 done = !(target.size() == maxResults && s.Step()); 242 } 243 244 GetChildren(std::list<std::string> & childrenPublicIds,int64_t id)245 void SQLiteDatabaseWrapper::GetChildren(std::list<std::string>& childrenPublicIds, 246 int64_t id) 247 { 248 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE parentId=?"); 249 s.BindInt64(0, id); 250 251 childrenPublicIds.clear(); 252 while (s.Step()) 253 { 254 childrenPublicIds.push_back(s.ColumnString(0)); 255 } 256 } 257 258 DeleteResource(int64_t id)259 void SQLiteDatabaseWrapper::DeleteResource(int64_t id) 260 { 261 signalRemainingAncestor_->Reset(); 262 263 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?"); 264 s.BindInt64(0, id); 265 s.Run(); 266 267 if (signalRemainingAncestor_->HasRemainingAncestor() && 268 listener_ != NULL) 269 { 270 listener_->SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorType(), 271 signalRemainingAncestor_->GetRemainingAncestorId()); 272 } 273 } 274 275 GetParentPublicId(std::string & target,int64_t id)276 bool SQLiteDatabaseWrapper::GetParentPublicId(std::string& target, 277 int64_t id) 278 { 279 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b " 280 "WHERE a.internalId = b.parentId AND b.internalId = ?"); 281 s.BindInt64(0, id); 282 283 if (s.Step()) 284 { 285 target = s.ColumnString(0); 286 return true; 287 } 288 else 289 { 290 return false; 291 } 292 } 293 294 GetTableRecordCount(const std::string & table)295 int64_t SQLiteDatabaseWrapper::GetTableRecordCount(const std::string& table) 296 { 297 /** 298 * "Generally one cannot use SQL parameters/placeholders for 299 * database identifiers (tables, columns, views, schemas, etc.) or 300 * database functions (e.g., CURRENT_DATE), but instead only for 301 * binding literal values." => To avoid any SQL injection, we 302 * check that the "table" parameter has only alphabetic 303 * characters. 304 * https://stackoverflow.com/a/1274764/881731 305 **/ 306 for (size_t i = 0; i < table.size(); i++) 307 { 308 if (!isalpha(table[i])) 309 { 310 throw OrthancException(ErrorCode_ParameterOutOfRange); 311 } 312 } 313 314 // Don't use "SQLITE_FROM_HERE", otherwise "table" would be cached 315 SQLite::Statement s(db_, "SELECT COUNT(*) FROM " + table); 316 317 if (s.Step()) 318 { 319 int64_t c = s.ColumnInt(0); 320 assert(!s.Step()); 321 return c; 322 } 323 else 324 { 325 throw OrthancException(ErrorCode_InternalError); 326 } 327 } 328 329 SQLiteDatabaseWrapper(const std::string & path)330 SQLiteDatabaseWrapper::SQLiteDatabaseWrapper(const std::string& path) : 331 listener_(NULL), 332 signalRemainingAncestor_(NULL), 333 version_(0) 334 { 335 db_.Open(path); 336 } 337 338 SQLiteDatabaseWrapper()339 SQLiteDatabaseWrapper::SQLiteDatabaseWrapper() : 340 listener_(NULL), 341 signalRemainingAncestor_(NULL), 342 version_(0) 343 { 344 db_.OpenInMemory(); 345 } 346 347 GetGlobalIntegerProperty(GlobalProperty property,int defaultValue)348 int SQLiteDatabaseWrapper::GetGlobalIntegerProperty(GlobalProperty property, 349 int defaultValue) 350 { 351 std::string tmp; 352 353 if (!LookupGlobalProperty(tmp, GlobalProperty_DatabasePatchLevel)) 354 { 355 return defaultValue; 356 } 357 else 358 { 359 try 360 { 361 return boost::lexical_cast<int>(tmp); 362 } 363 catch (boost::bad_lexical_cast&) 364 { 365 throw OrthancException(ErrorCode_ParameterOutOfRange, 366 "Global property " + boost::lexical_cast<std::string>(property) + 367 " should be an integer, but found: " + tmp); 368 } 369 } 370 } 371 372 Open()373 void SQLiteDatabaseWrapper::Open() 374 { 375 db_.Execute("PRAGMA ENCODING=\"UTF-8\";"); 376 377 // Performance tuning of SQLite with PRAGMAs 378 // http://www.sqlite.org/pragma.html 379 db_.Execute("PRAGMA SYNCHRONOUS=NORMAL;"); 380 db_.Execute("PRAGMA JOURNAL_MODE=WAL;"); 381 db_.Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;"); 382 db_.Execute("PRAGMA WAL_AUTOCHECKPOINT=1000;"); 383 //db_.Execute("PRAGMA TEMP_STORE=memory"); 384 385 // Make "LIKE" case-sensitive in SQLite 386 db_.Execute("PRAGMA case_sensitive_like = true;"); 387 388 { 389 SQLite::Transaction t(db_); 390 t.Begin(); 391 392 if (!db_.DoesTableExist("GlobalProperties")) 393 { 394 LOG(INFO) << "Creating the database"; 395 std::string query; 396 ServerResources::GetFileResource(query, ServerResources::PREPARE_DATABASE); 397 db_.Execute(query); 398 } 399 400 // Check the version of the database 401 std::string tmp; 402 if (!LookupGlobalProperty(tmp, GlobalProperty_DatabaseSchemaVersion)) 403 { 404 tmp = "Unknown"; 405 } 406 407 bool ok = false; 408 try 409 { 410 LOG(INFO) << "Version of the Orthanc database: " << tmp; 411 version_ = boost::lexical_cast<unsigned int>(tmp); 412 ok = true; 413 } 414 catch (boost::bad_lexical_cast&) 415 { 416 } 417 418 if (!ok) 419 { 420 throw OrthancException(ErrorCode_IncompatibleDatabaseVersion, 421 "Incompatible version of the Orthanc database: " + tmp); 422 } 423 424 // New in Orthanc 1.5.1 425 if (version_ == 6) 426 { 427 if (!LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) || 428 tmp != "1") 429 { 430 LOG(INFO) << "Installing the SQLite triggers to track the size of the attachments"; 431 std::string query; 432 ServerResources::GetFileResource(query, ServerResources::INSTALL_TRACK_ATTACHMENTS_SIZE); 433 db_.Execute(query); 434 } 435 } 436 437 t.Commit(); 438 } 439 440 signalRemainingAncestor_ = new Internals::SignalRemainingAncestor; 441 db_.Register(signalRemainingAncestor_); 442 } 443 444 ExecuteUpgradeScript(SQLite::Connection & db,ServerResources::FileResourceId script)445 static void ExecuteUpgradeScript(SQLite::Connection& db, 446 ServerResources::FileResourceId script) 447 { 448 std::string upgrade; 449 ServerResources::GetFileResource(upgrade, script); 450 db.BeginTransaction(); 451 db.Execute(upgrade); 452 db.CommitTransaction(); 453 } 454 455 Upgrade(unsigned int targetVersion,IStorageArea & storageArea)456 void SQLiteDatabaseWrapper::Upgrade(unsigned int targetVersion, 457 IStorageArea& storageArea) 458 { 459 if (targetVersion != 6) 460 { 461 throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); 462 } 463 464 // This version of Orthanc is only compatible with versions 3, 4, 465 // 5 and 6 of the DB schema 466 if (version_ != 3 && 467 version_ != 4 && 468 version_ != 5 && 469 version_ != 6) 470 { 471 throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); 472 } 473 474 if (version_ == 3) 475 { 476 LOG(WARNING) << "Upgrading database version from 3 to 4"; 477 ExecuteUpgradeScript(db_, ServerResources::UPGRADE_DATABASE_3_TO_4); 478 version_ = 4; 479 } 480 481 if (version_ == 4) 482 { 483 LOG(WARNING) << "Upgrading database version from 4 to 5"; 484 ExecuteUpgradeScript(db_, ServerResources::UPGRADE_DATABASE_4_TO_5); 485 version_ = 5; 486 } 487 488 if (version_ == 5) 489 { 490 LOG(WARNING) << "Upgrading database version from 5 to 6"; 491 // No change in the DB schema, the step from version 5 to 6 only 492 // consists in reconstructing the main DICOM tags information 493 // (as more tags got included). 494 db_.BeginTransaction(); 495 ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Patient); 496 ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Study); 497 ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Series); 498 ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Instance); 499 db_.Execute("UPDATE GlobalProperties SET value=\"6\" WHERE property=" + 500 boost::lexical_cast<std::string>(GlobalProperty_DatabaseSchemaVersion) + ";"); 501 db_.CommitTransaction(); 502 version_ = 6; 503 } 504 } 505 506 SetListener(IDatabaseListener & listener)507 void SQLiteDatabaseWrapper::SetListener(IDatabaseListener& listener) 508 { 509 listener_ = &listener; 510 db_.Register(new Internals::SignalFileDeleted(listener)); 511 db_.Register(new Internals::SignalResourceDeleted(listener)); 512 } 513 514 ClearTable(const std::string & tableName)515 void SQLiteDatabaseWrapper::ClearTable(const std::string& tableName) 516 { 517 db_.Execute("DELETE FROM " + tableName); 518 } 519 520 LookupParent(int64_t & parentId,int64_t resourceId)521 bool SQLiteDatabaseWrapper::LookupParent(int64_t& parentId, 522 int64_t resourceId) 523 { 524 SQLite::Statement s(db_, SQLITE_FROM_HERE, 525 "SELECT parentId FROM Resources WHERE internalId=?"); 526 s.BindInt64(0, resourceId); 527 528 if (!s.Step()) 529 { 530 throw OrthancException(ErrorCode_UnknownResource); 531 } 532 533 if (s.ColumnIsNull(0)) 534 { 535 return false; 536 } 537 else 538 { 539 parentId = s.ColumnInt(0); 540 return true; 541 } 542 } 543 544 GetResourceType(int64_t resourceId)545 ResourceType SQLiteDatabaseWrapper::GetResourceType(int64_t resourceId) 546 { 547 SQLite::Statement s(db_, SQLITE_FROM_HERE, 548 "SELECT resourceType FROM Resources WHERE internalId=?"); 549 s.BindInt64(0, resourceId); 550 551 if (s.Step()) 552 { 553 return static_cast<ResourceType>(s.ColumnInt(0)); 554 } 555 else 556 { 557 throw OrthancException(ErrorCode_UnknownResource); 558 } 559 } 560 561 GetPublicId(int64_t resourceId)562 std::string SQLiteDatabaseWrapper::GetPublicId(int64_t resourceId) 563 { 564 SQLite::Statement s(db_, SQLITE_FROM_HERE, 565 "SELECT publicId FROM Resources WHERE internalId=?"); 566 s.BindInt64(0, resourceId); 567 568 if (s.Step()) 569 { 570 return s.ColumnString(0); 571 } 572 else 573 { 574 throw OrthancException(ErrorCode_UnknownResource); 575 } 576 } 577 578 GetChanges(std::list<ServerIndexChange> & target,bool & done,int64_t since,uint32_t maxResults)579 void SQLiteDatabaseWrapper::GetChanges(std::list<ServerIndexChange>& target /*out*/, 580 bool& done /*out*/, 581 int64_t since, 582 uint32_t maxResults) 583 { 584 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?"); 585 s.BindInt64(0, since); 586 s.BindInt(1, maxResults + 1); 587 GetChangesInternal(target, done, s, maxResults); 588 } 589 590 GetLastChange(std::list<ServerIndexChange> & target)591 void SQLiteDatabaseWrapper::GetLastChange(std::list<ServerIndexChange>& target /*out*/) 592 { 593 bool done; // Ignored 594 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1"); 595 GetChangesInternal(target, done, s, 1); 596 } 597 598 599 class SQLiteDatabaseWrapper::Transaction : public IDatabaseWrapper::ITransaction 600 { 601 private: 602 SQLiteDatabaseWrapper& that_; 603 std::unique_ptr<SQLite::Transaction> transaction_; 604 int64_t initialDiskSize_; 605 606 public: Transaction(SQLiteDatabaseWrapper & that)607 Transaction(SQLiteDatabaseWrapper& that) : 608 that_(that), 609 transaction_(new SQLite::Transaction(that_.db_)) 610 { 611 #if defined(NDEBUG) 612 // Release mode 613 initialDiskSize_ = 0; 614 #else 615 // Debug mode 616 initialDiskSize_ = static_cast<int64_t>(that_.GetTotalCompressedSize()); 617 #endif 618 } 619 Begin()620 virtual void Begin() 621 { 622 transaction_->Begin(); 623 } 624 Rollback()625 virtual void Rollback() 626 { 627 transaction_->Rollback(); 628 } 629 Commit(int64_t fileSizeDelta)630 virtual void Commit(int64_t fileSizeDelta /* only used in debug */) 631 { 632 transaction_->Commit(); 633 634 assert(initialDiskSize_ + fileSizeDelta >= 0 && 635 initialDiskSize_ + fileSizeDelta == static_cast<int64_t>(that_.GetTotalCompressedSize())); 636 } 637 }; 638 639 StartTransaction()640 IDatabaseWrapper::ITransaction* SQLiteDatabaseWrapper::StartTransaction() 641 { 642 return new Transaction(*this); 643 } 644 645 GetAllMetadata(std::map<MetadataType,std::string> & target,int64_t id)646 void SQLiteDatabaseWrapper::GetAllMetadata(std::map<MetadataType, std::string>& target, 647 int64_t id) 648 { 649 target.clear(); 650 651 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type, value FROM Metadata WHERE id=?"); 652 s.BindInt64(0, id); 653 654 while (s.Step()) 655 { 656 MetadataType key = static_cast<MetadataType>(s.ColumnInt(0)); 657 target[key] = s.ColumnString(1); 658 } 659 } 660 661 SetGlobalProperty(GlobalProperty property,const std::string & value)662 void SQLiteDatabaseWrapper::SetGlobalProperty(GlobalProperty property, 663 const std::string& value) 664 { 665 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)"); 666 s.BindInt(0, property); 667 s.BindString(1, value); 668 s.Run(); 669 } 670 671 LookupGlobalProperty(std::string & target,GlobalProperty property)672 bool SQLiteDatabaseWrapper::LookupGlobalProperty(std::string& target, 673 GlobalProperty property) 674 { 675 SQLite::Statement s(db_, SQLITE_FROM_HERE, 676 "SELECT value FROM GlobalProperties WHERE property=?"); 677 s.BindInt(0, property); 678 679 if (!s.Step()) 680 { 681 return false; 682 } 683 else 684 { 685 target = s.ColumnString(0); 686 return true; 687 } 688 } 689 690 CreateResource(const std::string & publicId,ResourceType type)691 int64_t SQLiteDatabaseWrapper::CreateResource(const std::string& publicId, 692 ResourceType type) 693 { 694 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)"); 695 s.BindInt(0, type); 696 s.BindString(1, publicId); 697 s.Run(); 698 return db_.GetLastInsertRowId(); 699 } 700 701 LookupResource(int64_t & id,ResourceType & type,const std::string & publicId)702 bool SQLiteDatabaseWrapper::LookupResource(int64_t& id, 703 ResourceType& type, 704 const std::string& publicId) 705 { 706 SQLite::Statement s(db_, SQLITE_FROM_HERE, 707 "SELECT internalId, resourceType FROM Resources WHERE publicId=?"); 708 s.BindString(0, publicId); 709 710 if (!s.Step()) 711 { 712 return false; 713 } 714 else 715 { 716 id = s.ColumnInt(0); 717 type = static_cast<ResourceType>(s.ColumnInt(1)); 718 719 // Check whether there is a single resource with this public id 720 assert(!s.Step()); 721 722 return true; 723 } 724 } 725 726 AttachChild(int64_t parent,int64_t child)727 void SQLiteDatabaseWrapper::AttachChild(int64_t parent, 728 int64_t child) 729 { 730 SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?"); 731 s.BindInt64(0, parent); 732 s.BindInt64(1, child); 733 s.Run(); 734 } 735 736 SetMetadata(int64_t id,MetadataType type,const std::string & value)737 void SQLiteDatabaseWrapper::SetMetadata(int64_t id, 738 MetadataType type, 739 const std::string& value) 740 { 741 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)"); 742 s.BindInt64(0, id); 743 s.BindInt(1, type); 744 s.BindString(2, value); 745 s.Run(); 746 } 747 748 DeleteMetadata(int64_t id,MetadataType type)749 void SQLiteDatabaseWrapper::DeleteMetadata(int64_t id, 750 MetadataType type) 751 { 752 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?"); 753 s.BindInt64(0, id); 754 s.BindInt(1, type); 755 s.Run(); 756 } 757 758 LookupMetadata(std::string & target,int64_t id,MetadataType type)759 bool SQLiteDatabaseWrapper::LookupMetadata(std::string& target, 760 int64_t id, 761 MetadataType type) 762 { 763 SQLite::Statement s(db_, SQLITE_FROM_HERE, 764 "SELECT value FROM Metadata WHERE id=? AND type=?"); 765 s.BindInt64(0, id); 766 s.BindInt(1, type); 767 768 if (!s.Step()) 769 { 770 return false; 771 } 772 else 773 { 774 target = s.ColumnString(0); 775 return true; 776 } 777 } 778 779 AddAttachment(int64_t id,const FileInfo & attachment)780 void SQLiteDatabaseWrapper::AddAttachment(int64_t id, 781 const FileInfo& attachment) 782 { 783 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); 784 s.BindInt64(0, id); 785 s.BindInt(1, attachment.GetContentType()); 786 s.BindString(2, attachment.GetUuid()); 787 s.BindInt64(3, attachment.GetCompressedSize()); 788 s.BindInt64(4, attachment.GetUncompressedSize()); 789 s.BindInt(5, attachment.GetCompressionType()); 790 s.BindString(6, attachment.GetUncompressedMD5()); 791 s.BindString(7, attachment.GetCompressedMD5()); 792 s.Run(); 793 } 794 795 DeleteAttachment(int64_t id,FileContentType attachment)796 void SQLiteDatabaseWrapper::DeleteAttachment(int64_t id, 797 FileContentType attachment) 798 { 799 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?"); 800 s.BindInt64(0, id); 801 s.BindInt(1, attachment); 802 s.Run(); 803 } 804 805 ListAvailableAttachments(std::list<FileContentType> & target,int64_t id)806 void SQLiteDatabaseWrapper::ListAvailableAttachments(std::list<FileContentType>& target, 807 int64_t id) 808 { 809 target.clear(); 810 811 SQLite::Statement s(db_, SQLITE_FROM_HERE, 812 "SELECT fileType FROM AttachedFiles WHERE id=?"); 813 s.BindInt64(0, id); 814 815 while (s.Step()) 816 { 817 target.push_back(static_cast<FileContentType>(s.ColumnInt(0))); 818 } 819 } 820 LookupAttachment(FileInfo & attachment,int64_t id,FileContentType contentType)821 bool SQLiteDatabaseWrapper::LookupAttachment(FileInfo& attachment, 822 int64_t id, 823 FileContentType contentType) 824 { 825 SQLite::Statement s(db_, SQLITE_FROM_HERE, 826 "SELECT uuid, uncompressedSize, compressionType, compressedSize, " 827 "uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?"); 828 s.BindInt64(0, id); 829 s.BindInt(1, contentType); 830 831 if (!s.Step()) 832 { 833 return false; 834 } 835 else 836 { 837 attachment = FileInfo(s.ColumnString(0), 838 contentType, 839 s.ColumnInt64(1), 840 s.ColumnString(4), 841 static_cast<CompressionType>(s.ColumnInt(2)), 842 s.ColumnInt64(3), 843 s.ColumnString(5)); 844 return true; 845 } 846 } 847 848 ClearMainDicomTags(int64_t id)849 void SQLiteDatabaseWrapper::ClearMainDicomTags(int64_t id) 850 { 851 { 852 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM DicomIdentifiers WHERE id=?"); 853 s.BindInt64(0, id); 854 s.Run(); 855 } 856 857 { 858 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM MainDicomTags WHERE id=?"); 859 s.BindInt64(0, id); 860 s.Run(); 861 } 862 } 863 864 SetMainDicomTag(int64_t id,const DicomTag & tag,const std::string & value)865 void SQLiteDatabaseWrapper::SetMainDicomTag(int64_t id, 866 const DicomTag& tag, 867 const std::string& value) 868 { 869 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)"); 870 s.BindInt64(0, id); 871 s.BindInt(1, tag.GetGroup()); 872 s.BindInt(2, tag.GetElement()); 873 s.BindString(3, value); 874 s.Run(); 875 } 876 877 SetIdentifierTag(int64_t id,const DicomTag & tag,const std::string & value)878 void SQLiteDatabaseWrapper::SetIdentifierTag(int64_t id, 879 const DicomTag& tag, 880 const std::string& value) 881 { 882 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)"); 883 s.BindInt64(0, id); 884 s.BindInt(1, tag.GetGroup()); 885 s.BindInt(2, tag.GetElement()); 886 s.BindString(3, value); 887 s.Run(); 888 } 889 890 GetMainDicomTags(DicomMap & map,int64_t id)891 void SQLiteDatabaseWrapper::GetMainDicomTags(DicomMap& map, 892 int64_t id) 893 { 894 map.Clear(); 895 896 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?"); 897 s.BindInt64(0, id); 898 while (s.Step()) 899 { 900 map.SetValue(s.ColumnInt(1), 901 s.ColumnInt(2), 902 s.ColumnString(3), false); 903 } 904 } 905 906 GetChildrenPublicId(std::list<std::string> & target,int64_t id)907 void SQLiteDatabaseWrapper::GetChildrenPublicId(std::list<std::string>& target, 908 int64_t id) 909 { 910 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b " 911 "WHERE a.parentId = b.internalId AND b.internalId = ?"); 912 s.BindInt64(0, id); 913 914 target.clear(); 915 916 while (s.Step()) 917 { 918 target.push_back(s.ColumnString(0)); 919 } 920 } 921 922 GetChildrenInternalId(std::list<int64_t> & target,int64_t id)923 void SQLiteDatabaseWrapper::GetChildrenInternalId(std::list<int64_t>& target, 924 int64_t id) 925 { 926 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b " 927 "WHERE a.parentId = b.internalId AND b.internalId = ?"); 928 s.BindInt64(0, id); 929 930 target.clear(); 931 932 while (s.Step()) 933 { 934 target.push_back(s.ColumnInt64(0)); 935 } 936 } 937 938 LogChange(int64_t internalId,const ServerIndexChange & change)939 void SQLiteDatabaseWrapper::LogChange(int64_t internalId, 940 const ServerIndexChange& change) 941 { 942 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)"); 943 s.BindInt(0, change.GetChangeType()); 944 s.BindInt64(1, internalId); 945 s.BindInt(2, change.GetResourceType()); 946 s.BindString(3, change.GetDate()); 947 s.Run(); 948 } 949 950 LogExportedResource(const ExportedResource & resource)951 void SQLiteDatabaseWrapper::LogExportedResource(const ExportedResource& resource) 952 { 953 SQLite::Statement s(db_, SQLITE_FROM_HERE, 954 "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)"); 955 956 s.BindInt(0, resource.GetResourceType()); 957 s.BindString(1, resource.GetPublicId()); 958 s.BindString(2, resource.GetModality()); 959 s.BindString(3, resource.GetPatientId()); 960 s.BindString(4, resource.GetStudyInstanceUid()); 961 s.BindString(5, resource.GetSeriesInstanceUid()); 962 s.BindString(6, resource.GetSopInstanceUid()); 963 s.BindString(7, resource.GetDate()); 964 s.Run(); 965 } 966 967 GetExportedResources(std::list<ExportedResource> & target,bool & done,int64_t since,uint32_t maxResults)968 void SQLiteDatabaseWrapper::GetExportedResources(std::list<ExportedResource>& target, 969 bool& done, 970 int64_t since, 971 uint32_t maxResults) 972 { 973 SQLite::Statement s(db_, SQLITE_FROM_HERE, 974 "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?"); 975 s.BindInt64(0, since); 976 s.BindInt(1, maxResults + 1); 977 GetExportedResourcesInternal(target, done, s, maxResults); 978 } 979 980 GetLastExportedResource(std::list<ExportedResource> & target)981 void SQLiteDatabaseWrapper::GetLastExportedResource(std::list<ExportedResource>& target) 982 { 983 bool done; // Ignored 984 SQLite::Statement s(db_, SQLITE_FROM_HERE, 985 "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1"); 986 GetExportedResourcesInternal(target, done, s, 1); 987 } 988 989 GetTotalCompressedSize()990 uint64_t SQLiteDatabaseWrapper::GetTotalCompressedSize() 991 { 992 // Old SQL query that was used in Orthanc <= 1.5.0: 993 // SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles"); 994 995 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM GlobalIntegers WHERE key=0"); 996 s.Run(); 997 return static_cast<uint64_t>(s.ColumnInt64(0)); 998 } 999 1000 GetTotalUncompressedSize()1001 uint64_t SQLiteDatabaseWrapper::GetTotalUncompressedSize() 1002 { 1003 // Old SQL query that was used in Orthanc <= 1.5.0: 1004 // SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles"); 1005 1006 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM GlobalIntegers WHERE key=1"); 1007 s.Run(); 1008 return static_cast<uint64_t>(s.ColumnInt64(0)); 1009 } 1010 1011 GetResourceCount(ResourceType resourceType)1012 uint64_t SQLiteDatabaseWrapper::GetResourceCount(ResourceType resourceType) 1013 { 1014 SQLite::Statement s(db_, SQLITE_FROM_HERE, 1015 "SELECT COUNT(*) FROM Resources WHERE resourceType=?"); 1016 s.BindInt(0, resourceType); 1017 1018 if (!s.Step()) 1019 { 1020 return 0; 1021 } 1022 else 1023 { 1024 int64_t c = s.ColumnInt(0); 1025 assert(!s.Step()); 1026 return c; 1027 } 1028 } 1029 1030 GetAllPublicIds(std::list<std::string> & target,ResourceType resourceType)1031 void SQLiteDatabaseWrapper::GetAllPublicIds(std::list<std::string>& target, 1032 ResourceType resourceType) 1033 { 1034 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?"); 1035 s.BindInt(0, resourceType); 1036 1037 target.clear(); 1038 while (s.Step()) 1039 { 1040 target.push_back(s.ColumnString(0)); 1041 } 1042 } 1043 1044 GetAllPublicIds(std::list<std::string> & target,ResourceType resourceType,size_t since,size_t limit)1045 void SQLiteDatabaseWrapper::GetAllPublicIds(std::list<std::string>& target, 1046 ResourceType resourceType, 1047 size_t since, 1048 size_t limit) 1049 { 1050 if (limit == 0) 1051 { 1052 target.clear(); 1053 return; 1054 } 1055 1056 SQLite::Statement s(db_, SQLITE_FROM_HERE, 1057 "SELECT publicId FROM Resources WHERE " 1058 "resourceType=? LIMIT ? OFFSET ?"); 1059 s.BindInt(0, resourceType); 1060 s.BindInt64(1, limit); 1061 s.BindInt64(2, since); 1062 1063 target.clear(); 1064 while (s.Step()) 1065 { 1066 target.push_back(s.ColumnString(0)); 1067 } 1068 } 1069 1070 SelectPatientToRecycle(int64_t & internalId)1071 bool SQLiteDatabaseWrapper::SelectPatientToRecycle(int64_t& internalId) 1072 { 1073 SQLite::Statement s(db_, SQLITE_FROM_HERE, 1074 "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1"); 1075 1076 if (!s.Step()) 1077 { 1078 // No patient remaining or all the patients are protected 1079 return false; 1080 } 1081 else 1082 { 1083 internalId = s.ColumnInt(0); 1084 return true; 1085 } 1086 } 1087 1088 SelectPatientToRecycle(int64_t & internalId,int64_t patientIdToAvoid)1089 bool SQLiteDatabaseWrapper::SelectPatientToRecycle(int64_t& internalId, 1090 int64_t patientIdToAvoid) 1091 { 1092 SQLite::Statement s(db_, SQLITE_FROM_HERE, 1093 "SELECT patientId FROM PatientRecyclingOrder " 1094 "WHERE patientId != ? ORDER BY seq ASC LIMIT 1"); 1095 s.BindInt64(0, patientIdToAvoid); 1096 1097 if (!s.Step()) 1098 { 1099 // No patient remaining or all the patients are protected 1100 return false; 1101 } 1102 else 1103 { 1104 internalId = s.ColumnInt(0); 1105 return true; 1106 } 1107 } 1108 1109 IsProtectedPatient(int64_t internalId)1110 bool SQLiteDatabaseWrapper::IsProtectedPatient(int64_t internalId) 1111 { 1112 SQLite::Statement s(db_, SQLITE_FROM_HERE, 1113 "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?"); 1114 s.BindInt64(0, internalId); 1115 return !s.Step(); 1116 } 1117 1118 SetProtectedPatient(int64_t internalId,bool isProtected)1119 void SQLiteDatabaseWrapper::SetProtectedPatient(int64_t internalId, 1120 bool isProtected) 1121 { 1122 if (isProtected) 1123 { 1124 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?"); 1125 s.BindInt64(0, internalId); 1126 s.Run(); 1127 } 1128 else if (IsProtectedPatient(internalId)) 1129 { 1130 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)"); 1131 s.BindInt64(0, internalId); 1132 s.Run(); 1133 } 1134 else 1135 { 1136 // Nothing to do: The patient is already unprotected 1137 } 1138 } 1139 1140 IsExistingResource(int64_t internalId)1141 bool SQLiteDatabaseWrapper::IsExistingResource(int64_t internalId) 1142 { 1143 SQLite::Statement s(db_, SQLITE_FROM_HERE, 1144 "SELECT * FROM Resources WHERE internalId=?"); 1145 s.BindInt64(0, internalId); 1146 return s.Step(); 1147 } 1148 1149 IsDiskSizeAbove(uint64_t threshold)1150 bool SQLiteDatabaseWrapper::IsDiskSizeAbove(uint64_t threshold) 1151 { 1152 return GetTotalCompressedSize() > threshold; 1153 } 1154 1155 1156 1157 class SQLiteDatabaseWrapper::LookupFormatter : public ISqlLookupFormatter 1158 { 1159 private: 1160 std::list<std::string> values_; 1161 1162 public: GenerateParameter(const std::string & value)1163 virtual std::string GenerateParameter(const std::string& value) 1164 { 1165 values_.push_back(value); 1166 return "?"; 1167 } 1168 FormatResourceType(ResourceType level)1169 virtual std::string FormatResourceType(ResourceType level) 1170 { 1171 return boost::lexical_cast<std::string>(level); 1172 } 1173 FormatWildcardEscape()1174 virtual std::string FormatWildcardEscape() 1175 { 1176 return "ESCAPE '\\'"; 1177 } 1178 Bind(SQLite::Statement & statement) const1179 void Bind(SQLite::Statement& statement) const 1180 { 1181 size_t pos = 0; 1182 1183 for (std::list<std::string>::const_iterator 1184 it = values_.begin(); it != values_.end(); ++it, pos++) 1185 { 1186 statement.BindString(pos, *it); 1187 } 1188 } 1189 }; 1190 1191 AnswerLookup(std::list<std::string> & resourcesId,std::list<std::string> & instancesId,SQLite::Connection & db,ResourceType level)1192 static void AnswerLookup(std::list<std::string>& resourcesId, 1193 std::list<std::string>& instancesId, 1194 SQLite::Connection& db, 1195 ResourceType level) 1196 { 1197 resourcesId.clear(); 1198 instancesId.clear(); 1199 1200 std::unique_ptr<SQLite::Statement> statement; 1201 1202 switch (level) 1203 { 1204 case ResourceType_Patient: 1205 { 1206 statement.reset( 1207 new SQLite::Statement( 1208 db, SQLITE_FROM_HERE, 1209 "SELECT patients.publicId, instances.publicID FROM Lookup AS patients " 1210 "INNER JOIN Resources studies ON patients.internalId=studies.parentId " 1211 "INNER JOIN Resources series ON studies.internalId=series.parentId " 1212 "INNER JOIN Resources instances ON series.internalId=instances.parentId " 1213 "GROUP BY patients.publicId")); 1214 1215 break; 1216 } 1217 1218 case ResourceType_Study: 1219 { 1220 statement.reset( 1221 new SQLite::Statement( 1222 db, SQLITE_FROM_HERE, 1223 "SELECT studies.publicId, instances.publicID FROM Lookup AS studies " 1224 "INNER JOIN Resources series ON studies.internalId=series.parentId " 1225 "INNER JOIN Resources instances ON series.internalId=instances.parentId " 1226 "GROUP BY studies.publicId")); 1227 1228 break; 1229 } 1230 1231 case ResourceType_Series: 1232 { 1233 statement.reset( 1234 new SQLite::Statement( 1235 db, SQLITE_FROM_HERE, 1236 "SELECT series.publicId, instances.publicID FROM Lookup AS series " 1237 "INNER JOIN Resources instances ON series.internalId=instances.parentId " 1238 "GROUP BY series.publicId")); 1239 1240 break; 1241 } 1242 1243 case ResourceType_Instance: 1244 { 1245 statement.reset( 1246 new SQLite::Statement( 1247 db, SQLITE_FROM_HERE, "SELECT publicId, publicId FROM Lookup")); 1248 1249 break; 1250 } 1251 1252 default: 1253 throw OrthancException(ErrorCode_InternalError); 1254 } 1255 1256 assert(statement.get() != NULL); 1257 1258 while (statement->Step()) 1259 { 1260 resourcesId.push_back(statement->ColumnString(0)); 1261 instancesId.push_back(statement->ColumnString(1)); 1262 } 1263 } 1264 1265 ApplyLookupResources(std::list<std::string> & resourcesId,std::list<std::string> * instancesId,const std::vector<DatabaseConstraint> & lookup,ResourceType queryLevel,size_t limit)1266 void SQLiteDatabaseWrapper::ApplyLookupResources(std::list<std::string>& resourcesId, 1267 std::list<std::string>* instancesId, 1268 const std::vector<DatabaseConstraint>& lookup, 1269 ResourceType queryLevel, 1270 size_t limit) 1271 { 1272 LookupFormatter formatter; 1273 1274 std::string sql; 1275 LookupFormatter::Apply(sql, formatter, lookup, queryLevel, limit); 1276 1277 sql = "CREATE TEMPORARY TABLE Lookup AS " + sql; 1278 1279 { 1280 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DROP TABLE IF EXISTS Lookup"); 1281 s.Run(); 1282 } 1283 1284 { 1285 SQLite::Statement statement(db_, sql); 1286 formatter.Bind(statement); 1287 statement.Run(); 1288 } 1289 1290 if (instancesId != NULL) 1291 { 1292 AnswerLookup(resourcesId, *instancesId, db_, queryLevel); 1293 } 1294 else 1295 { 1296 resourcesId.clear(); 1297 1298 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Lookup"); 1299 1300 while (s.Step()) 1301 { 1302 resourcesId.push_back(s.ColumnString(0)); 1303 } 1304 } 1305 } 1306 1307 GetLastChangeIndex()1308 int64_t SQLiteDatabaseWrapper::GetLastChangeIndex() 1309 { 1310 SQLite::Statement s(db_, SQLITE_FROM_HERE, 1311 "SELECT seq FROM sqlite_sequence WHERE name='Changes'"); 1312 1313 if (s.Step()) 1314 { 1315 int64_t c = s.ColumnInt(0); 1316 assert(!s.Step()); 1317 return c; 1318 } 1319 else 1320 { 1321 // No change has been recorded so far in the database 1322 return 0; 1323 } 1324 } 1325 1326 TagMostRecentPatient(int64_t patient)1327 void SQLiteDatabaseWrapper::TagMostRecentPatient(int64_t patient) 1328 { 1329 { 1330 SQLite::Statement s(db_, SQLITE_FROM_HERE, 1331 "DELETE FROM PatientRecyclingOrder WHERE patientId=?"); 1332 s.BindInt64(0, patient); 1333 s.Run(); 1334 1335 assert(db_.GetLastChangeCount() == 0 || 1336 db_.GetLastChangeCount() == 1); 1337 1338 if (db_.GetLastChangeCount() == 0) 1339 { 1340 // The patient was protected, there was nothing to delete from the recycling order 1341 return; 1342 } 1343 } 1344 1345 { 1346 SQLite::Statement s(db_, SQLITE_FROM_HERE, 1347 "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)"); 1348 s.BindInt64(0, patient); 1349 s.Run(); 1350 } 1351 } 1352 } 1353