comparison MySQL/Plugins/MySQLIndex.cpp @ 144:740d9829f52e

handling of errors if MySQL user cannot CREATE TRIGGER
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 12 Mar 2020 12:10:52 +0100
parents 4cd7e45b671e
children 063aa53b5917
comparison
equal deleted inserted replaced
142:bbd57f5672fa 144:740d9829f52e
33 33
34 #include <ctype.h> 34 #include <ctype.h>
35 35
36 namespace OrthancDatabases 36 namespace OrthancDatabases
37 { 37 {
38 static void ThrowCannotCreateTrigger()
39 {
40 LOG(ERROR) << "The MySQL user is not allowed to create triggers => 2 possible solutions:";
41 LOG(ERROR) << " 1- Give the SUPER privilege to the MySQL database user, or";
42 LOG(ERROR) << " 2- Run \"set global log_bin_trust_function_creators=1;\" as MySQL root user.";
43 LOG(ERROR) << "Once you are done, drop and recreate the MySQL database";
44 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database,
45 "Need to fix the MySQL permissions for \"CREATE TRIGGER\"");
46 }
47
38 IDatabase* MySQLIndex::OpenInternal() 48 IDatabase* MySQLIndex::OpenInternal()
39 { 49 {
40 uint32_t expectedVersion = 6; 50 uint32_t expectedVersion = 6;
41 if (context_) 51 if (context_)
42 { 52 {
72 db->Open(); 82 db->Open();
73 db->Execute("SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE", false); 83 db->Execute("SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE", false);
74 84
75 { 85 {
76 MySQLDatabase::TransientAdvisoryLock lock(*db, MYSQL_LOCK_DATABASE_SETUP); 86 MySQLDatabase::TransientAdvisoryLock lock(*db, MYSQL_LOCK_DATABASE_SETUP);
77 MySQLTransaction t(*db); 87
78 88 /**
79 db->Execute("ALTER DATABASE " + parameters_.GetDatabase() + 89 * In a first transaction, we create the tables. Such a
80 " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", false); 90 * transaction cannot be rollback: "The CREATE TABLE statement
81 91 * in InnoDB is processed as a single transaction. This means
82 if (!db->DoesTableExist(t, "Resources")) 92 * that a ROLLBACK from the user does not undo CREATE TABLE
83 { 93 * statements the user made during that transaction."
84 std::string query; 94 * https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html
85 95 *
86 Orthanc::EmbeddedResources::GetFileResource 96 * As a consequence, we delay the initial population of the
87 (query, Orthanc::EmbeddedResources::MYSQL_PREPARE_INDEX); 97 * tables in a sequence of transactions below. This solves the
88 db->Execute(query, true); 98 * error message "MySQL plugin is incompatible with database
89 99 * schema version: 0" that was reported in the forum:
90 SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabaseSchemaVersion, expectedVersion); 100 * https://groups.google.com/d/msg/orthanc-users/OCFFkm1qm0k/Mbroy8VWAQAJ
91 SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, 1); 101 **/
92 } 102 {
93 103 MySQLTransaction t(*db);
94 if (!db->DoesTableExist(t, "Resources")) 104
95 { 105 db->Execute("ALTER DATABASE " + parameters_.GetDatabase() +
96 LOG(ERROR) << "Corrupted MySQL database"; 106 " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", false);
97 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 107
98 } 108 // This is the first table to be created
109 if (!db->DoesTableExist(t, "GlobalProperties"))
110 {
111 std::string query;
112
113 Orthanc::EmbeddedResources::GetFileResource
114 (query, Orthanc::EmbeddedResources::MYSQL_PREPARE_INDEX);
115 db->Execute(query, true);
116 }
117
118 t.Commit();
119 }
120
121 /**
122 * This is the sequence of transactions that initially populate
123 * the database. WARNING - As table creation cannot be rollback,
124 * don't forget to add "IF NOT EXISTS" if some table must be
125 * created below this point (in order to recover from failed
126 * transaction).
127 **/
99 128
100 int version = 0; 129 int version = 0;
101 if (!LookupGlobalIntegerProperty(version, *db, t, Orthanc::GlobalProperty_DatabaseSchemaVersion) || 130
102 version != 6) 131 {
103 { 132 MySQLTransaction t(*db);
104 LOG(ERROR) << "MySQL plugin is incompatible with database schema version: " << version; 133
105 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); 134 // This is the last table to be created
106 } 135 if (!db->DoesTableExist(t, "PatientRecyclingOrder"))
107 136 {
108 int revision; 137 LOG(ERROR) << "Corrupted MySQL database";
109 if (!LookupGlobalIntegerProperty(revision, *db, t, Orthanc::GlobalProperty_DatabasePatchLevel)) 138 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
110 { 139 }
111 revision = 1; 140
112 SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, revision); 141 // This is the last item to be created
142 if (!db->DoesTriggerExist(t, "PatientAdded"))
143 {
144 ThrowCannotCreateTrigger();
145 }
146
147 if (!LookupGlobalIntegerProperty(version, *db, t, Orthanc::GlobalProperty_DatabaseSchemaVersion))
148 {
149 SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabaseSchemaVersion, expectedVersion);
150 SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, 1);
151 version = expectedVersion;
152 }
153
154 if (version != 6)
155 {
156 LOG(ERROR) << "MySQL plugin is incompatible with database schema version: " << version;
157 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
158 }
159
160 t.Commit();
161 }
162
163 int revision = 0;
164
165 {
166 MySQLTransaction t(*db);
167
168 if (!LookupGlobalIntegerProperty(revision, *db, t, Orthanc::GlobalProperty_DatabasePatchLevel))
169 {
170 revision = 1;
171 SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, revision);
172 }
173
174 t.Commit();
113 } 175 }
114 176
115 if (revision == 1) 177 if (revision == 1)
116 { 178 {
179 MySQLTransaction t(*db);
180
117 // The serialization of jobs as a global property can lead to 181 // The serialization of jobs as a global property can lead to
118 // very long values => switch to the LONGTEXT type that can 182 // very long values => switch to the LONGTEXT type that can
119 // store up to 4GB: 183 // store up to 4GB:
120 // https://stackoverflow.com/a/13932834/881731 184 // https://stackoverflow.com/a/13932834/881731
121 db->Execute("ALTER TABLE GlobalProperties MODIFY value LONGTEXT", false); 185 db->Execute("ALTER TABLE GlobalProperties MODIFY value LONGTEXT", false);
122 186
123 revision = 2; 187 revision = 2;
124 SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, revision); 188 SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, revision);
189
190 t.Commit();
125 } 191 }
126 192
127 if (revision == 2) 193 if (revision == 2)
128 { 194 {
195 MySQLTransaction t(*db);
196
129 // Install the "GetLastChangeIndex" extension 197 // Install the "GetLastChangeIndex" extension
130 std::string query; 198 std::string query;
131 199
132 Orthanc::EmbeddedResources::GetFileResource 200 Orthanc::EmbeddedResources::GetFileResource
133 (query, Orthanc::EmbeddedResources::MYSQL_GET_LAST_CHANGE_INDEX); 201 (query, Orthanc::EmbeddedResources::MYSQL_GET_LAST_CHANGE_INDEX);
134 db->Execute(query, true); 202 db->Execute(query, true);
203
204 if (!db->DoesTriggerExist(t, "ChangeAdded"))
205 {
206 ThrowCannotCreateTrigger();
207 }
135 208
136 revision = 3; 209 revision = 3;
137 SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, revision); 210 SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, revision);
138 } 211
139 212 t.Commit();
213 }
214
140 if (revision == 3) 215 if (revision == 3)
141 { 216 {
217 MySQLTransaction t(*db);
218
142 // Reconfiguration of "Metadata" from TEXT type (up to 64KB) 219 // Reconfiguration of "Metadata" from TEXT type (up to 64KB)
143 // to the LONGTEXT type (up to 4GB). This might be important 220 // to the LONGTEXT type (up to 4GB). This might be important
144 // for applications such as the Osimis Web viewer that stores 221 // for applications such as the Osimis Web viewer that stores
145 // large amount of metadata. 222 // large amount of metadata.
146 // http://book.orthanc-server.com/faq/features.html#central-registry-of-metadata-and-attachments 223 // http://book.orthanc-server.com/faq/features.html#central-registry-of-metadata-and-attachments
147 db->Execute("ALTER TABLE Metadata MODIFY value LONGTEXT", false); 224 db->Execute("ALTER TABLE Metadata MODIFY value LONGTEXT", false);
148 225
149 revision = 4; 226 revision = 4;
150 SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, revision); 227 SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, revision);
151 } 228
152 229 t.Commit();
230 }
231
153 if (revision == 4) 232 if (revision == 4)
154 { 233 {
234 MySQLTransaction t(*db);
235
155 // Install the "CreateInstance" extension 236 // Install the "CreateInstance" extension
156 std::string query; 237 std::string query;
157 238
158 Orthanc::EmbeddedResources::GetFileResource 239 Orthanc::EmbeddedResources::GetFileResource
159 (query, Orthanc::EmbeddedResources::MYSQL_CREATE_INSTANCE); 240 (query, Orthanc::EmbeddedResources::MYSQL_CREATE_INSTANCE);
160 db->Execute(query, true); 241 db->Execute(query, true);
161 242
162 revision = 5; 243 revision = 5;
163 SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, revision); 244 SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, revision);
164 } 245
165 246 t.Commit();
247 }
248
166 if (revision != 5) 249 if (revision != 5)
167 { 250 {
168 LOG(ERROR) << "MySQL plugin is incompatible with database schema revision: " << revision; 251 LOG(ERROR) << "MySQL plugin is incompatible with database schema revision: " << revision;
169 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); 252 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
170 } 253 }
171
172 t.Commit();
173 } 254 }
174 255
175 /** 256 /**
176 * WARNING: This lock must be acquired after 257 * WARNING: This lock must be acquired after
177 * "MYSQL_LOCK_DATABASE_SETUP" is released. Indeed, in MySQL < 258 * "MYSQL_LOCK_DATABASE_SETUP" is released. Indeed, in MySQL <