Mercurial > hg > orthanc-databases
comparison Framework/Odbc/OdbcDatabase.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 | 79e21c33962d |
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 "OdbcDatabase.h" | |
23 | |
24 #include "../Common/ImplicitTransaction.h" | |
25 #include "../Common/RetryDatabaseFactory.h" | |
26 #include "../Common/Utf8StringValue.h" | |
27 #include "OdbcPreparedStatement.h" | |
28 #include "OdbcResult.h" | |
29 | |
30 #include <Logging.h> | |
31 #include <OrthancException.h> | |
32 #include <Toolbox.h> | |
33 | |
34 #include <boost/algorithm/string/predicate.hpp> | |
35 #include <sqlext.h> | |
36 | |
37 | |
38 namespace OrthancDatabases | |
39 { | |
40 static void SetAutoCommitTransaction(SQLHDBC handle, | |
41 bool autocommit) | |
42 { | |
43 // Go to autocommit mode | |
44 SQLPOINTER value = (SQLPOINTER) (autocommit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF); | |
45 | |
46 if (!SQL_SUCCEEDED(SQLSetConnectAttr(handle, SQL_ATTR_AUTOCOMMIT, value, SQL_IS_UINTEGER))) | |
47 { | |
48 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, | |
49 "Cannot switch the autocommit mode"); | |
50 } | |
51 } | |
52 | |
53 | |
54 class OdbcDatabase::OdbcImplicitTransaction : public ImplicitTransaction | |
55 { | |
56 private: | |
57 OdbcDatabase& db_; | |
58 | |
59 protected: | |
60 virtual IResult* ExecuteInternal(IPrecompiledStatement& statement, | |
61 const Dictionary& parameters) ORTHANC_OVERRIDE | |
62 { | |
63 return dynamic_cast<OdbcPreparedStatement&>(statement).Execute(parameters); | |
64 } | |
65 | |
66 virtual void ExecuteWithoutResultInternal(IPrecompiledStatement& statement, | |
67 const Dictionary& parameters) ORTHANC_OVERRIDE | |
68 { | |
69 std::unique_ptr<IResult> result(Execute(statement, parameters)); | |
70 } | |
71 | |
72 public: | |
73 OdbcImplicitTransaction(OdbcDatabase& db) : | |
74 db_(db) | |
75 { | |
76 SetAutoCommitTransaction(db_.GetHandle(), true); | |
77 } | |
78 | |
79 virtual bool DoesTableExist(const std::string& name) ORTHANC_OVERRIDE | |
80 { | |
81 return db_.DoesTableExist(name.c_str()); | |
82 } | |
83 | |
84 virtual bool DoesTriggerExist(const std::string& name) ORTHANC_OVERRIDE | |
85 { | |
86 return false; | |
87 } | |
88 | |
89 virtual void ExecuteMultiLines(const std::string& query) ORTHANC_OVERRIDE | |
90 { | |
91 db_.ExecuteMultiLines(query); | |
92 } | |
93 }; | |
94 | |
95 | |
96 class OdbcDatabase::OdbcExplicitTransaction : public ITransaction | |
97 { | |
98 private: | |
99 OdbcDatabase& db_; | |
100 bool isOpen_; | |
101 | |
102 void EndTransaction(SQLSMALLINT completionType) | |
103 { | |
104 if (!isOpen_) | |
105 { | |
106 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, "Transaction is already finalized"); | |
107 } | |
108 else if (SQL_SUCCEEDED(SQLEndTran(SQL_HANDLE_DBC, db_.GetHandle(), completionType))) | |
109 { | |
110 isOpen_ = false; | |
111 } | |
112 else | |
113 { | |
114 SQLCHAR stateBuf[SQL_SQLSTATE_SIZE + 1]; | |
115 SQLSMALLINT stateLength = 0; | |
116 | |
117 const SQLSMALLINT recNum = 1; | |
118 | |
119 if (SQL_SUCCEEDED(SQLGetDiagField(SQL_HANDLE_DBC, db_.GetHandle(), | |
120 recNum, SQL_DIAG_SQLSTATE, &stateBuf, sizeof(stateBuf), &stateLength))) | |
121 { | |
122 const std::string state(reinterpret_cast<const char*>(stateBuf)); | |
123 | |
124 if (state == "40001") | |
125 { | |
126 throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseCannotSerialize); | |
127 } | |
128 } | |
129 | |
130 switch (completionType) | |
131 { | |
132 case SQL_COMMIT: | |
133 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Cannot commit transaction"); | |
134 | |
135 case SQL_ROLLBACK: | |
136 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Cannot rollback transaction"); | |
137 | |
138 default: | |
139 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
140 } | |
141 } | |
142 } | |
143 | |
144 public: | |
145 OdbcExplicitTransaction(OdbcDatabase& db) : | |
146 db_(db), | |
147 isOpen_(true) | |
148 { | |
149 SetAutoCommitTransaction(db_.GetHandle(), false); | |
150 } | |
151 | |
152 virtual ~OdbcExplicitTransaction() | |
153 { | |
154 if (isOpen_) | |
155 { | |
156 LOG(INFO) << "An active ODBC transaction was dismissed"; | |
157 if (!SQL_SUCCEEDED(SQLEndTran(SQL_HANDLE_DBC, db_.GetHandle(), SQL_ROLLBACK))) | |
158 { | |
159 LOG(ERROR) << "Cannot rollback transaction"; | |
160 } | |
161 } | |
162 } | |
163 | |
164 virtual bool IsImplicit() const ORTHANC_OVERRIDE | |
165 { | |
166 return false; | |
167 } | |
168 | |
169 virtual void Commit() ORTHANC_OVERRIDE | |
170 { | |
171 EndTransaction(SQL_COMMIT); | |
172 } | |
173 | |
174 virtual void Rollback() ORTHANC_OVERRIDE | |
175 { | |
176 EndTransaction(SQL_ROLLBACK); | |
177 } | |
178 | |
179 virtual bool DoesTableExist(const std::string& name) ORTHANC_OVERRIDE | |
180 { | |
181 return db_.DoesTableExist(name.c_str()); | |
182 } | |
183 | |
184 virtual bool DoesTriggerExist(const std::string& name) ORTHANC_OVERRIDE | |
185 { | |
186 return false; | |
187 } | |
188 | |
189 virtual void ExecuteMultiLines(const std::string& query) ORTHANC_OVERRIDE | |
190 { | |
191 db_.ExecuteMultiLines(query); | |
192 } | |
193 | |
194 virtual IResult* Execute(IPrecompiledStatement& statement, | |
195 const Dictionary& parameters) ORTHANC_OVERRIDE | |
196 { | |
197 return dynamic_cast<OdbcPreparedStatement&>(statement).Execute(parameters); | |
198 } | |
199 | |
200 virtual void ExecuteWithoutResult(IPrecompiledStatement& statement, | |
201 const Dictionary& parameters) ORTHANC_OVERRIDE | |
202 { | |
203 std::unique_ptr<IResult> result(Execute(statement, parameters)); | |
204 } | |
205 }; | |
206 | |
207 | |
208 static bool ParseThreePartsVersion(unsigned int& majorVersion, | |
209 const std::string& version) | |
210 { | |
211 std::vector<std::string> tokens; | |
212 Orthanc::Toolbox::TokenizeString(tokens, version, '.'); | |
213 | |
214 try | |
215 { | |
216 if (tokens.size() == 3u) | |
217 { | |
218 int tmp = boost::lexical_cast<int>(tokens[0]); | |
219 if (tmp >= 0) | |
220 { | |
221 majorVersion = static_cast<unsigned int>(tmp); | |
222 return true; | |
223 } | |
224 } | |
225 } | |
226 catch (boost::bad_lexical_cast&) | |
227 { | |
228 } | |
229 | |
230 return false; | |
231 } | |
232 | |
233 | |
234 OdbcDatabase::OdbcDatabase(OdbcEnvironment& environment, | |
235 const std::string& connectionString) : | |
236 dbmsMajorVersion_(0) | |
237 { | |
238 LOG(INFO) << "Creating an ODBC connection: " << connectionString; | |
239 | |
240 /* Allocate a connection handle */ | |
241 if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_DBC, environment.GetHandle(), &handle_))) | |
242 { | |
243 throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseUnavailable, | |
244 "Cannot create ODBC connection"); | |
245 } | |
246 | |
247 /* Connect to the DSN mydsn */ | |
248 SQLCHAR* tmp = const_cast<SQLCHAR*>(reinterpret_cast<const SQLCHAR*>(connectionString.c_str())); | |
249 SQLCHAR outBuffer[2048]; | |
250 SQLSMALLINT outSize = 0; | |
251 | |
252 bool success = true; | |
253 | |
254 if (SQL_SUCCEEDED(SQLDriverConnect(handle_, NULL, tmp, SQL_NTS /* null-terminated string */, | |
255 outBuffer, sizeof(outBuffer), &outSize, SQL_DRIVER_COMPLETE))) | |
256 { | |
257 LOG(INFO) << "Returned connection string: " << outBuffer; | |
258 } | |
259 else | |
260 { | |
261 success = false; | |
262 } | |
263 | |
264 if (!SQL_SUCCEEDED(SQLSetConnectAttr(handle_, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER) SQL_TXN_SERIALIZABLE, SQL_NTS))) | |
265 { | |
266 /** | |
267 * Switch to the "serializable" isolation level that is expected | |
268 * by Orthanc. This is already the default for MySQL and MSSQL, | |
269 * but is needed for PostgreSQL. | |
270 * https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/transaction-isolation-levels | |
271 **/ | |
272 success = false; | |
273 } | |
274 | |
275 SQLCHAR versionBuffer[2048]; | |
276 SQLSMALLINT versionSize; | |
277 | |
278 if (success && | |
279 SQL_SUCCEEDED(SQLGetInfo(handle_, SQL_DBMS_NAME, outBuffer, sizeof(outBuffer) - 1, &outSize)) && | |
280 SQL_SUCCEEDED(SQLGetInfo(handle_, SQL_DBMS_VER, versionBuffer, sizeof(versionBuffer) - 1, &versionSize))) | |
281 { | |
282 std::string dbms(reinterpret_cast<const char*>(outBuffer), outSize); | |
283 std::string version(reinterpret_cast<const char*>(versionBuffer), versionSize); | |
284 | |
285 LOG(WARNING) << "DBMS Name: " << dbms; | |
286 LOG(WARNING) << "DBMS Version: " << version; | |
287 | |
288 if (dbms == "PostgreSQL") | |
289 { | |
290 dialect_ = Dialect_PostgreSQL; | |
291 } | |
292 else if (dbms == "SQLite") | |
293 { | |
294 dialect_ = Dialect_SQLite; | |
295 ExecuteMultiLines("PRAGMA FOREIGN_KEYS=ON"); // Necessary for cascaded delete to work | |
296 ExecuteMultiLines("PRAGMA ENCODING=\"UTF-8\""); | |
297 | |
298 // The following lines speed up SQLite | |
299 | |
300 /*ExecuteMultiLines("PRAGMA SYNCHRONOUS=NORMAL;"); | |
301 ExecuteMultiLines("PRAGMA JOURNAL_MODE=WAL;"); | |
302 ExecuteMultiLines("PRAGMA LOCKING_MODE=EXCLUSIVE;"); | |
303 ExecuteMultiLines("PRAGMA WAL_AUTOCHECKPOINT=1000;");*/ | |
304 } | |
305 else if (dbms == "MySQL") | |
306 { | |
307 dialect_ = Dialect_MySQL; | |
308 | |
309 if (!ParseThreePartsVersion(dbmsMajorVersion_, version)) | |
310 { | |
311 SQLFreeHandle(SQL_HANDLE_DBC, handle_); | |
312 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Cannot parse the version of MySQL: " + version); | |
313 } | |
314 } | |
315 else if (dbms == "Microsoft SQL Server") | |
316 { | |
317 dialect_ = Dialect_MSSQL; | |
318 | |
319 if (!ParseThreePartsVersion(dbmsMajorVersion_, version)) | |
320 { | |
321 SQLFreeHandle(SQL_HANDLE_DBC, handle_); | |
322 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Cannot parse the version of SQL Server: " + version); | |
323 } | |
324 } | |
325 else | |
326 { | |
327 SQLFreeHandle(SQL_HANDLE_DBC, handle_); | |
328 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Unknown SQL dialect for DBMS: " + dbms); | |
329 } | |
330 } | |
331 else | |
332 { | |
333 success = false; | |
334 } | |
335 | |
336 if (!success) | |
337 { | |
338 std::string error = FormatError(); | |
339 SQLFreeHandle(SQL_HANDLE_DBC, handle_); // Cannot call FormatError() below this point | |
340 | |
341 throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseUnavailable, "Error in SQLDriverConnect():\n" + error); | |
342 } | |
343 } | |
344 | |
345 | |
346 OdbcDatabase::~OdbcDatabase() | |
347 { | |
348 LOG(INFO) << "Destructing an ODBC connection"; | |
349 | |
350 if (!SQL_SUCCEEDED(SQLDisconnect(handle_))) | |
351 { | |
352 LOG(ERROR) << "Cannot disconnect from driver"; | |
353 } | |
354 | |
355 if (!SQL_SUCCEEDED(SQLFreeHandle(SQL_HANDLE_DBC, handle_))) | |
356 { | |
357 LOG(ERROR) << "Cannot destruct the ODBC connection"; | |
358 } | |
359 } | |
360 | |
361 | |
362 std::string OdbcDatabase::FormatError() | |
363 { | |
364 return OdbcEnvironment::FormatError(handle_, SQL_HANDLE_DBC); | |
365 } | |
366 | |
367 | |
368 void OdbcDatabase::ListTables(std::set<std::string>& target) | |
369 { | |
370 target.clear(); | |
371 | |
372 OdbcStatement statement(GetHandle()); | |
373 | |
374 if (SQL_SUCCEEDED(SQLTables(statement.GetHandle(), NULL, 0, NULL, 0, NULL, 0, | |
375 const_cast<SQLCHAR*>(reinterpret_cast<const SQLCHAR*>("'TABLE'")), SQL_NTS))) | |
376 { | |
377 OdbcResult result(statement, dialect_); | |
378 | |
379 while (!result.IsDone()) | |
380 { | |
381 if (result.GetFieldsCount() < 5) | |
382 { | |
383 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Invalid result for SQLTables()"); | |
384 } | |
385 else | |
386 { | |
387 if (result.GetField(2).GetType() == ValueType_Utf8String && | |
388 result.GetField(3).GetType() == ValueType_Utf8String && | |
389 dynamic_cast<const Utf8StringValue&>(result.GetField(3)).GetContent() == "TABLE") | |
390 { | |
391 std::string name = dynamic_cast<const Utf8StringValue&>(result.GetField(2)).GetContent(); | |
392 Orthanc::Toolbox::ToLowerCase(name); | |
393 target.insert(name); | |
394 } | |
395 } | |
396 | |
397 result.Next(); | |
398 } | |
399 } | |
400 } | |
401 | |
402 | |
403 bool OdbcDatabase::DoesTableExist(const std::string& name) | |
404 { | |
405 std::set<std::string> tables; | |
406 ListTables(tables); | |
407 return (tables.find(name) != tables.end()); | |
408 } | |
409 | |
410 | |
411 void OdbcDatabase::ExecuteMultiLines(const std::string& query) | |
412 { | |
413 OdbcStatement statement(GetHandle()); | |
414 | |
415 std::vector<std::string> lines; | |
416 Orthanc::Toolbox::TokenizeString(lines, query, ';'); | |
417 | |
418 for (size_t i = 0; i < lines.size(); i++) | |
419 { | |
420 std::string line = Orthanc::Toolbox::StripSpaces(lines[i]); | |
421 if (!line.empty()) | |
422 { | |
423 LOG(INFO) << "Running ODBC SQL: " << line; | |
424 SQLCHAR* tmp = const_cast<SQLCHAR*>(reinterpret_cast<const SQLCHAR*>(line.c_str())); | |
425 | |
426 SQLRETURN code = SQLExecDirect(statement.GetHandle(), tmp, SQL_NTS); | |
427 | |
428 if (code != SQL_NO_DATA && | |
429 code != SQL_SUCCESS && | |
430 code != SQL_SUCCESS_WITH_INFO) | |
431 { | |
432 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, | |
433 "Cannot execute multi-line SQL:\n" + statement.FormatError()); | |
434 } | |
435 } | |
436 } | |
437 } | |
438 | |
439 | |
440 IPrecompiledStatement* OdbcDatabase::Compile(const Query& query) | |
441 { | |
442 return new OdbcPreparedStatement(GetHandle(), GetDialect(), query); | |
443 } | |
444 | |
445 | |
446 ITransaction* OdbcDatabase::CreateTransaction(TransactionType type) | |
447 { | |
448 /** | |
449 * In ODBC, there is no "START TRANSACTION". A transaction is | |
450 * automatically created with each connection, and the "READ | |
451 * ONLY" status can only be set at the statement level | |
452 * (cf. SQL_CONCUR_READ_ONLY). One can only control the | |
453 * autocommit: https://stackoverflow.com/a/35351267/881731 | |
454 **/ | |
455 switch (type) | |
456 { | |
457 case TransactionType_Implicit: | |
458 return new OdbcImplicitTransaction(*this); | |
459 | |
460 case TransactionType_ReadWrite: | |
461 case TransactionType_ReadOnly: | |
462 return new OdbcExplicitTransaction(*this); | |
463 | |
464 default: | |
465 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
466 } | |
467 } | |
468 | |
469 | |
470 unsigned int OdbcDatabase::GetDbmsMajorVersion() const | |
471 { | |
472 return dbmsMajorVersion_; | |
473 } | |
474 | |
475 | |
476 IDatabaseFactory* OdbcDatabase::CreateDatabaseFactory(unsigned int maxConnectionRetries, | |
477 unsigned int connectionRetryInterval, | |
478 const std::string& connectionString, | |
479 bool checkEncodings) | |
480 { | |
481 class Factory : public RetryDatabaseFactory | |
482 { | |
483 private: | |
484 OdbcEnvironment environment_; | |
485 std::string connectionString_; | |
486 bool checkEncodings_; | |
487 | |
488 bool LookupConnectionOption(std::string& value, | |
489 const std::string& option) const | |
490 { | |
491 std::vector<std::string> tokens; | |
492 Orthanc::Toolbox::TokenizeString(tokens, connectionString_, ';'); | |
493 | |
494 for (size_t i = 0; i < tokens.size(); i++) | |
495 { | |
496 if (boost::starts_with(tokens[i], option + "=")) | |
497 { | |
498 value = tokens[i]; | |
499 return true; | |
500 } | |
501 } | |
502 | |
503 return false; | |
504 } | |
505 | |
506 | |
507 void CheckMSSQLEncodings(OdbcDatabase& db) | |
508 { | |
509 // https://en.wikipedia.org/wiki/History_of_Microsoft_SQL_Server | |
510 if (db.GetDbmsMajorVersion() <= 14) | |
511 { | |
512 // Microsoft SQL Server up to 2017 | |
513 | |
514 std::string value; | |
515 if (LookupConnectionOption(value, "AutoTranslate")) | |
516 { | |
517 if (value != "AutoTranslate=no") | |
518 { | |
519 LOG(WARNING) << "For UTF-8 to work properly, it is strongly advised to set \"AutoTranslate=no\" in the " | |
520 << "ODBC connection string when connecting to Microsoft SQL Server with version <= 2017"; | |
521 } | |
522 } | |
523 else | |
524 { | |
525 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, | |
526 "Your Microsoft SQL Server has version <= 2017, and thus doesn't support UTF-8; " | |
527 "Please upgrade or add \"AutoTranslate=no\" to your ODBC connection string"); | |
528 } | |
529 } | |
530 else | |
531 { | |
532 std::string value; | |
533 if (LookupConnectionOption(value, "AutoTranslate") && | |
534 value != "AutoTranslate=yes") | |
535 { | |
536 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, | |
537 "Your Microsoft SQL Server has version >= 2019, and thus fully supports UTF-8; " | |
538 "Please set \"AutoTranslate=yes\" in your ODBC connection string"); | |
539 } | |
540 } | |
541 } | |
542 | |
543 | |
544 void CheckMySQLEncodings(OdbcDatabase& db) | |
545 { | |
546 // https://dev.mysql.com/doc/connector-odbc/en/connector-odbc-configuration-connection-parameters.html | |
547 | |
548 std::string value; | |
549 if (LookupConnectionOption(value, "charset")) | |
550 { | |
551 if (value != "charset=utf8") | |
552 { | |
553 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, | |
554 "For compatibility with UTF-8 in Orthanc, your connection string to MySQL " | |
555 "must *not* set the \"charset\" option to another value than \"utf8\""); | |
556 } | |
557 } | |
558 else if (db.GetDbmsMajorVersion() < 8) | |
559 { | |
560 // MySQL up to 5.7 | |
561 LOG(WARNING) << "It is advised to set the \"charset=utf8\" option in your connection string if using MySQL <= 5.7"; | |
562 } | |
563 else | |
564 { | |
565 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, | |
566 "For compatibility with UTF-8 in Orthanc, your connection string to MySQL >= 8.0 " | |
567 "*must* set the \"charset=utf8\" in your connection string"); | |
568 } | |
569 } | |
570 | |
571 | |
572 protected: | |
573 IDatabase* TryOpen() | |
574 { | |
575 std::unique_ptr<OdbcDatabase> db(new OdbcDatabase(environment_, connectionString_)); | |
576 | |
577 if (checkEncodings_) | |
578 { | |
579 switch (db->GetDialect()) | |
580 { | |
581 case Dialect_MSSQL: | |
582 CheckMSSQLEncodings(*db); | |
583 break; | |
584 | |
585 case Dialect_MySQL: | |
586 CheckMySQLEncodings(*db); | |
587 break; | |
588 | |
589 case Dialect_SQLite: | |
590 case Dialect_PostgreSQL: | |
591 // Nothing specific to be checked wrt. encodings | |
592 break; | |
593 | |
594 default: | |
595 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
596 } | |
597 } | |
598 | |
599 if (db->GetDbmsMajorVersion() >= 15) | |
600 { | |
601 /** | |
602 * SQL Server 2019 introduces support for UTF-8. Note that | |
603 * "ALTER" cannot be run inside a transaction, and must be | |
604 * done *before* the creation of the tables. | |
605 * https://docs.microsoft.com/en-US/sql/relational-databases/collations/collation-and-unicode-support#utf8 | |
606 * | |
607 * Furthermore, this call must be done by both | |
608 * "odbc-index" and "odbc-storage" plugins, because | |
609 * altering collation is an operation that requires | |
610 * exclusive lock: If "odbc-storage" is the first plugin | |
611 * to be loaded and doesn't set the UTF-8 collation, | |
612 * "odbc-index" cannot start because it doesn't have | |
613 * exclusive access. | |
614 **/ | |
615 db->ExecuteMultiLines("ALTER DATABASE CURRENT COLLATE LATIN1_GENERAL_100_CI_AS_SC_UTF8"); | |
616 } | |
617 | |
618 return db.release(); | |
619 } | |
620 | |
621 public: | |
622 Factory(unsigned int maxConnectionRetries, | |
623 unsigned int connectionRetryInterval, | |
624 const std::string& connectionString, | |
625 bool checkEncodings) : | |
626 RetryDatabaseFactory(maxConnectionRetries, connectionRetryInterval), | |
627 connectionString_(connectionString), | |
628 checkEncodings_(checkEncodings) | |
629 { | |
630 } | |
631 }; | |
632 | |
633 return new Factory(maxConnectionRetries, connectionRetryInterval, connectionString, checkEncodings); | |
634 } | |
635 } |