Mercurial > hg > orthanc-databases
comparison Odbc/Plugins/OdbcIndex.cpp @ 329:b5fb8b77ce4d
initial commit of ODBC framework
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 10 Aug 2021 20:08:53 +0200 |
parents | |
children | 16aac0287485 cd9521e04249 |
comparison
equal
deleted
inserted
replaced
328:6a49c495c940 | 329:b5fb8b77ce4d |
---|---|
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-2021 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 Affero General Public License | |
9 * as published by the Free Software Foundation, either version 3 of | |
10 * the License, or (at your option) any later version. | |
11 * | |
12 * This program is distributed in the hope that it will be useful, but | |
13 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 * Affero General Public License for more details. | |
16 * | |
17 * You should have received a copy of the GNU Affero General Public License | |
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 **/ | |
20 | |
21 | |
22 #include "OdbcIndex.h" | |
23 | |
24 #include "../../Framework/Common/Integer64Value.h" | |
25 #include "../../Framework/Odbc/OdbcDatabase.h" | |
26 #include "../../Framework/Plugins/GlobalProperties.h" | |
27 | |
28 #include <EmbeddedResources.h> // Autogenerated file | |
29 | |
30 #include <Logging.h> | |
31 #include <OrthancException.h> | |
32 #include <Toolbox.h> | |
33 | |
34 #include <boost/algorithm/string/replace.hpp> | |
35 | |
36 | |
37 // Some aliases for internal properties | |
38 static const Orthanc::GlobalProperty GlobalProperty_LastChange = Orthanc::GlobalProperty_DatabaseInternal0; | |
39 | |
40 | |
41 namespace OrthancDatabases | |
42 { | |
43 static int64_t GetSQLiteLastInsert(DatabaseManager& manager) | |
44 { | |
45 DatabaseManager::CachedStatement statement( | |
46 STATEMENT_FROM_HERE, manager, "SELECT LAST_INSERT_ROWID()"); | |
47 | |
48 statement.Execute(); | |
49 | |
50 return statement.ReadInteger64(0); | |
51 } | |
52 | |
53 | |
54 static int64_t GetMySQLLastInsert(DatabaseManager& manager) | |
55 { | |
56 DatabaseManager::CachedStatement statement( | |
57 STATEMENT_FROM_HERE, manager, "SELECT LAST_INSERT_ID()"); | |
58 | |
59 statement.Execute(); | |
60 | |
61 return statement.ReadInteger64(0); | |
62 } | |
63 | |
64 | |
65 static int64_t GetMSSQLLastInsert(DatabaseManager& manager) | |
66 { | |
67 DatabaseManager::CachedStatement statement( | |
68 STATEMENT_FROM_HERE, manager, "SELECT @@IDENTITY"); | |
69 | |
70 statement.Execute(); | |
71 | |
72 return statement.ReadInteger64(0); | |
73 } | |
74 | |
75 | |
76 static void AddPatientToRecyclingOrder(DatabaseManager& manager, | |
77 int64_t patient) | |
78 { | |
79 // In the other database plugins, this is done with a trigger | |
80 | |
81 std::unique_ptr<DatabaseManager::CachedStatement> statement; | |
82 | |
83 switch (manager.GetDialect()) | |
84 { | |
85 case Dialect_SQLite: | |
86 case Dialect_MySQL: | |
87 statement.reset( | |
88 new DatabaseManager::CachedStatement( | |
89 STATEMENT_FROM_HERE, manager, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ${patient})")); | |
90 break; | |
91 | |
92 case Dialect_PostgreSQL: | |
93 statement.reset( | |
94 new DatabaseManager::CachedStatement( | |
95 STATEMENT_FROM_HERE, manager, "INSERT INTO PatientRecyclingOrder VALUES(DEFAULT, ${patient})")); | |
96 break; | |
97 | |
98 case Dialect_MSSQL: | |
99 statement.reset( | |
100 new DatabaseManager::CachedStatement( | |
101 STATEMENT_FROM_HERE, manager, "INSERT INTO PatientRecyclingOrder VALUES(${patient})")); | |
102 break; | |
103 | |
104 default: | |
105 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
106 } | |
107 | |
108 statement->SetParameterType("patient", ValueType_Integer64); | |
109 | |
110 Dictionary args; | |
111 args.SetIntegerValue("patient", patient); | |
112 statement->Execute(args); | |
113 } | |
114 | |
115 | |
116 static OrthancPluginResourceType GetParentType(OrthancPluginResourceType level) | |
117 { | |
118 switch (level) | |
119 { | |
120 case OrthancPluginResourceType_Study: | |
121 return OrthancPluginResourceType_Patient; | |
122 | |
123 case OrthancPluginResourceType_Series: | |
124 return OrthancPluginResourceType_Study; | |
125 | |
126 case OrthancPluginResourceType_Instance: | |
127 return OrthancPluginResourceType_Series; | |
128 | |
129 default: | |
130 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
131 } | |
132 } | |
133 | |
134 | |
135 OdbcIndex::OdbcIndex(OrthancPluginContext* context, | |
136 const std::string& connectionString) : | |
137 IndexBackend(context), | |
138 maxConnectionRetries_(10), | |
139 connectionRetryInterval_(5), | |
140 connectionString_(connectionString) | |
141 { | |
142 } | |
143 | |
144 | |
145 void OdbcIndex::SetConnectionRetryInterval(unsigned int seconds) | |
146 { | |
147 if (seconds == 0) | |
148 { | |
149 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
150 } | |
151 else | |
152 { | |
153 connectionRetryInterval_ = seconds; | |
154 } | |
155 } | |
156 | |
157 | |
158 IDatabaseFactory* OdbcIndex::CreateDatabaseFactory() | |
159 { | |
160 return OdbcDatabase::CreateDatabaseFactory(maxConnectionRetries_, connectionRetryInterval_, connectionString_, true); | |
161 } | |
162 | |
163 | |
164 void OdbcIndex::ConfigureDatabase(DatabaseManager& manager) | |
165 { | |
166 uint32_t expectedVersion = 6; | |
167 | |
168 if (GetContext()) // "GetContext()" can possibly be NULL in the unit tests | |
169 { | |
170 expectedVersion = OrthancPluginGetExpectedDatabaseVersion(GetContext()); | |
171 } | |
172 | |
173 // Check the expected version of the database | |
174 if (expectedVersion != 6) | |
175 { | |
176 LOG(ERROR) << "This database plugin is incompatible with your version of Orthanc " | |
177 << "expecting the DB schema version " << expectedVersion | |
178 << ", but this plugin is only compatible with version 6"; | |
179 throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin); | |
180 } | |
181 | |
182 OdbcDatabase& db = dynamic_cast<OdbcDatabase&>(manager.GetDatabase()); | |
183 | |
184 if (!db.DoesTableExist("resources")) | |
185 { | |
186 std::string sql; | |
187 Orthanc::EmbeddedResources::GetFileResource(sql, Orthanc::EmbeddedResources::ODBC_PREPARE_INDEX); | |
188 | |
189 switch (db.GetDialect()) | |
190 { | |
191 case Dialect_SQLite: | |
192 boost::replace_all(sql, "${LONGTEXT}", "TEXT"); | |
193 boost::replace_all(sql, "${AUTOINCREMENT_TYPE}", "INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT"); | |
194 boost::replace_all(sql, "${AUTOINCREMENT_INSERT}", "NULL, "); | |
195 break; | |
196 | |
197 case Dialect_PostgreSQL: | |
198 boost::replace_all(sql, "${LONGTEXT}", "TEXT"); | |
199 boost::replace_all(sql, "${AUTOINCREMENT_TYPE}", "BIGSERIAL NOT NULL PRIMARY KEY"); | |
200 boost::replace_all(sql, "${AUTOINCREMENT_INSERT}", "DEFAULT, "); | |
201 break; | |
202 | |
203 case Dialect_MySQL: | |
204 boost::replace_all(sql, "${LONGTEXT}", "LONGTEXT"); | |
205 boost::replace_all(sql, "${AUTOINCREMENT_TYPE}", "BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY"); | |
206 boost::replace_all(sql, "${AUTOINCREMENT_INSERT}", "NULL, "); | |
207 break; | |
208 | |
209 case Dialect_MSSQL: | |
210 /** | |
211 * cf. OMSSQL-5: Use VARCHAR(MAX) instead of TEXT: (1) | |
212 * Microsoft issued a warning stating that "ntext, text, and | |
213 * image data types will be removed in a future version of | |
214 * SQL Server" | |
215 * (https://msdn.microsoft.com/en-us/library/ms187993.aspx), | |
216 * and (2) SQL Server does not support comparison of TEXT | |
217 * with '=' operator (e.g. in WHERE statements such as | |
218 * IndexBackend::LookupIdentifier())." | |
219 **/ | |
220 boost::replace_all(sql, "${LONGTEXT}", "VARCHAR(MAX)"); | |
221 boost::replace_all(sql, "${AUTOINCREMENT_TYPE}", "BIGINT IDENTITY NOT NULL PRIMARY KEY"); | |
222 boost::replace_all(sql, "${AUTOINCREMENT_INSERT}", ""); | |
223 break; | |
224 | |
225 default: | |
226 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
227 } | |
228 | |
229 { | |
230 DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); | |
231 | |
232 db.ExecuteMultiLines(sql); | |
233 | |
234 if (db.GetDialect() == Dialect_MySQL) | |
235 { | |
236 // Switch to the collation that is the default since MySQL | |
237 // 8.0.1. This must be *after* the creation of the tables. | |
238 db.ExecuteMultiLines("ALTER DATABASE CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); | |
239 } | |
240 | |
241 t.Commit(); | |
242 } | |
243 } | |
244 } | |
245 | |
246 | |
247 int64_t OdbcIndex::CreateResource(DatabaseManager& manager, | |
248 const char* publicId, | |
249 OrthancPluginResourceType type) | |
250 { | |
251 Dictionary args; | |
252 args.SetUtf8Value("id", publicId); | |
253 args.SetIntegerValue("type", static_cast<int>(type)); | |
254 | |
255 switch (manager.GetDatabase().GetDialect()) | |
256 { | |
257 case Dialect_SQLite: | |
258 { | |
259 { | |
260 DatabaseManager::CachedStatement statement( | |
261 STATEMENT_FROM_HERE, manager, "INSERT INTO Resources VALUES(NULL, ${type}, ${id}, NULL)"); | |
262 | |
263 statement.SetParameterType("id", ValueType_Utf8String); | |
264 statement.SetParameterType("type", ValueType_Integer64); | |
265 statement.Execute(args); | |
266 } | |
267 | |
268 // Must be out of the scope of "DatabaseManager::CachedStatement" | |
269 const int64_t id = GetSQLiteLastInsert(manager); | |
270 | |
271 if (type == OrthancPluginResourceType_Patient) | |
272 { | |
273 AddPatientToRecyclingOrder(manager, id); | |
274 } | |
275 | |
276 return id; | |
277 } | |
278 | |
279 case Dialect_PostgreSQL: | |
280 { | |
281 int64_t id; | |
282 | |
283 { | |
284 DatabaseManager::CachedStatement statement( | |
285 STATEMENT_FROM_HERE, manager, | |
286 "INSERT INTO Resources VALUES(DEFAULT, ${type}, ${id}, NULL) RETURNING internalId"); | |
287 | |
288 statement.SetParameterType("id", ValueType_Utf8String); | |
289 statement.SetParameterType("type", ValueType_Integer64); | |
290 statement.Execute(args); | |
291 id = statement.ReadInteger64(0); | |
292 } | |
293 | |
294 if (type == OrthancPluginResourceType_Patient) | |
295 { | |
296 AddPatientToRecyclingOrder(manager, id); | |
297 } | |
298 | |
299 return id; | |
300 } | |
301 | |
302 case Dialect_MySQL: | |
303 { | |
304 { | |
305 DatabaseManager::CachedStatement statement( | |
306 STATEMENT_FROM_HERE, manager, "INSERT INTO Resources VALUES(NULL, ${type}, ${id}, NULL)"); | |
307 | |
308 statement.SetParameterType("id", ValueType_Utf8String); | |
309 statement.SetParameterType("type", ValueType_Integer64); | |
310 statement.Execute(args); | |
311 } | |
312 | |
313 // Must be out of the scope of "DatabaseManager::CachedStatement" | |
314 const int64_t id = GetMySQLLastInsert(manager); | |
315 | |
316 if (type == OrthancPluginResourceType_Patient) | |
317 { | |
318 AddPatientToRecyclingOrder(manager, id); | |
319 } | |
320 | |
321 return id; | |
322 } | |
323 | |
324 case Dialect_MSSQL: | |
325 { | |
326 { | |
327 DatabaseManager::CachedStatement statement( | |
328 STATEMENT_FROM_HERE, manager, "INSERT INTO Resources VALUES(${type}, ${id}, NULL)"); | |
329 | |
330 statement.SetParameterType("id", ValueType_Utf8String); | |
331 statement.SetParameterType("type", ValueType_Integer64); | |
332 statement.Execute(args); | |
333 } | |
334 | |
335 // Must be out of the scope of "DatabaseManager::CachedStatement" | |
336 const int64_t id = GetMSSQLLastInsert(manager); | |
337 | |
338 if (type == OrthancPluginResourceType_Patient) | |
339 { | |
340 AddPatientToRecyclingOrder(manager, id); | |
341 } | |
342 | |
343 return id; | |
344 } | |
345 | |
346 default: | |
347 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
348 } | |
349 } | |
350 | |
351 | |
352 void OdbcIndex::DeleteResource(IDatabaseBackendOutput& output, | |
353 DatabaseManager& manager, | |
354 int64_t id) | |
355 { | |
356 /** | |
357 * Contrarily to PostgreSQL and SQLite, the MySQL dialect | |
358 * doesn't support cascaded delete inside the same | |
359 * table. Furthermore, for maximum portability, we don't use | |
360 * triggers in the ODBC plugins. We therefore implement a custom | |
361 * version of this deletion. | |
362 **/ | |
363 | |
364 ClearDeletedFiles(manager); | |
365 ClearDeletedResources(manager); | |
366 | |
367 OrthancPluginResourceType type; | |
368 bool hasParent; | |
369 int64_t parentId; | |
370 | |
371 { | |
372 DatabaseManager::CachedStatement lookupResource( | |
373 STATEMENT_FROM_HERE, manager, | |
374 "SELECT resourceType, parentId FROM Resources WHERE internalId=${id}"); | |
375 lookupResource.SetParameterType("id", ValueType_Integer64); | |
376 | |
377 Dictionary args; | |
378 args.SetIntegerValue("id", id); | |
379 lookupResource.Execute(args); | |
380 | |
381 if (lookupResource.IsDone()) | |
382 { | |
383 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); | |
384 } | |
385 | |
386 type = static_cast<OrthancPluginResourceType>(lookupResource.ReadInteger32(0)); | |
387 | |
388 if (lookupResource.GetResultField(1).GetType() == ValueType_Null) | |
389 { | |
390 hasParent = false; | |
391 } | |
392 else | |
393 { | |
394 hasParent = true; | |
395 parentId = lookupResource.ReadInteger64(1); | |
396 } | |
397 } | |
398 | |
399 { | |
400 DatabaseManager::CachedStatement scheduleRootDeletion( | |
401 STATEMENT_FROM_HERE, manager, | |
402 "INSERT INTO DeletedResources SELECT internalId, resourceType, publicId " | |
403 "FROM Resources WHERE Resources.internalId = ${id}"); | |
404 scheduleRootDeletion.SetParameterType("id", ValueType_Integer64); | |
405 | |
406 Dictionary args; | |
407 args.SetIntegerValue("id", id); | |
408 scheduleRootDeletion.Execute(args); | |
409 } | |
410 | |
411 { | |
412 const std::string scheduleChildrenDeletion = | |
413 "INSERT INTO DeletedResources SELECT Resources.internalId, Resources.resourceType, Resources.publicId " | |
414 "FROM Resources INNER JOIN DeletedResources ON Resources.parentId = DeletedResources.internalId " | |
415 "WHERE Resources.resourceType = ${level}"; | |
416 | |
417 switch (type) | |
418 { | |
419 /** | |
420 * WARNING: Don't add "break" or reorder cases below. | |
421 **/ | |
422 | |
423 case OrthancPluginResourceType_Patient: | |
424 { | |
425 DatabaseManager::CachedStatement statement(STATEMENT_FROM_HERE, manager, scheduleChildrenDeletion); | |
426 statement.SetParameterType("level", ValueType_Integer64); | |
427 | |
428 Dictionary args; | |
429 args.SetIntegerValue("level", OrthancPluginResourceType_Study); | |
430 statement.Execute(args); | |
431 } | |
432 | |
433 case OrthancPluginResourceType_Study: | |
434 { | |
435 DatabaseManager::CachedStatement statement(STATEMENT_FROM_HERE, manager, scheduleChildrenDeletion); | |
436 statement.SetParameterType("level", ValueType_Integer64); | |
437 | |
438 Dictionary args; | |
439 args.SetIntegerValue("level", OrthancPluginResourceType_Series); | |
440 statement.Execute(args); | |
441 } | |
442 | |
443 case OrthancPluginResourceType_Series: | |
444 { | |
445 DatabaseManager::CachedStatement statement(STATEMENT_FROM_HERE, manager, scheduleChildrenDeletion); | |
446 statement.SetParameterType("level", ValueType_Integer64); | |
447 | |
448 Dictionary args; | |
449 args.SetIntegerValue("level", OrthancPluginResourceType_Instance); | |
450 statement.Execute(args); | |
451 } | |
452 | |
453 case OrthancPluginResourceType_Instance: | |
454 // No child | |
455 break; | |
456 | |
457 default: | |
458 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
459 } | |
460 } | |
461 | |
462 bool hasRemainingAncestor = false; | |
463 std::string remainingAncestor; | |
464 OrthancPluginResourceType ancestorType; | |
465 | |
466 if (hasParent) | |
467 { | |
468 int64_t currentAncestor = parentId; | |
469 int64_t currentResource = id; | |
470 OrthancPluginResourceType currentType = type; | |
471 | |
472 for (;;) | |
473 { | |
474 bool hasSiblings; | |
475 | |
476 { | |
477 std::string suffix; | |
478 if (manager.GetDialect() == Dialect_MSSQL) | |
479 { | |
480 suffix = "ORDER BY internalId OFFSET 0 ROWS FETCH FIRST 1 ROWS ONLY"; | |
481 } | |
482 else | |
483 { | |
484 suffix = "LIMIT 1"; | |
485 } | |
486 | |
487 DatabaseManager::CachedStatement lookupSiblings( | |
488 STATEMENT_FROM_HERE, manager, | |
489 "SELECT internalId FROM Resources WHERE parentId = ${parent} AND internalId <> ${id} " + suffix); | |
490 | |
491 lookupSiblings.SetParameterType("parent", ValueType_Integer64); | |
492 lookupSiblings.SetParameterType("id", ValueType_Integer64); | |
493 | |
494 Dictionary args; | |
495 args.SetIntegerValue("parent", currentAncestor); | |
496 args.SetIntegerValue("id", currentResource); | |
497 lookupSiblings.Execute(args); | |
498 | |
499 hasSiblings = !lookupSiblings.IsDone(); | |
500 } | |
501 | |
502 if (hasSiblings) | |
503 { | |
504 // There remains some sibling: Signal this remaining ancestor | |
505 hasRemainingAncestor = true; | |
506 remainingAncestor = GetPublicId(manager, currentAncestor); | |
507 ancestorType = GetParentType(currentType); | |
508 break; | |
509 } | |
510 else | |
511 { | |
512 // No sibling remaining: This parent resource must be deleted | |
513 { | |
514 DatabaseManager::CachedStatement addDeletedResource( | |
515 STATEMENT_FROM_HERE, manager, | |
516 "INSERT INTO DeletedResources SELECT internalId, resourceType, publicId " | |
517 "FROM Resources WHERE internalId=${id}"); | |
518 addDeletedResource.SetParameterType("id", ValueType_Integer64); | |
519 | |
520 Dictionary args; | |
521 args.SetIntegerValue("id", currentAncestor); | |
522 addDeletedResource.Execute(args); | |
523 } | |
524 | |
525 int64_t tmp; | |
526 if (LookupParent(tmp, manager, currentAncestor)) | |
527 { | |
528 currentResource = currentAncestor; | |
529 currentAncestor = tmp; | |
530 currentType = GetParentType(currentType); | |
531 } | |
532 else | |
533 { | |
534 assert(currentType == OrthancPluginResourceType_Study); | |
535 break; | |
536 } | |
537 } | |
538 } | |
539 } | |
540 | |
541 { | |
542 // This is implemented by triggers in the PostgreSQL and MySQL plugins | |
543 DatabaseManager::CachedStatement lookupDeletedAttachments( | |
544 STATEMENT_FROM_HERE, manager, | |
545 "INSERT INTO DeletedFiles SELECT AttachedFiles.* FROM AttachedFiles " | |
546 "INNER JOIN DeletedResources ON AttachedFiles.id = DeletedResources.internalId"); | |
547 lookupDeletedAttachments.Execute(); | |
548 } | |
549 | |
550 { | |
551 // Note that the attachments are automatically deleted by DELETE CASCADE | |
552 DatabaseManager::CachedStatement applyResourcesDeletion( | |
553 STATEMENT_FROM_HERE, manager, | |
554 "DELETE FROM Resources WHERE internalId IN (SELECT internalId FROM DeletedResources)"); | |
555 applyResourcesDeletion.Execute(); | |
556 } | |
557 | |
558 SignalDeletedResources(output, manager); | |
559 SignalDeletedFiles(output, manager); | |
560 | |
561 if (hasRemainingAncestor) | |
562 { | |
563 assert(!remainingAncestor.empty()); | |
564 output.SignalRemainingAncestor(remainingAncestor, ancestorType); | |
565 } | |
566 } | |
567 | |
568 | |
569 static void ExecuteLogChange(DatabaseManager::CachedStatement& statement, | |
570 const Dictionary& args) | |
571 { | |
572 statement.SetParameterType("changeType", ValueType_Integer64); | |
573 statement.SetParameterType("id", ValueType_Integer64); | |
574 statement.SetParameterType("resourceType", ValueType_Integer64); | |
575 statement.SetParameterType("date", ValueType_Utf8String); | |
576 statement.Execute(args); | |
577 } | |
578 | |
579 | |
580 void OdbcIndex::LogChange(DatabaseManager& manager, | |
581 int32_t changeType, | |
582 int64_t resourceId, | |
583 OrthancPluginResourceType resourceType, | |
584 const char* date) | |
585 { | |
586 Dictionary args; | |
587 args.SetIntegerValue("changeType", changeType); | |
588 args.SetIntegerValue("id", resourceId); | |
589 args.SetIntegerValue("resourceType", resourceType); | |
590 args.SetUtf8Value("date", date); | |
591 | |
592 int64_t seq; | |
593 | |
594 switch (manager.GetDatabase().GetDialect()) | |
595 { | |
596 case Dialect_SQLite: | |
597 { | |
598 DatabaseManager::CachedStatement statement( | |
599 STATEMENT_FROM_HERE, manager, | |
600 "INSERT INTO Changes VALUES(NULL, ${changeType}, ${id}, ${resourceType}, ${date})"); | |
601 ExecuteLogChange(statement, args); | |
602 seq = GetSQLiteLastInsert(manager); | |
603 break; | |
604 } | |
605 | |
606 case Dialect_PostgreSQL: | |
607 { | |
608 DatabaseManager::CachedStatement statement( | |
609 STATEMENT_FROM_HERE, manager, | |
610 "INSERT INTO Changes VALUES(DEFAULT, ${changeType}, ${id}, ${resourceType}, ${date}) RETURNING seq"); | |
611 ExecuteLogChange(statement, args); | |
612 seq = statement.ReadInteger64(0); | |
613 break; | |
614 } | |
615 | |
616 case Dialect_MySQL: | |
617 { | |
618 DatabaseManager::CachedStatement statement( | |
619 STATEMENT_FROM_HERE, manager, | |
620 "INSERT INTO Changes VALUES(NULL, ${changeType}, ${id}, ${resourceType}, ${date})"); | |
621 ExecuteLogChange(statement, args); | |
622 seq = GetMySQLLastInsert(manager); | |
623 break; | |
624 } | |
625 | |
626 case Dialect_MSSQL: | |
627 { | |
628 DatabaseManager::CachedStatement statement( | |
629 STATEMENT_FROM_HERE, manager, | |
630 "INSERT INTO Changes VALUES(${changeType}, ${id}, ${resourceType}, ${date})"); | |
631 ExecuteLogChange(statement, args); | |
632 seq = GetMSSQLLastInsert(manager); | |
633 break; | |
634 } | |
635 | |
636 default: | |
637 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
638 } | |
639 | |
640 std::string value = boost::lexical_cast<std::string>(seq); | |
641 SetGlobalProperty(manager, MISSING_SERVER_IDENTIFIER, GlobalProperty_LastChange, value.c_str()); | |
642 } | |
643 | |
644 | |
645 int64_t OdbcIndex::GetLastChangeIndex(DatabaseManager& manager) | |
646 { | |
647 std::string value; | |
648 | |
649 if (LookupGlobalProperty(value, manager, MISSING_SERVER_IDENTIFIER, GlobalProperty_LastChange)) | |
650 { | |
651 return boost::lexical_cast<int64_t>(value); | |
652 } | |
653 else | |
654 { | |
655 return 0; | |
656 } | |
657 } | |
658 | |
659 | |
660 void OdbcIndex::DeleteAttachment(IDatabaseBackendOutput& output, | |
661 DatabaseManager& manager, | |
662 int64_t id, | |
663 int32_t attachment) | |
664 { | |
665 ClearDeletedFiles(manager); | |
666 | |
667 Dictionary args; | |
668 args.SetIntegerValue("id", id); | |
669 args.SetIntegerValue("type", static_cast<int>(attachment)); | |
670 | |
671 { | |
672 // This is implemented by triggers in the PostgreSQL and MySQL plugins | |
673 DatabaseManager::CachedStatement statement( | |
674 STATEMENT_FROM_HERE, manager, | |
675 "INSERT INTO DeletedFiles SELECT * FROM AttachedFiles WHERE id=${id} AND fileType=${type}"); | |
676 | |
677 statement.SetParameterType("id", ValueType_Integer64); | |
678 statement.SetParameterType("type", ValueType_Integer64); | |
679 statement.Execute(args); | |
680 } | |
681 | |
682 { | |
683 DatabaseManager::CachedStatement statement( | |
684 STATEMENT_FROM_HERE, manager, | |
685 "DELETE FROM AttachedFiles WHERE id=${id} AND fileType=${type}"); | |
686 | |
687 statement.SetParameterType("id", ValueType_Integer64); | |
688 statement.SetParameterType("type", ValueType_Integer64); | |
689 statement.Execute(args); | |
690 } | |
691 | |
692 SignalDeletedFiles(output, manager); | |
693 } | |
694 } |