mirror of
https://github.com/Lastorder-DC/rhymix.git
synced 2026-01-03 08:41:39 +09:00
356 lines
8.7 KiB
PHP
356 lines
8.7 KiB
PHP
<?php
|
|
|
|
namespace Rhymix\Framework\Parsers;
|
|
|
|
/**
|
|
* DB table parser class for XE compatibility.
|
|
*/
|
|
class DBTableParser extends BaseParser
|
|
{
|
|
/**
|
|
* Mapping for XE-compatible types.
|
|
*/
|
|
protected const XE_COMPAT_TYPES = array(
|
|
'bignumber' => 'bigint',
|
|
'number' => 'bigint',
|
|
'bigtext' => 'longtext',
|
|
'date' => 'char(14)',
|
|
);
|
|
|
|
/**
|
|
* List of types for which the size attribute will be ignored.
|
|
*/
|
|
protected const NO_SIZE_TYPES = array(
|
|
'bigint' => true,
|
|
'int' => true,
|
|
'integer' => true,
|
|
);
|
|
|
|
/**
|
|
* Load a table definition XML file.
|
|
*
|
|
* @param string $filename
|
|
* @param string $content
|
|
* @return ?object
|
|
*/
|
|
public static function loadXML(string $filename = '', string $content = ''): ?object
|
|
{
|
|
// Load the XML content.
|
|
if ($content)
|
|
{
|
|
$xml = simplexml_load_string($content);
|
|
}
|
|
else
|
|
{
|
|
$xml = simplexml_load_string(file_get_contents($filename));
|
|
}
|
|
|
|
if ($xml === false)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Initialize table definition.
|
|
$table = new DBTable\Table;
|
|
if ($filename)
|
|
{
|
|
$table->name = preg_replace('/\.xml$/', '', basename($filename));
|
|
}
|
|
else
|
|
{
|
|
$table->name = strval($xml['name']);
|
|
}
|
|
|
|
$is_deleted = strval($xml['deleted']);
|
|
if ($is_deleted !== '')
|
|
{
|
|
$table->is_deleted = toBool($is_deleted);
|
|
}
|
|
|
|
// Load columns.
|
|
foreach ($xml->column as $column_info)
|
|
{
|
|
// Get the column name and type.
|
|
$column = new DBTable\Column;
|
|
$column->name = strval($column_info['name']);
|
|
list($column->type, $column->xetype, $column->size) = self::getTypeAndSize(strval($column_info['type']), strval($column_info['size']));
|
|
|
|
// Get all attributes.
|
|
$attribs = self::_getAttributes($column_info);
|
|
|
|
// Get the charset/utf8mb4 attribute.
|
|
if (isset($attribs['charset']))
|
|
{
|
|
$column->charset = $attribs['charset'];
|
|
}
|
|
elseif (isset($attribs['utf8mb4']))
|
|
{
|
|
$column->charset = toBool($attribs['utf8mb4']) ? 'utf8mb4' : 'utf8';
|
|
}
|
|
elseif ($column->xetype === 'date' || ($column->name === 'ipaddress' && $column->size >= 60) || ($column->type === 'char' && $column->size == 1))
|
|
{
|
|
$column->charset = 'latin1';
|
|
}
|
|
|
|
// Get the default value.
|
|
if (isset($attribs['default']))
|
|
{
|
|
$column->default_value = $attribs['default'];
|
|
}
|
|
|
|
// Get the NOT NULL attribute.
|
|
if (isset($attribs['notnull']))
|
|
{
|
|
$column->not_null = true;
|
|
}
|
|
|
|
// Get index information.
|
|
if (isset($attribs['index']))
|
|
{
|
|
$index_name = $attribs['index'];
|
|
if (!isset($table->indexes[$index_name]))
|
|
{
|
|
$table->indexes[$index_name] = new DBTable\Index;
|
|
$table->indexes[$index_name]->name = $index_name;
|
|
}
|
|
$table->indexes[$index_name]->columns[$column->name] = 0;
|
|
$column->is_indexed = true;
|
|
}
|
|
if (isset($attribs['unique']))
|
|
{
|
|
$index_name = $attribs['unique'];
|
|
if (!isset($table->indexes[$index_name]))
|
|
{
|
|
$table->indexes[$index_name] = new DBTable\Index;
|
|
$table->indexes[$index_name]->name = $index_name;
|
|
$table->indexes[$index_name]->type = 'UNIQUE';
|
|
}
|
|
$table->indexes[$index_name]->columns[$column->name] = 0;
|
|
$column->is_indexed = true;
|
|
$column->is_unique = true;
|
|
}
|
|
|
|
// Get primary key information.
|
|
if (isset($attribs['primarykey']) && toBool($attribs['primarykey']))
|
|
{
|
|
$table->primary_key[] = $column->name;
|
|
$column->is_indexed = true;
|
|
$column->is_unique = true;
|
|
$column->is_primary_key = true;
|
|
}
|
|
|
|
// Get auto-increment information.
|
|
if (isset($attribs['autoincrement']) && toBool($attribs['autoincrement']))
|
|
{
|
|
$column->auto_increment = true;
|
|
}
|
|
|
|
// Add the column to the table definition.
|
|
$table->columns[$column->name] = $column;
|
|
}
|
|
|
|
// Load indexes.
|
|
foreach ($xml->index as $index_info)
|
|
{
|
|
// Get the index name and list of columns.
|
|
$index_info = self::_getAttributes($index_info);
|
|
$index = new DBTable\Index;
|
|
$index->name = $index_info['name'];
|
|
$idxcolumns = array_map('trim', explode(',', $index_info['columns'] ?? $index_info['column']));
|
|
foreach ($idxcolumns as $idxcolumn)
|
|
{
|
|
if (preg_match('/^(\S+)\s*\(([0-9]+)\)$/', $idxcolumn, $matches))
|
|
{
|
|
$index->columns[$matches[1]] = intval($matches[2]);
|
|
$idxcolumn = $matches[1];
|
|
}
|
|
else
|
|
{
|
|
$index->columns[$idxcolumn] = 0;
|
|
}
|
|
}
|
|
|
|
// Get the index type.
|
|
if (isset($index_info['type']) && $index_info['type'])
|
|
{
|
|
$index->type = strtoupper($index_info['type']);
|
|
}
|
|
elseif (isset($index_info['unique']) && toBool($index_info['unique']))
|
|
{
|
|
$index->type = 'UNIQUE';
|
|
}
|
|
|
|
// Set attributes on indexed columns.
|
|
if (isset($table->columns[$idxcolumn]) && is_object($table->columns[$idxcolumn]))
|
|
{
|
|
$table->columns[$idxcolumn]->is_indexed = true;
|
|
$table->columns[$idxcolumn]->is_unique = $index->type === 'UNIQUE' ? true : $table->columns[$idxcolumn]->is_unique;
|
|
}
|
|
|
|
// If any index options are given, also store them in the index class.
|
|
if (isset($index_info['options']) && $index_info['options'])
|
|
{
|
|
$index->options = $index_info['options'];
|
|
}
|
|
|
|
// Add the index to the column definition.
|
|
$table->indexes[$index->name] = $index;
|
|
}
|
|
|
|
// Load other constraints (foreign keys).
|
|
foreach ($xml->constraint as $const_info)
|
|
{
|
|
$const_info = self::_getAttributes($const_info);
|
|
$constraint = new DBTable\Constraint;
|
|
$constraint->type = strtoupper($const_info['type'] ?? '');
|
|
$constraint->column = ($const_info['column'] ?? null) ?: null;
|
|
$constraint->references = ($const_info['references'] ?? null) ?: null;
|
|
$constraint->condition = ($const_info['condition'] ?? null) ?: null;
|
|
$constraint->on_delete = ($const_info['ondelete'] ?? null) ?: $constraint->on_delete;
|
|
$constraint->on_update = ($const_info['onupdate'] ?? null) ?: $constraint->on_update;
|
|
$table->constraints[] = $constraint;
|
|
}
|
|
|
|
// Return the complete table definition.
|
|
return $table;
|
|
}
|
|
|
|
/**
|
|
* Get column type and size.
|
|
*
|
|
* @param string $type
|
|
* @param string $size
|
|
* @return array
|
|
*/
|
|
public static function getTypeAndSize(string $type, string $size): array
|
|
{
|
|
// Map XE-compatible types to database native types.
|
|
if (isset(self::XE_COMPAT_TYPES[$type]))
|
|
{
|
|
$xetype = $type;
|
|
$type = self::XE_COMPAT_TYPES[$type];
|
|
}
|
|
else
|
|
{
|
|
$xetype = 'none';
|
|
$type = ltrim($type, '\\');
|
|
}
|
|
|
|
// Extract and normalize the size.
|
|
if (preg_match('/^([a-z0-9_]+)\(([0-9,\s]+)\)$/i', $type, $matches))
|
|
{
|
|
$type = $matches[1];
|
|
$size = $matches[2];
|
|
}
|
|
$size = implode(',', array_map('trim', explode(',', $size))) ?: null;
|
|
if (isset(self::NO_SIZE_TYPES[$type]))
|
|
{
|
|
$size = null;
|
|
}
|
|
|
|
// Return a complete array.
|
|
return [$type, $xetype, $size];
|
|
}
|
|
|
|
/**
|
|
* Get the XE-compatible type from a real database type.
|
|
*
|
|
* @param string $type
|
|
* @param string $size
|
|
* @return string
|
|
*/
|
|
public static function getXEType(string $type, string $size): string
|
|
{
|
|
$type = strtolower($type);
|
|
switch ($type)
|
|
{
|
|
case 'bigint':
|
|
return 'bignumber';
|
|
case 'int':
|
|
case 'integer':
|
|
return 'number';
|
|
case 'longtext':
|
|
return 'bigtext';
|
|
case 'char':
|
|
case 'varchar':
|
|
if ($size == 14)
|
|
{
|
|
return 'date';
|
|
}
|
|
default:
|
|
return $type;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Order tables according to foreign key relations.
|
|
*
|
|
* @param array $tables [$table_name => $filename]
|
|
* @return array
|
|
*/
|
|
public static function resolveDependency(array $tables): array
|
|
{
|
|
// Compile the list of each table's dependency.
|
|
$ref_list = [];
|
|
$i = 0;
|
|
foreach ($tables as $table_name => $filename)
|
|
{
|
|
$table = self::loadXML($filename);
|
|
if ($table)
|
|
{
|
|
$info = (object)['name' => $table_name, 'refs' => [], 'index' => $i++];
|
|
foreach ($table->constraints as $constraint)
|
|
{
|
|
if ($constraint->references)
|
|
{
|
|
$ref = explode('.', $constraint->references);
|
|
$reference_table_name = $ref[0];
|
|
if ($reference_table_name === $table_name)
|
|
{
|
|
continue; // Ignore self-references.
|
|
}
|
|
|
|
$info->refs[] = $reference_table_name;
|
|
}
|
|
}
|
|
$ref_list[$table_name] = $info;
|
|
}
|
|
}
|
|
|
|
// Sort each table after the ones they are dependent on.
|
|
for ($j = 0; $j < count($ref_list); $j++)
|
|
{
|
|
$changed = false;
|
|
foreach ($ref_list as $table_name => $info)
|
|
{
|
|
if (count($info->refs))
|
|
{
|
|
foreach ($info->refs as $ref_name)
|
|
{
|
|
if (isset($ref_list[$ref_name]) && $info->index <= $ref_list[$ref_name]->index)
|
|
{
|
|
$info->index = $ref_list[$ref_name]->index + 1;
|
|
$changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!$changed)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
uasort($ref_list, function($a, $b) {
|
|
return $a->index - $b->index;
|
|
});
|
|
|
|
// Produce a result in the same format as the input.
|
|
$result = [];
|
|
foreach ($ref_list as $table_name => $info)
|
|
{
|
|
$result[$table_name] = $tables[$table_name];
|
|
}
|
|
return $result;
|
|
}
|
|
}
|