Mercurial > hg > orthanc-databases
comparison PostgreSQL/Plugins/PostgreSQLIndex.cpp @ 569:f18e46d7dbf8 attach-custom-data
merged find-refactoring -> attach-custom-data
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Tue, 24 Sep 2024 15:04:21 +0200 |
parents | cd9521e04249 d8ee2f676a3c |
children | 7453fc5bef1a |
comparison
equal
deleted
inserted
replaced
368:82f73188b58d | 569:f18e46d7dbf8 |
---|---|
1 /** | 1 /** |
2 * Orthanc - A Lightweight, RESTful DICOM Store | 2 * Orthanc - A Lightweight, RESTful DICOM Store |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | 3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics |
4 * Department, University Hospital of Liege, Belgium | 4 * Department, University Hospital of Liege, Belgium |
5 * Copyright (C) 2017-2021 Osimis S.A., Belgium | 5 * Copyright (C) 2017-2023 Osimis S.A., Belgium |
6 * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium | |
7 * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium | |
6 * | 8 * |
7 * This program is free software: you can redistribute it and/or | 9 * This program is free software: you can redistribute it and/or |
8 * modify it under the terms of the GNU Affero General Public License | 10 * modify it under the terms of the GNU Affero General Public License |
9 * as published by the Free Software Foundation, either version 3 of | 11 * as published by the Free Software Foundation, either version 3 of |
10 * the License, or (at your option) any later version. | 12 * the License, or (at your option) any later version. |
27 #include "PostgreSQLDefinitions.h" | 29 #include "PostgreSQLDefinitions.h" |
28 | 30 |
29 #include <EmbeddedResources.h> // Auto-generated file | 31 #include <EmbeddedResources.h> // Auto-generated file |
30 | 32 |
31 #include <Compatibility.h> // For std::unique_ptr<> | 33 #include <Compatibility.h> // For std::unique_ptr<> |
34 #include <Toolbox.h> | |
32 #include <Logging.h> | 35 #include <Logging.h> |
33 #include <OrthancException.h> | 36 #include <OrthancException.h> |
34 | 37 |
35 | 38 |
36 namespace Orthanc | 39 namespace Orthanc |
57 IDatabaseFactory* PostgreSQLIndex::CreateDatabaseFactory() | 60 IDatabaseFactory* PostgreSQLIndex::CreateDatabaseFactory() |
58 { | 61 { |
59 return PostgreSQLDatabase::CreateDatabaseFactory(parameters_); | 62 return PostgreSQLDatabase::CreateDatabaseFactory(parameters_); |
60 } | 63 } |
61 | 64 |
65 void PostgreSQLIndex::ApplyPrepareIndex(DatabaseManager::Transaction& t, DatabaseManager& manager) | |
66 { | |
67 std::string query; | |
68 | |
69 Orthanc::EmbeddedResources::GetFileResource | |
70 (query, Orthanc::EmbeddedResources::POSTGRESQL_PREPARE_INDEX); | |
71 t.GetDatabaseTransaction().ExecuteMultiLines(query); | |
72 } | |
62 | 73 |
63 void PostgreSQLIndex::ConfigureDatabase(DatabaseManager& manager) | 74 void PostgreSQLIndex::ConfigureDatabase(DatabaseManager& manager, |
75 bool hasIdentifierTags, | |
76 const std::list<IdentifierTag>& identifierTags) | |
64 { | 77 { |
65 uint32_t expectedVersion = 6; | 78 uint32_t expectedVersion = 6; |
66 | 79 |
67 if (GetContext()) // "GetContext()" can possibly be NULL in the unit tests | 80 if (GetContext()) // "GetContext()" can possibly be NULL in the unit tests |
68 { | 81 { |
71 | 84 |
72 // Check the expected version of the database | 85 // Check the expected version of the database |
73 if (expectedVersion != 6) | 86 if (expectedVersion != 6) |
74 { | 87 { |
75 LOG(ERROR) << "This database plugin is incompatible with your version of Orthanc " | 88 LOG(ERROR) << "This database plugin is incompatible with your version of Orthanc " |
76 << "expecting the DB schema version " << expectedVersion | 89 << "expecting the Orthanc DB schema version " << expectedVersion |
77 << ", but this plugin is only compatible with version 6"; | 90 << ", but this plugin is only compatible with version 6"; |
78 throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin); | 91 throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin); |
79 } | 92 } |
80 | 93 |
81 PostgreSQLDatabase& db = dynamic_cast<PostgreSQLDatabase&>(manager.GetDatabase()); | 94 PostgreSQLDatabase& db = dynamic_cast<PostgreSQLDatabase&>(manager.GetDatabase()); |
84 { | 97 { |
85 db.AdvisoryLock(POSTGRESQL_LOCK_INDEX); | 98 db.AdvisoryLock(POSTGRESQL_LOCK_INDEX); |
86 } | 99 } |
87 | 100 |
88 { | 101 { |
102 // lock the full DB while checking if it needs to be create/ugraded | |
89 PostgreSQLDatabase::TransientAdvisoryLock lock(db, POSTGRESQL_LOCK_DATABASE_SETUP); | 103 PostgreSQLDatabase::TransientAdvisoryLock lock(db, POSTGRESQL_LOCK_DATABASE_SETUP); |
90 | 104 |
91 if (clearAll_) | 105 if (clearAll_) |
92 { | 106 { |
93 db.ClearAll(); | 107 db.ClearAll(); |
96 { | 110 { |
97 DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); | 111 DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); |
98 | 112 |
99 if (!t.GetDatabaseTransaction().DoesTableExist("Resources")) | 113 if (!t.GetDatabaseTransaction().DoesTableExist("Resources")) |
100 { | 114 { |
101 std::string query; | 115 LOG(WARNING) << "PostgreSQL is creating the database schema"; |
102 | 116 |
103 Orthanc::EmbeddedResources::GetFileResource | 117 ApplyPrepareIndex(t, manager); |
104 (query, Orthanc::EmbeddedResources::POSTGRESQL_PREPARE_INDEX); | 118 |
105 t.GetDatabaseTransaction().ExecuteMultiLines(query); | 119 if (!t.GetDatabaseTransaction().DoesTableExist("Resources")) |
106 | 120 { |
107 SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabaseSchemaVersion, expectedVersion); | 121 LOG(ERROR) << "Corrupted PostgreSQL database or failed to create the database schema"; |
108 SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel, 1); | 122 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); |
109 SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_HasTrigramIndex, 0); | |
110 } | |
111 | |
112 if (!t.GetDatabaseTransaction().DoesTableExist("Resources")) | |
113 { | |
114 LOG(ERROR) << "Corrupted PostgreSQL database"; | |
115 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
116 } | |
117 | |
118 int version = 0; | |
119 if (!LookupGlobalIntegerProperty(version, manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabaseSchemaVersion) || | |
120 version != 6) | |
121 { | |
122 LOG(ERROR) << "PostgreSQL plugin is incompatible with database schema version: " << version; | |
123 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); | |
124 } | |
125 | |
126 int revision; | |
127 if (!LookupGlobalIntegerProperty(revision, manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel)) | |
128 { | |
129 revision = 1; | |
130 SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel, revision); | |
131 } | |
132 | |
133 if (revision != 1) | |
134 { | |
135 LOG(ERROR) << "PostgreSQL plugin is incompatible with database schema revision: " << revision; | |
136 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); | |
137 } | |
138 | |
139 t.Commit(); | |
140 } | |
141 | |
142 { | |
143 DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); | |
144 | |
145 int hasTrigram = 0; | |
146 if (!LookupGlobalIntegerProperty(hasTrigram, manager, MISSING_SERVER_IDENTIFIER, | |
147 Orthanc::GlobalProperty_HasTrigramIndex) || | |
148 hasTrigram != 1) | |
149 { | |
150 /** | |
151 * Apply fix for performance issue (speed up wildcard search | |
152 * by using GIN trigrams). This implements the patch suggested | |
153 * in issue #47, BUT we also keep the original | |
154 * "DicomIdentifiersIndexValues", as it leads to better | |
155 * performance for "strict" searches (i.e. searches involving | |
156 * no wildcard). | |
157 * https://www.postgresql.org/docs/current/static/pgtrgm.html | |
158 * https://bugs.orthanc-server.com/show_bug.cgi?id=47 | |
159 **/ | |
160 try | |
161 { | |
162 // We've observed 9 minutes on DB with 100000 studies | |
163 LOG(WARNING) << "Trying to enable trigram matching on the PostgreSQL database " | |
164 << "to speed up wildcard searches. This may take several minutes"; | |
165 | |
166 t.GetDatabaseTransaction().ExecuteMultiLines( | |
167 "CREATE EXTENSION IF NOT EXISTS pg_trgm; " | |
168 "CREATE INDEX DicomIdentifiersIndexValues2 ON DicomIdentifiers USING gin(value gin_trgm_ops);"); | |
169 | |
170 SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_HasTrigramIndex, 1); | |
171 LOG(WARNING) << "Trigram index has been created"; | |
172 | |
173 t.Commit(); | |
174 } | |
175 catch (Orthanc::OrthancException&) | |
176 { | |
177 LOG(WARNING) << "Performance warning: Your PostgreSQL server does " | |
178 << "not support trigram matching"; | |
179 LOG(WARNING) << "-> Consider installing the \"pg_trgm\" extension on the " | |
180 << "PostgreSQL server, e.g. on Debian: sudo apt install postgresql-contrib"; | |
181 } | 123 } |
182 } | 124 } |
183 else | 125 else |
184 { | 126 { |
185 t.Commit(); | 127 LOG(WARNING) << "The database schema already exists, checking if it needs to be updated"; |
186 } | 128 |
187 } | 129 int version = 0; |
188 | 130 if (!LookupGlobalIntegerProperty(version, manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabaseSchemaVersion) || |
189 { | 131 version != 6) |
190 DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); | 132 { |
191 | 133 LOG(ERROR) << "PostgreSQL plugin is incompatible with Orthanc database schema version: " << version; |
192 int property = 0; | 134 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); |
193 if (!LookupGlobalIntegerProperty(property, manager, MISSING_SERVER_IDENTIFIER, | 135 } |
194 Orthanc::GlobalProperty_HasCreateInstance) || | 136 |
195 property != 2) | 137 bool needToRunUpgradeFromUnknownToV1 = false; |
196 { | 138 bool needToRunUpgradeV1toV2 = false; |
197 LOG(INFO) << "Installing the CreateInstance extension"; | 139 bool needToRunUpgradeV2toV3 = false; |
198 | 140 |
199 if (property == 1) | 141 int revision; |
200 { | 142 if (!LookupGlobalIntegerProperty(revision, manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel)) |
201 // Drop older, experimental versions of this extension | 143 { |
202 t.GetDatabaseTransaction().ExecuteMultiLines("DROP FUNCTION CreateInstance(" | 144 LOG(WARNING) << "No DatabasePatchLevel found, assuming it's 1"; |
203 "IN patient TEXT, IN study TEXT, IN series TEXT, in instance TEXT)"); | 145 revision = 1; |
204 } | 146 needToRunUpgradeFromUnknownToV1 = true; |
205 | 147 needToRunUpgradeV1toV2 = true; |
206 std::string query; | 148 } |
207 Orthanc::EmbeddedResources::GetFileResource | 149 else if (revision == 1) |
208 (query, Orthanc::EmbeddedResources::POSTGRESQL_CREATE_INSTANCE); | 150 { |
209 t.GetDatabaseTransaction().ExecuteMultiLines(query); | 151 LOG(WARNING) << "DatabasePatchLevel is 1"; |
210 | 152 needToRunUpgradeFromUnknownToV1 = true; |
211 SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_HasCreateInstance, 2); | 153 needToRunUpgradeV1toV2 = true; |
212 } | 154 needToRunUpgradeV2toV3 = true; |
213 | 155 } |
214 | 156 else if (revision == 2) |
215 if (!LookupGlobalIntegerProperty(property, manager, MISSING_SERVER_IDENTIFIER, | 157 { |
216 Orthanc::GlobalProperty_GetTotalSizeIsFast) || | 158 LOG(WARNING) << "DatabasePatchLevel is 2"; |
217 property != 1) | 159 needToRunUpgradeV2toV3 = true; |
218 { | 160 } |
219 LOG(INFO) << "Installing the FastTotalSize extension"; | 161 |
220 | 162 int hasTrigram = 0; |
221 std::string query; | 163 if (!LookupGlobalIntegerProperty(hasTrigram, manager, MISSING_SERVER_IDENTIFIER, |
222 Orthanc::EmbeddedResources::GetFileResource | 164 Orthanc::GlobalProperty_HasTrigramIndex) || |
223 (query, Orthanc::EmbeddedResources::POSTGRESQL_FAST_TOTAL_SIZE); | 165 hasTrigram != 1) |
224 t.GetDatabaseTransaction().ExecuteMultiLines(query); | 166 { |
225 | 167 // We've observed 9 minutes on DB with 100000 studies |
226 SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_GetTotalSizeIsFast, 1); | 168 LOG(WARNING) << "The DB schema update will try to enable trigram matching on the PostgreSQL database " |
227 } | 169 << "to speed up wildcard searches. This may take several minutes"; |
228 | 170 needToRunUpgradeV1toV2 = true; |
229 | 171 } |
230 // Installing this extension requires the "GlobalIntegers" table | 172 |
231 // created by the "FastTotalSize" extension | 173 int property = 0; |
232 property = 0; | 174 if (!LookupGlobalIntegerProperty(property, manager, MISSING_SERVER_IDENTIFIER, |
233 if (!LookupGlobalIntegerProperty(property, manager, MISSING_SERVER_IDENTIFIER, | 175 Orthanc::GlobalProperty_HasFastCountResources) || |
234 Orthanc::GlobalProperty_HasFastCountResources) || | 176 property != 1) |
235 property != 1) | 177 { |
236 { | 178 needToRunUpgradeV1toV2 = true; |
237 LOG(INFO) << "Installing the FastCountResources extension"; | 179 } |
238 | 180 if (!LookupGlobalIntegerProperty(property, manager, MISSING_SERVER_IDENTIFIER, |
239 std::string query; | 181 Orthanc::GlobalProperty_GetTotalSizeIsFast) || |
240 Orthanc::EmbeddedResources::GetFileResource | 182 property != 1) |
241 (query, Orthanc::EmbeddedResources::POSTGRESQL_FAST_COUNT_RESOURCES); | 183 { |
242 t.GetDatabaseTransaction().ExecuteMultiLines(query); | 184 needToRunUpgradeV1toV2 = true; |
243 | 185 } |
244 SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_HasFastCountResources, 1); | 186 if (!LookupGlobalIntegerProperty(property, manager, MISSING_SERVER_IDENTIFIER, |
245 } | 187 Orthanc::GlobalProperty_GetLastChangeIndex) || |
246 | 188 property != 1) |
247 | 189 { |
248 // Installing this extension requires the "GlobalIntegers" table | 190 needToRunUpgradeV1toV2 = true; |
249 // created by the "GetLastChangeIndex" extension | 191 } |
250 property = 0; | 192 |
251 if (!LookupGlobalIntegerProperty(property, manager, MISSING_SERVER_IDENTIFIER, | 193 if (needToRunUpgradeFromUnknownToV1) |
252 Orthanc::GlobalProperty_GetLastChangeIndex) || | 194 { |
253 property != 1) | 195 LOG(WARNING) << "Upgrading DB schema from unknown to revision 1"; |
254 { | 196 std::string query; |
255 LOG(INFO) << "Installing the GetLastChangeIndex extension"; | 197 |
256 | 198 Orthanc::EmbeddedResources::GetFileResource |
257 std::string query; | 199 (query, Orthanc::EmbeddedResources::POSTGRESQL_UPGRADE_UNKNOWN_TO_REV1); |
258 Orthanc::EmbeddedResources::GetFileResource | 200 t.GetDatabaseTransaction().ExecuteMultiLines(query); |
259 (query, Orthanc::EmbeddedResources::POSTGRESQL_GET_LAST_CHANGE_INDEX); | 201 } |
260 t.GetDatabaseTransaction().ExecuteMultiLines(query); | 202 |
261 | 203 if (needToRunUpgradeV1toV2) |
262 SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_GetLastChangeIndex, 1); | 204 { |
263 } | 205 LOG(WARNING) << "Upgrading DB schema from revision 1 to revision 2"; |
264 | 206 |
265 t.Commit(); | 207 std::string query; |
266 } | 208 |
267 | 209 Orthanc::EmbeddedResources::GetFileResource |
268 | 210 (query, Orthanc::EmbeddedResources::POSTGRESQL_UPGRADE_REV1_TO_REV2); |
269 { | 211 t.GetDatabaseTransaction().ExecuteMultiLines(query); |
270 // New in release 4.0 to deal with multiple writers | 212 |
271 DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); | 213 // apply all idempotent changes that are in the PrepareIndex |
272 | 214 ApplyPrepareIndex(t, manager); |
273 if (!t.GetDatabaseTransaction().DoesTableExist("ServerProperties")) | 215 } |
274 { | 216 |
275 t.GetDatabaseTransaction().ExecuteMultiLines("CREATE TABLE ServerProperties(server VARCHAR(64) NOT NULL, " | 217 if (needToRunUpgradeV2toV3) |
276 "property INTEGER, value TEXT, PRIMARY KEY(server, property))"); | 218 { |
277 } | 219 LOG(WARNING) << "Upgrading DB schema from revision 2 to revision 3"; |
278 | 220 |
279 /** | 221 std::string query; |
280 * PostgreSQL 9.5: "Adding a column with a default requires | 222 |
281 * updating each row of the table (to store the new column | 223 Orthanc::EmbeddedResources::GetFileResource |
282 * value). However, if no default is specified, PostgreSQL is | 224 (query, Orthanc::EmbeddedResources::POSTGRESQL_UPGRADE_REV2_TO_REV3); |
283 * able to avoid the physical update." => We set no default | 225 t.GetDatabaseTransaction().ExecuteMultiLines(query); |
284 * for performance (older entries will be NULL) | 226 |
285 * https://www.postgresql.org/docs/9.5/ddl-alter.html | 227 // apply all idempotent changes that are in the PrepareIndex (update triggers + set Patch level to 3) |
286 **/ | 228 ApplyPrepareIndex(t, manager); |
287 if (!db.DoesColumnExist("Metadata", "revision")) | 229 } |
288 { | |
289 t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE Metadata ADD COLUMN revision INTEGER"); | |
290 } | |
291 | |
292 if (!db.DoesColumnExist("AttachedFiles", "revision")) | |
293 { | |
294 t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE AttachedFiles ADD COLUMN revision INTEGER"); | |
295 } | |
296 | |
297 // new in v 4.X | |
298 if (!db.DoesColumnExist("DeletedFiles", "revision")) | |
299 { | |
300 t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE DeletedFiles ADD COLUMN revision INTEGER"); | |
301 } | |
302 | |
303 if (!db.DoesColumnExist("AttachedFiles", "customData")) | |
304 { | |
305 t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE AttachedFiles ADD COLUMN customData TEXT"); | |
306 } | |
307 | |
308 if (!db.DoesColumnExist("DeletedFiles", "customData")) | |
309 { | |
310 // add the column and modify the trigger | |
311 t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE DeletedFiles ADD COLUMN customData TEXT"); | |
312 | |
313 t.GetDatabaseTransaction().ExecuteMultiLines( | |
314 "DROP TRIGGER AttachedFileDeleted ON AttachedFiles"); | |
315 | |
316 t.GetDatabaseTransaction().ExecuteMultiLines( | |
317 "DROP FUNCTION AttachedFileDeletedFunc"); | |
318 | |
319 t.GetDatabaseTransaction().ExecuteMultiLines( | |
320 "CREATE FUNCTION AttachedFileDeletedFunc() " | |
321 "RETURNS TRIGGER AS $body$" | |
322 "BEGIN" | |
323 " INSERT INTO DeletedFiles VALUES" | |
324 " (old.uuid, old.filetype, old.compressedSize," | |
325 " old.uncompressedSize, old.compressionType," | |
326 " old.uncompressedHash, old.compressedHash," | |
327 " old.revision, old.customData);" | |
328 " RETURN NULL;" | |
329 "END;" | |
330 "$body$ LANGUAGE plpgsql;"); | |
331 | |
332 t.GetDatabaseTransaction().ExecuteMultiLines( | |
333 "CREATE TRIGGER AttachedFileDeleted " | |
334 "AFTER DELETE ON AttachedFiles " | |
335 "FOR EACH ROW " | |
336 "EXECUTE PROCEDURE AttachedFileDeletedFunc();" | |
337 ); | |
338 } | 230 } |
339 | 231 |
340 t.Commit(); | 232 t.Commit(); |
341 } | 233 } |
342 } | 234 } |
370 uint64_t result; | 262 uint64_t result; |
371 | 263 |
372 { | 264 { |
373 DatabaseManager::CachedStatement statement( | 265 DatabaseManager::CachedStatement statement( |
374 STATEMENT_FROM_HERE, manager, | 266 STATEMENT_FROM_HERE, manager, |
375 "SELECT value FROM GlobalIntegers WHERE key = 0"); | 267 "SELECT * FROM UpdateSingleStatistic(0)"); |
376 | 268 |
377 statement.SetReadOnly(true); | |
378 statement.Execute(); | 269 statement.Execute(); |
379 | 270 |
380 result = static_cast<uint64_t>(statement.ReadInteger64(0)); | 271 result = static_cast<uint64_t>(statement.ReadInteger64(0)); |
381 } | 272 } |
382 | 273 |
383 assert(result == IndexBackend::GetTotalCompressedSize(manager)); | 274 // disabled because this is not alway true while transactions are being executed in READ COMITTED TRANSACTION. This is however true when no files are being delete/added |
275 //assert(result == IndexBackend::GetTotalCompressedSize(manager)); | |
384 return result; | 276 return result; |
385 } | 277 } |
386 | 278 |
387 | 279 |
388 uint64_t PostgreSQLIndex::GetTotalUncompressedSize(DatabaseManager& manager) | 280 uint64_t PostgreSQLIndex::GetTotalUncompressedSize(DatabaseManager& manager) |
391 uint64_t result; | 283 uint64_t result; |
392 | 284 |
393 { | 285 { |
394 DatabaseManager::CachedStatement statement( | 286 DatabaseManager::CachedStatement statement( |
395 STATEMENT_FROM_HERE, manager, | 287 STATEMENT_FROM_HERE, manager, |
396 "SELECT value FROM GlobalIntegers WHERE key = 1"); | 288 "SELECT * FROM UpdateSingleStatistic(1)"); |
397 | 289 |
398 statement.SetReadOnly(true); | |
399 statement.Execute(); | 290 statement.Execute(); |
400 | 291 |
401 result = static_cast<uint64_t>(statement.ReadInteger64(0)); | 292 result = static_cast<uint64_t>(statement.ReadInteger64(0)); |
402 } | 293 } |
403 | 294 |
404 assert(result == IndexBackend::GetTotalUncompressedSize(manager)); | 295 // disabled because this is not alway true while transactions are being executed in READ COMITTED TRANSACTION. This is however true when no files are being delete/added |
296 // assert(result == IndexBackend::GetTotalUncompressedSize(manager)); | |
405 return result; | 297 return result; |
406 } | 298 } |
299 | |
300 int64_t PostgreSQLIndex::IncrementGlobalProperty(DatabaseManager& manager, | |
301 const char* serverIdentifier, | |
302 int32_t property, | |
303 int64_t increment) | |
304 { | |
305 if (serverIdentifier == NULL) | |
306 { | |
307 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
308 } | |
309 else | |
310 { | |
311 if (strlen(serverIdentifier) == 0) | |
312 { | |
313 DatabaseManager::CachedStatement statement( | |
314 STATEMENT_FROM_HERE, manager, | |
315 "INSERT INTO GlobalProperties (property, value) VALUES(${property}, ${increment}) " | |
316 " ON CONFLICT (property) DO UPDATE SET value = CAST(GlobalProperties.value AS BIGINT) + ${increment}" | |
317 " RETURNING CAST(value AS BIGINT)"); | |
318 | |
319 statement.SetParameterType("property", ValueType_Integer64); | |
320 statement.SetParameterType("increment", ValueType_Integer64); | |
321 | |
322 Dictionary args; | |
323 args.SetIntegerValue("property", property); | |
324 args.SetIntegerValue("increment", increment); | |
325 | |
326 statement.Execute(args); | |
327 | |
328 return statement.ReadInteger64(0); | |
329 } | |
330 else | |
331 { | |
332 DatabaseManager::CachedStatement statement( | |
333 STATEMENT_FROM_HERE, manager, | |
334 "INSERT INTO ServerProperties (server, property, value) VALUES(${server}, ${property}, ${increment}) " | |
335 " ON CONFLICT (server, property) DO UPDATE SET value = CAST(ServerProperties.value AS BIGINT) + ${increment}" | |
336 " RETURNING CAST(value AS BIGINT)"); | |
337 | |
338 statement.SetParameterType("server", ValueType_Utf8String); | |
339 statement.SetParameterType("property", ValueType_Integer64); | |
340 statement.SetParameterType("increment", ValueType_Integer64); | |
341 | |
342 Dictionary args; | |
343 args.SetUtf8Value("server", serverIdentifier); | |
344 args.SetIntegerValue("property", property); | |
345 args.SetIntegerValue("increment", increment); | |
346 | |
347 statement.Execute(args); | |
348 | |
349 return statement.ReadInteger64(0); | |
350 } | |
351 } | |
352 } | |
353 | |
354 void PostgreSQLIndex::UpdateAndGetStatistics(DatabaseManager& manager, | |
355 int64_t& patientsCount, | |
356 int64_t& studiesCount, | |
357 int64_t& seriesCount, | |
358 int64_t& instancesCount, | |
359 int64_t& compressedSize, | |
360 int64_t& uncompressedSize) | |
361 { | |
362 DatabaseManager::CachedStatement statement( | |
363 STATEMENT_FROM_HERE, manager, | |
364 "SELECT * FROM UpdateStatistics()"); | |
365 | |
366 statement.Execute(); | |
367 | |
368 patientsCount = statement.ReadInteger64(0); | |
369 studiesCount = statement.ReadInteger64(1); | |
370 seriesCount = statement.ReadInteger64(2); | |
371 instancesCount = statement.ReadInteger64(3); | |
372 compressedSize = statement.ReadInteger64(4); | |
373 uncompressedSize = statement.ReadInteger64(5); | |
374 } | |
375 | |
376 void PostgreSQLIndex::ClearDeletedFiles(DatabaseManager& manager) | |
377 { | |
378 { // note: the temporary table lifespan is the session, not the transaction -> that's why we need the IF NOT EXISTS | |
379 DatabaseManager::CachedStatement statement( | |
380 STATEMENT_FROM_HERE, manager, | |
381 "SELECT CreateDeletedFilesTemporaryTable()" | |
382 ); | |
383 statement.ExecuteWithoutResult(); | |
384 } | |
385 | |
386 } | |
387 | |
388 void PostgreSQLIndex::ClearDeletedResources(DatabaseManager& manager) | |
389 { | |
390 { // note: the temporary table lifespan is the session, not the transaction -> that's why we need the IF NOT EXISTS | |
391 DatabaseManager::CachedStatement statement( | |
392 STATEMENT_FROM_HERE, manager, | |
393 "CREATE TEMPORARY TABLE IF NOT EXISTS DeletedResources(" | |
394 "resourceType INTEGER NOT NULL," | |
395 "publicId VARCHAR(64) NOT NULL" | |
396 ");" | |
397 ); | |
398 statement.Execute(); | |
399 } | |
400 { | |
401 DatabaseManager::CachedStatement statement( | |
402 STATEMENT_FROM_HERE, manager, | |
403 "DELETE FROM DeletedResources;" | |
404 ); | |
405 | |
406 statement.Execute(); | |
407 } | |
408 | |
409 } | |
410 | |
411 void PostgreSQLIndex::ClearRemainingAncestor(DatabaseManager& manager) | |
412 { | |
413 } | |
414 | |
415 void PostgreSQLIndex::DeleteResource(IDatabaseBackendOutput& output, | |
416 DatabaseManager& manager, | |
417 int64_t id) | |
418 { | |
419 // clearing of temporary table is now implemented in the funcion DeleteResource | |
420 DatabaseManager::CachedStatement statement( | |
421 STATEMENT_FROM_HERE, manager, | |
422 "SELECT * FROM DeleteResource(${id})"); | |
423 | |
424 statement.SetParameterType("id", ValueType_Integer64); | |
425 | |
426 Dictionary args; | |
427 args.SetIntegerValue("id", id); | |
428 | |
429 statement.Execute(args); | |
430 | |
431 if (statement.IsDone() || | |
432 statement.GetResultFieldsCount() != 2) | |
433 { | |
434 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); | |
435 } | |
436 | |
437 statement.SetResultFieldType(0, ValueType_Integer64); | |
438 statement.SetResultFieldType(1, ValueType_Utf8String); | |
439 | |
440 if (!statement.IsNull(0)) | |
441 { | |
442 output.SignalRemainingAncestor( | |
443 statement.ReadString(1), | |
444 static_cast<OrthancPluginResourceType>(statement.ReadInteger32(0))); | |
445 } | |
446 | |
447 SignalDeletedFiles(output, manager); | |
448 SignalDeletedResources(output, manager); | |
449 } | |
450 | |
407 | 451 |
408 | 452 |
409 #if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 | 453 #if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 |
410 void PostgreSQLIndex::CreateInstance(OrthancPluginCreateInstanceResult& result, | 454 void PostgreSQLIndex::CreateInstance(OrthancPluginCreateInstanceResult& result, |
411 DatabaseManager& manager, | 455 DatabaseManager& manager, |
426 Dictionary args; | 470 Dictionary args; |
427 args.SetUtf8Value("patient", hashPatient); | 471 args.SetUtf8Value("patient", hashPatient); |
428 args.SetUtf8Value("study", hashStudy); | 472 args.SetUtf8Value("study", hashStudy); |
429 args.SetUtf8Value("series", hashSeries); | 473 args.SetUtf8Value("series", hashSeries); |
430 args.SetUtf8Value("instance", hashInstance); | 474 args.SetUtf8Value("instance", hashInstance); |
431 | 475 |
432 statement.Execute(args); | 476 statement.Execute(args); |
433 | 477 |
434 if (statement.IsDone() || | 478 if (statement.IsDone() || |
435 statement.GetResultFieldsCount() != 8) | 479 statement.GetResultFieldsCount() != 8) |
436 { | 480 { |
439 | 483 |
440 for (size_t i = 0; i < 8; i++) | 484 for (size_t i = 0; i < 8; i++) |
441 { | 485 { |
442 statement.SetResultFieldType(i, ValueType_Integer64); | 486 statement.SetResultFieldType(i, ValueType_Integer64); |
443 } | 487 } |
488 | |
489 // LOG(INFO) << statement.ReadInteger64(0) << statement.ReadInteger64(1) << statement.ReadInteger64(2) << statement.ReadInteger64(3); | |
444 | 490 |
445 result.isNewInstance = (statement.ReadInteger64(3) == 1); | 491 result.isNewInstance = (statement.ReadInteger64(3) == 1); |
446 result.instanceId = statement.ReadInteger64(7); | 492 result.instanceId = statement.ReadInteger64(7); |
447 | 493 |
448 if (result.isNewInstance) | 494 if (result.isNewInstance) |
456 } | 502 } |
457 } | 503 } |
458 #endif | 504 #endif |
459 | 505 |
460 | 506 |
507 #if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 | |
508 static void ExecuteSetResourcesContentTags( | |
509 DatabaseManager& manager, | |
510 const std::string& table, | |
511 uint32_t count, | |
512 const OrthancPluginResourcesContentTags* tags) | |
513 { | |
514 std::string sql; | |
515 | |
516 std::vector<std::string> resourceIds; | |
517 std::vector<std::string> groups; | |
518 std::vector<std::string> elements; | |
519 std::vector<std::string> values; | |
520 | |
521 Dictionary args; | |
522 | |
523 for (uint32_t i = 0; i < count; i++) | |
524 { | |
525 std::string resourceArgName = "r" + boost::lexical_cast<std::string>(i); | |
526 std::string groupArgName = "g" + boost::lexical_cast<std::string>(i); | |
527 std::string elementArgName = "e" + boost::lexical_cast<std::string>(i); | |
528 std::string valueArgName = "v" + boost::lexical_cast<std::string>(i); | |
529 | |
530 args.SetIntegerValue(resourceArgName, tags[i].resource); | |
531 args.SetInteger32Value(elementArgName, tags[i].element); | |
532 args.SetInteger32Value(groupArgName, tags[i].group); | |
533 args.SetUtf8Value(valueArgName, tags[i].value); | |
534 | |
535 std::string insert = ("(${" + resourceArgName + "}, ${" + | |
536 groupArgName + "}, ${" + | |
537 elementArgName + "}, " + | |
538 "${" + valueArgName + "})"); | |
539 | |
540 if (sql.empty()) | |
541 { | |
542 sql = "INSERT INTO " + table + " VALUES " + insert; | |
543 } | |
544 else | |
545 { | |
546 sql += ", " + insert; | |
547 } | |
548 } | |
549 | |
550 if (!sql.empty()) | |
551 { | |
552 DatabaseManager::CachedStatement statement(STATEMENT_FROM_HERE_DYNAMIC(sql), manager, sql); | |
553 | |
554 for (uint32_t i = 0; i < count; i++) | |
555 { | |
556 statement.SetParameterType("r" + boost::lexical_cast<std::string>(i), | |
557 ValueType_Integer64); | |
558 statement.SetParameterType("g" + boost::lexical_cast<std::string>(i), | |
559 ValueType_Integer32); | |
560 statement.SetParameterType("e" + boost::lexical_cast<std::string>(i), | |
561 ValueType_Integer32); | |
562 statement.SetParameterType("v" + boost::lexical_cast<std::string>(i), | |
563 ValueType_Utf8String); | |
564 } | |
565 | |
566 statement.Execute(args); | |
567 } | |
568 } | |
569 #endif | |
570 | |
571 | |
572 #if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 | |
573 static void ExecuteSetResourcesContentMetadata( | |
574 DatabaseManager& manager, | |
575 bool hasRevisionsSupport, | |
576 uint32_t count, | |
577 const OrthancPluginResourcesContentMetadata* metadata) | |
578 { | |
579 if (count < 1) | |
580 { | |
581 return; | |
582 } | |
583 | |
584 std::vector<std::string> resourceIds; | |
585 std::vector<std::string> metadataTypes; | |
586 std::vector<std::string> metadataValues; | |
587 std::vector<std::string> revisions; | |
588 | |
589 Dictionary args; | |
590 | |
591 for (uint32_t i = 0; i < count; i++) | |
592 { | |
593 std::string resourceArgName = "r" + boost::lexical_cast<std::string>(i); | |
594 std::string typeArgName = "t" + boost::lexical_cast<std::string>(i); | |
595 std::string valueArgName = "v" + boost::lexical_cast<std::string>(i); | |
596 | |
597 args.SetIntegerValue(resourceArgName, metadata[i].resource); | |
598 args.SetInteger32Value(typeArgName, metadata[i].metadata); | |
599 args.SetUtf8Value(valueArgName, metadata[i].value); | |
600 | |
601 resourceIds.push_back("${" + resourceArgName + "}"); | |
602 metadataTypes.push_back("${" + typeArgName + "}"); | |
603 metadataValues.push_back("${" + valueArgName + "}"); | |
604 revisions.push_back("0"); | |
605 } | |
606 | |
607 std::string joinedResourceIds; | |
608 std::string joinedMetadataTypes; | |
609 std::string joinedMetadataValues; | |
610 std::string joinedRevisions; | |
611 | |
612 Orthanc::Toolbox::JoinStrings(joinedResourceIds, resourceIds, ","); | |
613 Orthanc::Toolbox::JoinStrings(joinedMetadataTypes, metadataTypes, ","); | |
614 Orthanc::Toolbox::JoinStrings(joinedMetadataValues, metadataValues, ","); | |
615 Orthanc::Toolbox::JoinStrings(joinedRevisions, revisions, ","); | |
616 | |
617 std::string sql = std::string("SELECT InsertOrUpdateMetadata(ARRAY[") + | |
618 joinedResourceIds + "], ARRAY[" + | |
619 joinedMetadataTypes + "], ARRAY[" + | |
620 joinedMetadataValues + "], ARRAY[" + | |
621 joinedRevisions + "])"; | |
622 | |
623 DatabaseManager::CachedStatement statement(STATEMENT_FROM_HERE_DYNAMIC(sql), manager, sql); | |
624 | |
625 for (uint32_t i = 0; i < count; i++) | |
626 { | |
627 statement.SetParameterType("v" + boost::lexical_cast<std::string>(i), | |
628 ValueType_Utf8String); | |
629 statement.SetParameterType("r" + boost::lexical_cast<std::string>(i), | |
630 ValueType_Integer64); | |
631 statement.SetParameterType("t" + boost::lexical_cast<std::string>(i), | |
632 ValueType_Integer32); | |
633 } | |
634 | |
635 statement.Execute(args); | |
636 } | |
637 #endif | |
638 | |
639 | |
640 void PostgreSQLIndex::SetResourcesContent(DatabaseManager& manager, | |
641 uint32_t countIdentifierTags, | |
642 const OrthancPluginResourcesContentTags* identifierTags, | |
643 uint32_t countMainDicomTags, | |
644 const OrthancPluginResourcesContentTags* mainDicomTags, | |
645 uint32_t countMetadata, | |
646 const OrthancPluginResourcesContentMetadata* metadata) | |
647 { | |
648 ExecuteSetResourcesContentTags(manager, "DicomIdentifiers", countIdentifierTags, identifierTags); | |
649 | |
650 ExecuteSetResourcesContentTags(manager, "MainDicomTags", countMainDicomTags, mainDicomTags); | |
651 | |
652 ExecuteSetResourcesContentMetadata(manager, HasRevisionsSupport(), countMetadata, metadata); | |
653 | |
654 } | |
655 | |
656 | |
461 uint64_t PostgreSQLIndex::GetResourcesCount(DatabaseManager& manager, | 657 uint64_t PostgreSQLIndex::GetResourcesCount(DatabaseManager& manager, |
462 OrthancPluginResourceType resourceType) | 658 OrthancPluginResourceType resourceType) |
463 { | 659 { |
464 // Optimized version thanks to the "FastCountResources.sql" extension | 660 // Optimized version thanks to the "FastCountResources.sql" extension |
465 | 661 |
469 OrthancPluginResourceType_Instance == 3); | 665 OrthancPluginResourceType_Instance == 3); |
470 | 666 |
471 uint64_t result; | 667 uint64_t result; |
472 | 668 |
473 { | 669 { |
474 DatabaseManager::CachedStatement statement( | 670 DatabaseManager::StandaloneStatement statement( |
475 STATEMENT_FROM_HERE, manager, | 671 manager, |
476 "SELECT value FROM GlobalIntegers WHERE key = ${key}"); | 672 std::string("SELECT * FROM UpdateSingleStatistic(") + boost::lexical_cast<std::string>(resourceType + 2) + ")"); // For an explanation of the "+ 2" below, check out "PrepareIndex.sql" |
477 | 673 |
478 statement.SetParameterType("key", ValueType_Integer64); | 674 statement.Execute(); |
479 | |
480 Dictionary args; | |
481 | |
482 // For an explanation of the "+ 2" below, check out "FastCountResources.sql" | |
483 args.SetIntegerValue("key", static_cast<int>(resourceType + 2)); | |
484 | |
485 statement.SetReadOnly(true); | |
486 statement.Execute(args); | |
487 | 675 |
488 result = static_cast<uint64_t>(statement.ReadInteger64(0)); | 676 result = static_cast<uint64_t>(statement.ReadInteger64(0)); |
489 } | 677 } |
490 | 678 |
679 // disabled because this is not alway true while transactions are being executed in READ COMITTED TRANSACTION. This is however true when no files are being delete/added | |
491 assert(result == IndexBackend::GetResourcesCount(manager, resourceType)); | 680 assert(result == IndexBackend::GetResourcesCount(manager, resourceType)); |
681 | |
492 return result; | 682 return result; |
493 } | 683 } |
494 | 684 |
495 | 685 |
496 int64_t PostgreSQLIndex::GetLastChangeIndex(DatabaseManager& manager) | 686 int64_t PostgreSQLIndex::GetLastChangeIndex(DatabaseManager& manager) |
511 { | 701 { |
512 // This behavior is implemented in "CreateInstance()", and no | 702 // This behavior is implemented in "CreateInstance()", and no |
513 // backward compatibility is necessary | 703 // backward compatibility is necessary |
514 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); | 704 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); |
515 } | 705 } |
706 | |
707 | |
708 // #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) | |
709 // bool PostgreSQLIndex::HasFindSupport() const | |
710 // { | |
711 // // TODO-FIND | |
712 // return false; | |
713 // } | |
714 // #endif | |
715 | |
716 | |
717 // #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) | |
718 // void PostgreSQLIndex::ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response, | |
719 // DatabaseManager& manager, | |
720 // const Orthanc::DatabasePluginMessages::Find_Request& request) | |
721 // { | |
722 // // TODO-FIND | |
723 // throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
724 // } | |
725 // #endif | |
516 } | 726 } |