Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/config/schema/PhabricatorConfigSchemaSpec.php
12256 views
1
<?php
2
3
abstract class PhabricatorConfigSchemaSpec extends Phobject {
4
5
private $server;
6
private $utf8Charset;
7
private $utf8BinaryCollation;
8
private $utf8SortingCollation;
9
10
const DATATYPE_UNKNOWN = '<unknown>';
11
12
public function setUTF8SortingCollation($utf8_sorting_collation) {
13
$this->utf8SortingCollation = $utf8_sorting_collation;
14
return $this;
15
}
16
17
public function getUTF8SortingCollation() {
18
return $this->utf8SortingCollation;
19
}
20
21
public function setUTF8BinaryCollation($utf8_binary_collation) {
22
$this->utf8BinaryCollation = $utf8_binary_collation;
23
return $this;
24
}
25
26
public function getUTF8BinaryCollation() {
27
return $this->utf8BinaryCollation;
28
}
29
30
public function setUTF8Charset($utf8_charset) {
31
$this->utf8Charset = $utf8_charset;
32
return $this;
33
}
34
35
public function getUTF8Charset() {
36
return $this->utf8Charset;
37
}
38
39
public function setServer(PhabricatorConfigServerSchema $server) {
40
$this->server = $server;
41
return $this;
42
}
43
44
public function getServer() {
45
return $this->server;
46
}
47
48
abstract public function buildSchemata();
49
50
protected function buildLiskObjectSchema(PhabricatorLiskDAO $object) {
51
$index_options = array();
52
53
$persistence = $object->getSchemaPersistence();
54
if ($persistence !== null) {
55
$index_options['persistence'] = $persistence;
56
}
57
58
$this->buildRawSchema(
59
$object->getApplicationName(),
60
$object->getTableName(),
61
$object->getSchemaColumns(),
62
$object->getSchemaKeys(),
63
$index_options);
64
}
65
66
protected function buildFerretIndexSchema(PhabricatorFerretEngine $engine) {
67
$index_options = array(
68
'persistence' => PhabricatorConfigTableSchema::PERSISTENCE_INDEX,
69
);
70
71
$this->buildRawSchema(
72
$engine->getApplicationName(),
73
$engine->getDocumentTableName(),
74
$engine->getDocumentSchemaColumns(),
75
$engine->getDocumentSchemaKeys(),
76
$index_options);
77
78
$this->buildRawSchema(
79
$engine->getApplicationName(),
80
$engine->getFieldTableName(),
81
$engine->getFieldSchemaColumns(),
82
$engine->getFieldSchemaKeys(),
83
$index_options);
84
85
$this->buildRawSchema(
86
$engine->getApplicationName(),
87
$engine->getNgramsTableName(),
88
$engine->getNgramsSchemaColumns(),
89
$engine->getNgramsSchemaKeys(),
90
$index_options);
91
92
// NOTE: The common ngrams table is not marked as an index table. It is
93
// tiny and persisting it across a restore saves us a lot of work garbage
94
// collecting common ngrams from the index after it gets built.
95
96
$this->buildRawSchema(
97
$engine->getApplicationName(),
98
$engine->getCommonNgramsTableName(),
99
$engine->getCommonNgramsSchemaColumns(),
100
$engine->getCommonNgramsSchemaKeys());
101
}
102
103
protected function buildRawSchema(
104
$database_name,
105
$table_name,
106
array $columns,
107
array $keys,
108
array $options = array()) {
109
110
PhutilTypeSpec::checkMap(
111
$options,
112
array(
113
'persistence' => 'optional string',
114
));
115
116
$database = $this->getDatabase($database_name);
117
118
$table = $this->newTable($table_name);
119
120
if (PhabricatorSearchDocument::isInnoDBFulltextEngineAvailable()) {
121
$fulltext_engine = 'InnoDB';
122
} else {
123
$fulltext_engine = 'MyISAM';
124
}
125
126
foreach ($columns as $name => $type) {
127
if ($type === null) {
128
continue;
129
}
130
131
$details = $this->getDetailsForDataType($type);
132
133
$column_type = $details['type'];
134
$charset = $details['charset'];
135
$collation = $details['collation'];
136
$nullable = $details['nullable'];
137
$auto = $details['auto'];
138
139
$column = $this->newColumn($name)
140
->setDataType($type)
141
->setColumnType($column_type)
142
->setCharacterSet($charset)
143
->setCollation($collation)
144
->setNullable($nullable)
145
->setAutoIncrement($auto);
146
147
// If this table has any FULLTEXT fields, we expect it to use the best
148
// available FULLTEXT engine, which may not be InnoDB.
149
switch ($type) {
150
case 'fulltext':
151
case 'fulltext?':
152
$table->setEngine($fulltext_engine);
153
break;
154
}
155
156
$table->addColumn($column);
157
}
158
159
foreach ($keys as $key_name => $key_spec) {
160
if ($key_spec === null) {
161
// This is a subclass removing a key which Lisk expects.
162
continue;
163
}
164
165
$key = $this->newKey($key_name)
166
->setColumnNames(idx($key_spec, 'columns', array()));
167
168
$key->setUnique((bool)idx($key_spec, 'unique'));
169
$key->setIndexType(idx($key_spec, 'type', 'BTREE'));
170
171
$table->addKey($key);
172
}
173
174
$persistence_type = idx($options, 'persistence');
175
if ($persistence_type !== null) {
176
$table->setPersistenceType($persistence_type);
177
}
178
179
$database->addTable($table);
180
}
181
182
protected function buildEdgeSchemata(PhabricatorLiskDAO $object) {
183
$this->buildRawSchema(
184
$object->getApplicationName(),
185
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
186
array(
187
'src' => 'phid',
188
'type' => 'uint32',
189
'dst' => 'phid',
190
'dateCreated' => 'epoch',
191
'seq' => 'uint32',
192
'dataID' => 'id?',
193
),
194
array(
195
'PRIMARY' => array(
196
'columns' => array('src', 'type', 'dst'),
197
'unique' => true,
198
),
199
'src' => array(
200
'columns' => array('src', 'type', 'dateCreated', 'seq'),
201
),
202
'key_dst' => array(
203
'columns' => array('dst', 'type', 'src'),
204
'unique' => true,
205
),
206
));
207
208
$this->buildRawSchema(
209
$object->getApplicationName(),
210
PhabricatorEdgeConfig::TABLE_NAME_EDGEDATA,
211
array(
212
'id' => 'auto',
213
'data' => 'text',
214
),
215
array(
216
'PRIMARY' => array(
217
'columns' => array('id'),
218
'unique' => true,
219
),
220
));
221
}
222
223
protected function getDatabase($name) {
224
$server = $this->getServer();
225
226
$database = $server->getDatabase($this->getNamespacedDatabase($name));
227
if (!$database) {
228
$database = $this->newDatabase($name);
229
$server->addDatabase($database);
230
}
231
232
return $database;
233
}
234
235
protected function newDatabase($name) {
236
return id(new PhabricatorConfigDatabaseSchema())
237
->setName($this->getNamespacedDatabase($name))
238
->setCharacterSet($this->getUTF8Charset())
239
->setCollation($this->getUTF8BinaryCollation());
240
}
241
242
protected function getNamespacedDatabase($name) {
243
$namespace = PhabricatorLiskDAO::getStorageNamespace();
244
return $namespace.'_'.$name;
245
}
246
247
protected function newTable($name) {
248
return id(new PhabricatorConfigTableSchema())
249
->setName($name)
250
->setCollation($this->getUTF8BinaryCollation())
251
->setEngine('InnoDB');
252
}
253
254
protected function newColumn($name) {
255
return id(new PhabricatorConfigColumnSchema())
256
->setName($name);
257
}
258
259
protected function newKey($name) {
260
return id(new PhabricatorConfigKeySchema())
261
->setName($name);
262
}
263
264
public function getMaximumByteLengthForDataType($data_type) {
265
$info = $this->getDetailsForDataType($data_type);
266
return idx($info, 'bytes');
267
}
268
269
private function getDetailsForDataType($data_type) {
270
$column_type = null;
271
$charset = null;
272
$collation = null;
273
$auto = false;
274
$bytes = null;
275
276
// If the type ends with "?", make the column nullable.
277
$nullable = false;
278
if (preg_match('/\?$/', $data_type)) {
279
$nullable = true;
280
$data_type = substr($data_type, 0, -1);
281
}
282
283
// NOTE: MySQL allows fragments like "VARCHAR(32) CHARACTER SET binary",
284
// but just interprets that to mean "VARBINARY(32)". The fragment is
285
// totally disallowed in a MODIFY statement vs a CREATE TABLE statement.
286
287
$is_binary = ($this->getUTF8Charset() == 'binary');
288
$matches = null;
289
$pattern = '/^(fulltext|sort|text|char)(\d+)?\z/';
290
if (preg_match($pattern, $data_type, $matches)) {
291
292
// Limit the permitted column lengths under the theory that it would
293
// be nice to eventually reduce this to a small set of standard lengths.
294
295
static $valid_types = array(
296
'text255' => true,
297
'text160' => true,
298
'text128' => true,
299
'text64' => true,
300
'text40' => true,
301
'text32' => true,
302
'text20' => true,
303
'text16' => true,
304
'text12' => true,
305
'text8' => true,
306
'text4' => true,
307
'text' => true,
308
'char3' => true,
309
'sort255' => true,
310
'sort128' => true,
311
'sort64' => true,
312
'sort32' => true,
313
'sort' => true,
314
'fulltext' => true,
315
);
316
317
if (empty($valid_types[$data_type])) {
318
throw new Exception(pht('Unknown column type "%s"!', $data_type));
319
}
320
321
$type = $matches[1];
322
$size = idx($matches, 2);
323
324
if ($size) {
325
$bytes = $size;
326
}
327
328
switch ($type) {
329
case 'text':
330
if ($is_binary) {
331
if ($size) {
332
$column_type = 'varbinary('.$size.')';
333
} else {
334
$column_type = 'longblob';
335
}
336
} else {
337
if ($size) {
338
$column_type = 'varchar('.$size.')';
339
} else {
340
$column_type = 'longtext';
341
}
342
}
343
break;
344
case 'sort':
345
if ($size) {
346
$column_type = 'varchar('.$size.')';
347
} else {
348
$column_type = 'longtext';
349
}
350
break;
351
case 'fulltext';
352
// MySQL (at least, under MyISAM) refuses to create a FULLTEXT index
353
// on a LONGBLOB column. We'd also lose case insensitivity in search.
354
// Force this column to utf8 collation. This will truncate results
355
// with 4-byte UTF characters in their text, but work reasonably in
356
// the majority of cases.
357
$column_type = 'longtext';
358
break;
359
case 'char':
360
$column_type = 'char('.$size.')';
361
break;
362
}
363
364
switch ($type) {
365
case 'text':
366
case 'char':
367
if ($is_binary) {
368
// We leave collation and character set unspecified in order to
369
// generate valid SQL.
370
} else {
371
$charset = $this->getUTF8Charset();
372
$collation = $this->getUTF8BinaryCollation();
373
}
374
break;
375
case 'sort':
376
case 'fulltext':
377
if ($is_binary) {
378
$charset = 'utf8';
379
} else {
380
$charset = $this->getUTF8Charset();
381
}
382
$collation = $this->getUTF8SortingCollation();
383
break;
384
}
385
} else {
386
switch ($data_type) {
387
case 'auto':
388
$column_type = 'int(10) unsigned';
389
$auto = true;
390
break;
391
case 'auto64':
392
$column_type = 'bigint(20) unsigned';
393
$auto = true;
394
break;
395
case 'id':
396
case 'epoch':
397
case 'uint32':
398
$column_type = 'int(10) unsigned';
399
break;
400
case 'sint32':
401
$column_type = 'int(10)';
402
break;
403
case 'id64':
404
case 'uint64':
405
$column_type = 'bigint(20) unsigned';
406
break;
407
case 'sint64':
408
$column_type = 'bigint(20)';
409
break;
410
case 'phid':
411
case 'policy';
412
case 'hashpath64':
413
case 'ipaddress':
414
$column_type = 'varbinary(64)';
415
break;
416
case 'bytes64':
417
$column_type = 'binary(64)';
418
break;
419
case 'bytes40':
420
$column_type = 'binary(40)';
421
break;
422
case 'bytes32':
423
$column_type = 'binary(32)';
424
break;
425
case 'bytes20':
426
$column_type = 'binary(20)';
427
break;
428
case 'bytes12':
429
$column_type = 'binary(12)';
430
break;
431
case 'bytes4':
432
$column_type = 'binary(4)';
433
break;
434
case 'bytes':
435
$column_type = 'longblob';
436
break;
437
case 'bool':
438
$column_type = 'tinyint(1)';
439
break;
440
case 'double':
441
$column_type = 'double';
442
break;
443
case 'date':
444
$column_type = 'date';
445
break;
446
default:
447
$column_type = self::DATATYPE_UNKNOWN;
448
$charset = self::DATATYPE_UNKNOWN;
449
$collation = self::DATATYPE_UNKNOWN;
450
break;
451
}
452
}
453
454
return array(
455
'type' => $column_type,
456
'charset' => $charset,
457
'collation' => $collation,
458
'nullable' => $nullable,
459
'auto' => $auto,
460
'bytes' => $bytes,
461
);
462
}
463
464
}
465
466