Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/config/schema/PhabricatorConfigSchemaQuery.php
12256 views
1
<?php
2
3
final class PhabricatorConfigSchemaQuery extends Phobject {
4
5
private $refs;
6
private $apis;
7
8
public function setRefs(array $refs) {
9
$this->refs = $refs;
10
return $this;
11
}
12
13
public function getRefs() {
14
if (!$this->refs) {
15
return PhabricatorDatabaseRef::getMasterDatabaseRefs();
16
}
17
return $this->refs;
18
}
19
20
public function setAPIs(array $apis) {
21
$map = array();
22
foreach ($apis as $api) {
23
$map[$api->getRef()->getRefKey()] = $api;
24
}
25
$this->apis = $map;
26
return $this;
27
}
28
29
private function getDatabaseNames(PhabricatorDatabaseRef $ref) {
30
$api = $this->getAPI($ref);
31
$patches = PhabricatorSQLPatchList::buildAllPatches();
32
return $api->getDatabaseList(
33
$patches,
34
$only_living = true);
35
}
36
37
private function getAPI(PhabricatorDatabaseRef $ref) {
38
$key = $ref->getRefKey();
39
40
if (isset($this->apis[$key])) {
41
return $this->apis[$key];
42
}
43
44
return id(new PhabricatorStorageManagementAPI())
45
->setUser($ref->getUser())
46
->setHost($ref->getHost())
47
->setPort($ref->getPort())
48
->setNamespace(PhabricatorLiskDAO::getDefaultStorageNamespace())
49
->setPassword($ref->getPass());
50
}
51
52
public function loadActualSchemata() {
53
$refs = $this->getRefs();
54
55
$schemata = array();
56
foreach ($refs as $ref) {
57
$schema = $this->loadActualSchemaForServer($ref);
58
$schemata[$schema->getRef()->getRefKey()] = $schema;
59
}
60
61
return $schemata;
62
}
63
64
private function loadActualSchemaForServer(PhabricatorDatabaseRef $ref) {
65
$databases = $this->getDatabaseNames($ref);
66
67
$conn = $ref->newManagementConnection();
68
69
$tables = queryfx_all(
70
$conn,
71
'SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_COLLATION, ENGINE
72
FROM INFORMATION_SCHEMA.TABLES
73
WHERE TABLE_SCHEMA IN (%Ls)',
74
$databases);
75
76
$database_info = queryfx_all(
77
$conn,
78
'SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
79
FROM INFORMATION_SCHEMA.SCHEMATA
80
WHERE SCHEMA_NAME IN (%Ls)',
81
$databases);
82
$database_info = ipull($database_info, null, 'SCHEMA_NAME');
83
84
// Find databases which exist, but which the user does not have permission
85
// to see.
86
$invisible_databases = array();
87
foreach ($databases as $database_name) {
88
if (isset($database_info[$database_name])) {
89
continue;
90
}
91
92
try {
93
queryfx($conn, 'SHOW TABLES IN %T', $database_name);
94
} catch (AphrontAccessDeniedQueryException $ex) {
95
// This database exists, the user just doesn't have permission to
96
// see it.
97
$invisible_databases[] = $database_name;
98
} catch (AphrontSchemaQueryException $ex) {
99
// This database is legitimately missing.
100
}
101
}
102
103
$sql = array();
104
foreach ($tables as $table) {
105
$sql[] = qsprintf(
106
$conn,
107
'(TABLE_SCHEMA = %s AND TABLE_NAME = %s)',
108
$table['TABLE_SCHEMA'],
109
$table['TABLE_NAME']);
110
}
111
112
if ($sql) {
113
$column_info = queryfx_all(
114
$conn,
115
'SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME,
116
COLLATION_NAME, COLUMN_TYPE, IS_NULLABLE, EXTRA
117
FROM INFORMATION_SCHEMA.COLUMNS
118
WHERE %LO',
119
$sql);
120
$column_info = igroup($column_info, 'TABLE_SCHEMA');
121
} else {
122
$column_info = array();
123
}
124
125
// NOTE: Tables like KEY_COLUMN_USAGE and TABLE_CONSTRAINTS only contain
126
// primary, unique, and foreign keys, so we can't use them here. We pull
127
// indexes later on using SHOW INDEXES.
128
129
$server_schema = id(new PhabricatorConfigServerSchema())
130
->setRef($ref);
131
132
$tables = igroup($tables, 'TABLE_SCHEMA');
133
foreach ($tables as $database_name => $database_tables) {
134
$info = $database_info[$database_name];
135
136
$database_schema = id(new PhabricatorConfigDatabaseSchema())
137
->setName($database_name)
138
->setCharacterSet($info['DEFAULT_CHARACTER_SET_NAME'])
139
->setCollation($info['DEFAULT_COLLATION_NAME']);
140
141
$database_column_info = idx($column_info, $database_name, array());
142
$database_column_info = igroup($database_column_info, 'TABLE_NAME');
143
144
foreach ($database_tables as $table) {
145
$table_name = $table['TABLE_NAME'];
146
147
$table_schema = id(new PhabricatorConfigTableSchema())
148
->setName($table_name)
149
->setCollation($table['TABLE_COLLATION'])
150
->setEngine($table['ENGINE']);
151
152
$columns = idx($database_column_info, $table_name, array());
153
foreach ($columns as $column) {
154
if (strpos($column['EXTRA'], 'auto_increment') === false) {
155
$auto_increment = false;
156
} else {
157
$auto_increment = true;
158
}
159
160
$column_schema = id(new PhabricatorConfigColumnSchema())
161
->setName($column['COLUMN_NAME'])
162
->setCharacterSet($column['CHARACTER_SET_NAME'])
163
->setCollation($column['COLLATION_NAME'])
164
->setColumnType($column['COLUMN_TYPE'])
165
->setNullable($column['IS_NULLABLE'] == 'YES')
166
->setAutoIncrement($auto_increment);
167
168
$table_schema->addColumn($column_schema);
169
}
170
171
$key_parts = queryfx_all(
172
$conn,
173
'SHOW INDEXES FROM %T.%T',
174
$database_name,
175
$table_name);
176
$keys = igroup($key_parts, 'Key_name');
177
foreach ($keys as $key_name => $key_pieces) {
178
$key_pieces = isort($key_pieces, 'Seq_in_index');
179
$head = head($key_pieces);
180
181
// This handles string indexes which index only a prefix of a field.
182
$column_names = array();
183
foreach ($key_pieces as $piece) {
184
$name = $piece['Column_name'];
185
if ($piece['Sub_part']) {
186
$name = $name.'('.$piece['Sub_part'].')';
187
}
188
$column_names[] = $name;
189
}
190
191
$key_schema = id(new PhabricatorConfigKeySchema())
192
->setName($key_name)
193
->setColumnNames($column_names)
194
->setUnique(!$head['Non_unique'])
195
->setIndexType($head['Index_type']);
196
197
$table_schema->addKey($key_schema);
198
}
199
200
$database_schema->addTable($table_schema);
201
}
202
203
$server_schema->addDatabase($database_schema);
204
}
205
206
foreach ($invisible_databases as $database_name) {
207
$server_schema->addDatabase(
208
id(new PhabricatorConfigDatabaseSchema())
209
->setName($database_name)
210
->setAccessDenied(true));
211
}
212
213
return $server_schema;
214
}
215
216
public function loadExpectedSchemata() {
217
$refs = $this->getRefs();
218
219
$schemata = array();
220
foreach ($refs as $ref) {
221
$schema = $this->loadExpectedSchemaForServer($ref);
222
$schemata[$schema->getRef()->getRefKey()] = $schema;
223
}
224
225
return $schemata;
226
}
227
228
public function loadExpectedSchemaForServer(PhabricatorDatabaseRef $ref) {
229
$databases = $this->getDatabaseNames($ref);
230
$info = $this->getAPI($ref)->getCharsetInfo();
231
232
$specs = id(new PhutilClassMapQuery())
233
->setAncestorClass('PhabricatorConfigSchemaSpec')
234
->execute();
235
236
$server_schema = id(new PhabricatorConfigServerSchema())
237
->setRef($ref);
238
239
foreach ($specs as $spec) {
240
$spec
241
->setUTF8Charset(
242
$info[PhabricatorStorageManagementAPI::CHARSET_DEFAULT])
243
->setUTF8BinaryCollation(
244
$info[PhabricatorStorageManagementAPI::COLLATE_TEXT])
245
->setUTF8SortingCollation(
246
$info[PhabricatorStorageManagementAPI::COLLATE_SORT])
247
->setServer($server_schema)
248
->buildSchemata($server_schema);
249
}
250
251
return $server_schema;
252
}
253
254
public function buildComparisonSchemata(
255
array $expect_servers,
256
array $actual_servers) {
257
258
$schemata = array();
259
foreach ($actual_servers as $key => $actual_server) {
260
$schemata[$key] = $this->buildComparisonSchemaForServer(
261
$expect_servers[$key],
262
$actual_server);
263
}
264
265
return $schemata;
266
}
267
268
private function buildComparisonSchemaForServer(
269
PhabricatorConfigServerSchema $expect,
270
PhabricatorConfigServerSchema $actual) {
271
272
$comp_server = $actual->newEmptyClone();
273
274
$all_databases = $actual->getDatabases() + $expect->getDatabases();
275
foreach ($all_databases as $database_name => $database_template) {
276
$actual_database = $actual->getDatabase($database_name);
277
$expect_database = $expect->getDatabase($database_name);
278
279
$issues = $this->compareSchemata($expect_database, $actual_database);
280
281
$comp_database = $database_template->newEmptyClone()
282
->setIssues($issues);
283
284
if (!$actual_database) {
285
$actual_database = $expect_database->newEmptyClone();
286
}
287
288
if (!$expect_database) {
289
$expect_database = $actual_database->newEmptyClone();
290
}
291
292
$all_tables =
293
$actual_database->getTables() +
294
$expect_database->getTables();
295
foreach ($all_tables as $table_name => $table_template) {
296
$actual_table = $actual_database->getTable($table_name);
297
$expect_table = $expect_database->getTable($table_name);
298
299
$issues = $this->compareSchemata($expect_table, $actual_table);
300
301
$comp_table = $table_template->newEmptyClone()
302
->setIssues($issues);
303
304
if (!$actual_table) {
305
$actual_table = $expect_table->newEmptyClone();
306
}
307
if (!$expect_table) {
308
$expect_table = $actual_table->newEmptyClone();
309
}
310
311
$all_columns =
312
$actual_table->getColumns() +
313
$expect_table->getColumns();
314
foreach ($all_columns as $column_name => $column_template) {
315
$actual_column = $actual_table->getColumn($column_name);
316
$expect_column = $expect_table->getColumn($column_name);
317
318
$issues = $this->compareSchemata($expect_column, $actual_column);
319
320
$comp_column = $column_template->newEmptyClone()
321
->setIssues($issues);
322
323
$comp_table->addColumn($comp_column);
324
}
325
326
$all_keys =
327
$actual_table->getKeys() +
328
$expect_table->getKeys();
329
foreach ($all_keys as $key_name => $key_template) {
330
$actual_key = $actual_table->getKey($key_name);
331
$expect_key = $expect_table->getKey($key_name);
332
333
$issues = $this->compareSchemata($expect_key, $actual_key);
334
335
$comp_key = $key_template->newEmptyClone()
336
->setIssues($issues);
337
338
$comp_table->addKey($comp_key);
339
}
340
341
$comp_table->setPersistenceType($expect_table->getPersistenceType());
342
343
$comp_database->addTable($comp_table);
344
}
345
$comp_server->addDatabase($comp_database);
346
}
347
348
return $comp_server;
349
}
350
351
private function compareSchemata(
352
PhabricatorConfigStorageSchema $expect = null,
353
PhabricatorConfigStorageSchema $actual = null) {
354
355
$expect_is_key = ($expect instanceof PhabricatorConfigKeySchema);
356
$actual_is_key = ($actual instanceof PhabricatorConfigKeySchema);
357
358
if ($expect_is_key || $actual_is_key) {
359
$missing_issue = PhabricatorConfigStorageSchema::ISSUE_MISSINGKEY;
360
$surplus_issue = PhabricatorConfigStorageSchema::ISSUE_SURPLUSKEY;
361
} else {
362
$missing_issue = PhabricatorConfigStorageSchema::ISSUE_MISSING;
363
$surplus_issue = PhabricatorConfigStorageSchema::ISSUE_SURPLUS;
364
}
365
366
if (!$expect && !$actual) {
367
throw new Exception(pht('Can not compare two missing schemata!'));
368
} else if ($expect && !$actual) {
369
$issues = array($missing_issue);
370
} else if ($actual && !$expect) {
371
$issues = array($surplus_issue);
372
} else {
373
$issues = $actual->compareTo($expect);
374
}
375
376
return $issues;
377
}
378
379
380
}
381
382