forked from joomla/super-powers
update 2024-04-24 21:22:23
This commit is contained in:
parent
321d89954a
commit
c300122c3d
@ -9598,7 +9598,7 @@ class Table extends BaseTable implements Tableinterface
|
|||||||
'store' => 'json',
|
'store' => 'json',
|
||||||
'tab_name' => 'Updates',
|
'tab_name' => 'Updates',
|
||||||
'db' => [
|
'db' => [
|
||||||
'type' => 'TEXT',
|
'type' => 'MEDIUMTEXT',
|
||||||
'default' => 'EMPTY',
|
'default' => 'EMPTY',
|
||||||
'null_switch' => 'NOT NULL',
|
'null_switch' => 'NOT NULL',
|
||||||
'unique_key' => false,
|
'unique_key' => false,
|
||||||
@ -10526,7 +10526,7 @@ class Table extends BaseTable implements Tableinterface
|
|||||||
'store' => 'json',
|
'store' => 'json',
|
||||||
'tab_name' => 'Updates',
|
'tab_name' => 'Updates',
|
||||||
'db' => [
|
'db' => [
|
||||||
'type' => 'TEXT',
|
'type' => 'MEDIUMTEXT',
|
||||||
'default' => 'EMPTY',
|
'default' => 'EMPTY',
|
||||||
'null_switch' => 'NOT NULL',
|
'null_switch' => 'NOT NULL',
|
||||||
'unique_key' => false,
|
'unique_key' => false,
|
||||||
@ -10734,7 +10734,7 @@ class Table extends BaseTable implements Tableinterface
|
|||||||
'store' => 'json',
|
'store' => 'json',
|
||||||
'tab_name' => 'Updates',
|
'tab_name' => 'Updates',
|
||||||
'db' => [
|
'db' => [
|
||||||
'type' => 'TEXT',
|
'type' => 'MEDIUMTEXT',
|
||||||
'default' => 'EMPTY',
|
'default' => 'EMPTY',
|
||||||
'null_switch' => 'NOT NULL',
|
'null_switch' => 'NOT NULL',
|
||||||
'unique_key' => false,
|
'unique_key' => false,
|
||||||
|
@ -48,7 +48,7 @@ abstract class BaseTable implements Tableinterface
|
|||||||
'tab_name' => NULL,
|
'tab_name' => NULL,
|
||||||
'db' => [
|
'db' => [
|
||||||
'type' => 'INT(11)',
|
'type' => 'INT(11)',
|
||||||
'default' => '',
|
'default' => 'EMPTY',
|
||||||
'auto_increment' => true,
|
'auto_increment' => true,
|
||||||
'primary_key' => true,
|
'primary_key' => true,
|
||||||
'null_switch' => 'NOT NULL'
|
'null_switch' => 'NOT NULL'
|
||||||
@ -227,7 +227,7 @@ abstract class BaseTable implements Tableinterface
|
|||||||
'tab_name' => NULL,
|
'tab_name' => NULL,
|
||||||
'db' => [
|
'db' => [
|
||||||
'type' => 'TEXT',
|
'type' => 'TEXT',
|
||||||
'default' => '',
|
'default' => 'EMPTY',
|
||||||
'null_switch' => 'NULL'
|
'null_switch' => 'NULL'
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
'tab_name' => NULL,
|
'tab_name' => NULL,
|
||||||
'db' => [
|
'db' => [
|
||||||
'type' => 'INT(11)',
|
'type' => 'INT(11)',
|
||||||
'default' => '',
|
'default' => 'EMPTY',
|
||||||
'auto_increment' => true,
|
'auto_increment' => true,
|
||||||
'primary_key' => true,
|
'primary_key' => true,
|
||||||
'null_switch' => 'NOT NULL'
|
'null_switch' => 'NOT NULL'
|
||||||
@ -203,7 +203,7 @@
|
|||||||
'tab_name' => NULL,
|
'tab_name' => NULL,
|
||||||
'db' => [
|
'db' => [
|
||||||
'type' => 'TEXT',
|
'type' => 'TEXT',
|
||||||
'default' => '',
|
'default' => 'EMPTY',
|
||||||
'null_switch' => 'NULL'
|
'null_switch' => 'NULL'
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -19,23 +19,28 @@ abstract Schema #Orange {
|
|||||||
- array $keys
|
- array $keys
|
||||||
- array $columns
|
- array $columns
|
||||||
- array $success
|
- array $success
|
||||||
|
# $currentVersion
|
||||||
+ __construct(Table $table)
|
+ __construct(Table $table)
|
||||||
+ update() : array
|
+ update() : array
|
||||||
+ createTable(string $table) : void
|
|
||||||
+ updateSchema(string $table) : void
|
|
||||||
# {abstract} getCode() : string
|
# {abstract} getCode() : string
|
||||||
|
# tableExists(string $table) : bool
|
||||||
|
+ updateSchema(string $table) : void
|
||||||
|
+ createTable(string $table) : void
|
||||||
|
# getExistingColumns(string $table) : array
|
||||||
# addMissingColumns(string $table, array $columns) : void
|
# addMissingColumns(string $table, array $columns) : void
|
||||||
# checkColumnsDataType(string $table, array $columns) : void
|
# checkColumnsDataType(string $table, array $columns) : void
|
||||||
|
# getColumnDefinition(string $table, string $field) : ?string
|
||||||
|
# checkDefault(string $table, string $column) : void
|
||||||
# updateColumnsDataType(string $table, array $columns) : void
|
# updateColumnsDataType(string $table, array $columns) : void
|
||||||
|
# getTable(string $table) : string
|
||||||
|
isDataTypeChangeSignificant(string $currentType, string $expectedType) : bool
|
||||||
|
# adjustExistingDefaults(string $table, string $column, ...) : bool
|
||||||
# updateColumnDataType(string $updateString, string $table, ...) : bool
|
# updateColumnDataType(string $updateString, string $table, ...) : bool
|
||||||
# getTableKeys() : string
|
# getTableKeys() : string
|
||||||
|
# setKeys(array $column) : void
|
||||||
# setUniqueKey(array $column) : void
|
# setUniqueKey(array $column) : void
|
||||||
# setKey(array $column) : void
|
# setKey(array $column) : void
|
||||||
# getTable(string $table) : string
|
# getDefaultValue(string $type, ?string $defaultValue, ...) : string
|
||||||
- tableExists(string $table) : bool
|
|
||||||
- getExistingColumns(string $table) : array
|
|
||||||
- getColumnDefinition(string $table, string $field) : ?string
|
|
||||||
- setKeys(array $column) : void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
note right of Schema::__construct
|
note right of Schema::__construct
|
||||||
@ -51,25 +56,39 @@ note left of Schema::update
|
|||||||
return: array
|
return: array
|
||||||
end note
|
end note
|
||||||
|
|
||||||
note right of Schema::createTable
|
note right of Schema::getCode
|
||||||
Create a table with all necessary fields.
|
Get the targeted component code
|
||||||
|
|
||||||
since: 3.2.1
|
since: 3.2.1
|
||||||
return: void
|
return: string
|
||||||
end note
|
end note
|
||||||
|
|
||||||
note left of Schema::updateSchema
|
note left of Schema::tableExists
|
||||||
|
Check if a table exists in the database.
|
||||||
|
|
||||||
|
since: 3.2.1
|
||||||
|
return: bool
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of Schema::updateSchema
|
||||||
Update the schema of an existing table.
|
Update the schema of an existing table.
|
||||||
|
|
||||||
since: 3.2.1
|
since: 3.2.1
|
||||||
return: void
|
return: void
|
||||||
end note
|
end note
|
||||||
|
|
||||||
note right of Schema::getCode
|
note left of Schema::createTable
|
||||||
Get the targeted component code
|
Create a table with all necessary fields.
|
||||||
|
|
||||||
since: 3.2.1
|
since: 3.2.1
|
||||||
return: string
|
return: void
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of Schema::getExistingColumns
|
||||||
|
Fetch existing columns from a database table.
|
||||||
|
|
||||||
|
since: 3.2.1
|
||||||
|
return: array
|
||||||
end note
|
end note
|
||||||
|
|
||||||
note left of Schema::addMissingColumns
|
note left of Schema::addMissingColumns
|
||||||
@ -86,6 +105,21 @@ note right of Schema::checkColumnsDataType
|
|||||||
return: void
|
return: void
|
||||||
end note
|
end note
|
||||||
|
|
||||||
|
note left of Schema::getColumnDefinition
|
||||||
|
Generates a SQL snippet for defining a table column, incorporating column type,
|
||||||
|
default value, nullability, and auto-increment properties.
|
||||||
|
|
||||||
|
since: 3.2.1
|
||||||
|
return: ?string
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of Schema::checkDefault
|
||||||
|
Check and Update the default values if needed, including existing data adjustments
|
||||||
|
|
||||||
|
since: 3.2.1
|
||||||
|
return: void
|
||||||
|
end note
|
||||||
|
|
||||||
note left of Schema::updateColumnsDataType
|
note left of Schema::updateColumnsDataType
|
||||||
Update the data type of the given fields.
|
Update the data type of the given fields.
|
||||||
|
|
||||||
@ -93,7 +127,38 @@ note left of Schema::updateColumnsDataType
|
|||||||
return: void
|
return: void
|
||||||
end note
|
end note
|
||||||
|
|
||||||
note right of Schema::updateColumnDataType
|
note right of Schema::getTable
|
||||||
|
Add the component name to get the full table name.
|
||||||
|
|
||||||
|
since: 3.2.1
|
||||||
|
return: string
|
||||||
|
end note
|
||||||
|
|
||||||
|
note left of Schema::isDataTypeChangeSignificant
|
||||||
|
Determines if the change in data type between two definitions is significant.
|
||||||
|
This function checks if there's a significant difference between the current
|
||||||
|
data type and the expected data type that would require updating the database schema.
|
||||||
|
It ignores size and other modifiers for certain data types where MySQL considers
|
||||||
|
these attributes irrelevant for storage.
|
||||||
|
|
||||||
|
since: 3.2.1
|
||||||
|
return: bool
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of Schema::adjustExistingDefaults
|
||||||
|
Updates existing rows in a column to a new default value
|
||||||
|
|
||||||
|
since: 3.2.1
|
||||||
|
return: bool
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
string $table
|
||||||
|
string $column
|
||||||
|
mixed $currentDefault
|
||||||
|
mixed $newDefault
|
||||||
|
end note
|
||||||
|
|
||||||
|
note left of Schema::updateColumnDataType
|
||||||
Update the data type of the given field.
|
Update the data type of the given field.
|
||||||
|
|
||||||
since: 3.2.1
|
since: 3.2.1
|
||||||
@ -105,13 +170,20 @@ note right of Schema::updateColumnDataType
|
|||||||
string $field
|
string $field
|
||||||
end note
|
end note
|
||||||
|
|
||||||
note left of Schema::getTableKeys
|
note right of Schema::getTableKeys
|
||||||
Key all needed keys for this table
|
Key all needed keys for this table
|
||||||
|
|
||||||
since: 3.2.1
|
since: 3.2.1
|
||||||
return: string
|
return: string
|
||||||
end note
|
end note
|
||||||
|
|
||||||
|
note left of Schema::setKeys
|
||||||
|
Function to set the view keys
|
||||||
|
|
||||||
|
since: 3.2.1
|
||||||
|
return: void
|
||||||
|
end note
|
||||||
|
|
||||||
note right of Schema::setUniqueKey
|
note right of Schema::setUniqueKey
|
||||||
Function to set the unique key
|
Function to set the unique key
|
||||||
|
|
||||||
@ -126,40 +198,19 @@ note left of Schema::setKey
|
|||||||
return: void
|
return: void
|
||||||
end note
|
end note
|
||||||
|
|
||||||
note right of Schema::getTable
|
note right of Schema::getDefaultValue
|
||||||
Add the component name to get the full table name.
|
Adjusts the default value SQL fragment for a database field based on its type and specific rules.
|
||||||
|
If the field is of type DATETIME and the Joomla version is not 3, it sets the default to CURRENT_TIMESTAMP
|
||||||
|
if not explicitly specified otherwise. For all other types, or when a 'EMPTY' default is specified, it handles
|
||||||
|
defaults by either leaving them unset or applying the provided default, properly quoted for SQL safety.
|
||||||
|
|
||||||
since: 3.2.1
|
since: 3.2.1
|
||||||
return: string
|
return: string
|
||||||
end note
|
|
||||||
|
|
||||||
note left of Schema::tableExists
|
arguments:
|
||||||
Check if a table exists in the database.
|
string $type
|
||||||
|
?string $defaultValue
|
||||||
since: 3.2.1
|
bool $pure = false
|
||||||
return: bool
|
|
||||||
end note
|
|
||||||
|
|
||||||
note right of Schema::getExistingColumns
|
|
||||||
Fetch existing columns from a database table.
|
|
||||||
|
|
||||||
since: 3.2.1
|
|
||||||
return: array
|
|
||||||
end note
|
|
||||||
|
|
||||||
note left of Schema::getColumnDefinition
|
|
||||||
Generates a SQL snippet for defining a table column, incorporating column type,
|
|
||||||
default value, nullability, and auto-increment properties.
|
|
||||||
|
|
||||||
since: 3.2.1
|
|
||||||
return: ?string
|
|
||||||
end note
|
|
||||||
|
|
||||||
note right of Schema::setKeys
|
|
||||||
Function to set the view keys
|
|
||||||
|
|
||||||
since: 3.2.1
|
|
||||||
return: void
|
|
||||||
end note
|
end note
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
|
@ -13,6 +13,7 @@ namespace VDM\Joomla\Abstraction;
|
|||||||
|
|
||||||
|
|
||||||
use Joomla\CMS\Factory;
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Version;
|
||||||
use VDM\Joomla\Interfaces\Tableinterface as Table;
|
use VDM\Joomla\Interfaces\Tableinterface as Table;
|
||||||
use VDM\Joomla\Interfaces\SchemaInterface;
|
use VDM\Joomla\Interfaces\SchemaInterface;
|
||||||
|
|
||||||
@ -87,6 +88,14 @@ abstract class Schema implements SchemaInterface
|
|||||||
*/
|
*/
|
||||||
private array $success;
|
private array $success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Joomla Version We are IN
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 3.2.1
|
||||||
|
**/
|
||||||
|
protected $currentVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
@ -108,8 +117,11 @@ abstract class Schema implements SchemaInterface
|
|||||||
|
|
||||||
// set the component table
|
// set the component table
|
||||||
$this->prefix = $this->db->getPrefix() . $this->getCode();
|
$this->prefix = $this->db->getPrefix() . $this->getCode();
|
||||||
|
|
||||||
|
// set the current version
|
||||||
|
$this->currentVersion = Version::MAJOR_VERSION;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw new \Exception("Error: failed to initialize schema class due to a database error.", 0, $e);
|
throw new \Exception("Error: failed to initialize schema class due to a database error.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +153,7 @@ abstract class Schema implements SchemaInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw new \Exception("Error: updating database schema.", 0, $e);
|
throw new \Exception("Error: updating database schema. " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($this->success) == 1)
|
if (count($this->success) == 1)
|
||||||
@ -156,6 +168,63 @@ abstract class Schema implements SchemaInterface
|
|||||||
return $this->success;
|
return $this->success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the targeted component code
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @since 3.2.1
|
||||||
|
*/
|
||||||
|
abstract protected function getCode(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a table exists in the database.
|
||||||
|
*
|
||||||
|
* @param string $table The name of the table to check.
|
||||||
|
*
|
||||||
|
* @return bool True if table exists, False otherwise.
|
||||||
|
* @since 3.2.1
|
||||||
|
*/
|
||||||
|
protected function tableExists(string $table): bool
|
||||||
|
{
|
||||||
|
return in_array($this->getTable($table), $this->tables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the schema of an existing table.
|
||||||
|
*
|
||||||
|
* @param string $table The table to update.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @since 3.2.1
|
||||||
|
* @throws \Exception If there is an error while updating the schema.
|
||||||
|
*/
|
||||||
|
public function updateSchema(string $table): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$existingColumns = $this->getExistingColumns($table);
|
||||||
|
$expectedColumns = $this->table->fields($table, true);
|
||||||
|
|
||||||
|
$missingColumns = array_diff($expectedColumns, $existingColumns);
|
||||||
|
|
||||||
|
if (!empty($missingColumns))
|
||||||
|
{
|
||||||
|
$this->addMissingColumns($table, $missingColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->checkColumnsDataType($table, $expectedColumns);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception("Error: updating schema for $table table. " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($missingColumns))
|
||||||
|
{
|
||||||
|
$column_s = (count($missingColumns) == 1) ? 'column' : 'columns';
|
||||||
|
$missingColumns = implode(', ', $missingColumns);
|
||||||
|
$this->success[] = "Success: added missing ($missingColumns) $column_s to $table table.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a table with all necessary fields.
|
* Create a table with all necessary fields.
|
||||||
*
|
*
|
||||||
@ -189,56 +258,27 @@ abstract class Schema implements SchemaInterface
|
|||||||
$this->db->setQuery($createTableSql);
|
$this->db->setQuery($createTableSql);
|
||||||
$this->db->execute();
|
$this->db->execute();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw new \Exception("Error: failed to create missing $table table.", 0, $e);
|
throw new \Exception("Error: failed to create missing $table table. " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->success[] = "Success: created missing $table table.";
|
$this->success[] = "Success: created missing $table table.";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the schema of an existing table.
|
* Fetch existing columns from a database table.
|
||||||
*
|
*
|
||||||
* @param string $table The table to update.
|
* @param string $table The name of the table.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return array An array of column names.
|
||||||
* @since 3.2.1
|
* @since 3.2.1
|
||||||
* @throws \Exception If there is an error while updating the schema.
|
|
||||||
*/
|
*/
|
||||||
public function updateSchema(string $table): void
|
protected function getExistingColumns(string $table): array
|
||||||
{
|
{
|
||||||
try {
|
$this->columns = $this->db->getTableColumns($this->getTable($table), false);
|
||||||
$existingColumns = $this->getExistingColumns($table);
|
|
||||||
$expectedColumns = $this->table->fields($table, true);
|
|
||||||
|
|
||||||
$missingColumns = array_diff($expectedColumns, $existingColumns);
|
return array_keys($this->columns);
|
||||||
|
|
||||||
if (!empty($missingColumns))
|
|
||||||
{
|
|
||||||
$this->addMissingColumns($table, $missingColumns);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->checkColumnsDataType($table, $expectedColumns);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
throw new \Exception("Error: updating schema for $table table.", 0, $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($missingColumns))
|
|
||||||
{
|
|
||||||
$column_s = (count($missingColumns) == 1) ? 'column' : 'columns';
|
|
||||||
$missingColumns = implode(', ', $missingColumns);
|
|
||||||
$this->success[] = "Success: added missing ($missingColumns) $column_s to $table table.";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the targeted component code
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @since 3.2.1
|
|
||||||
*/
|
|
||||||
abstract protected function getCode(): string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add missing columns to a table.
|
* Add missing columns to a table.
|
||||||
*
|
*
|
||||||
@ -270,7 +310,7 @@ abstract class Schema implements SchemaInterface
|
|||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$column_s = (count($columns) == 1) ? 'column' : 'columns';
|
$column_s = (count($columns) == 1) ? 'column' : 'columns';
|
||||||
$columns = implode(', ', $columns);
|
$columns = implode(', ', $columns);
|
||||||
throw new \Exception("Error: failed to add ($columns) $column_s to $table table.", 0, $e);
|
throw new \Exception("Error: failed to add ($columns) $column_s to $table table. " . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,18 +331,20 @@ abstract class Schema implements SchemaInterface
|
|||||||
$current = $this->columns[$column] ?? null;
|
$current = $this->columns[$column] ?? null;
|
||||||
if ($current === null || ($expected = $this->table->get($table, $column, 'db')) === null)
|
if ($current === null || ($expected = $this->table->get($table, $column, 'db')) === null)
|
||||||
{
|
{
|
||||||
// this field is no longer part of the component and can be ignored
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the data type and size match
|
// check if the data type and size match
|
||||||
if (strcasecmp($current->Type, $expected['type']) != 0)
|
if ($this->isDataTypeChangeSignificant($current->Type, $expected['type']))
|
||||||
{
|
{
|
||||||
$requireUpdate[$column] = [
|
$requireUpdate[$column] = [
|
||||||
'column' => $column,
|
'column' => $column,
|
||||||
'current' => $current->Type,
|
'current' => $current->Type,
|
||||||
'expected' => $expected['type']
|
'expected' => $expected['type']
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// check if update of default values is needed
|
||||||
|
$this->checkDefault($table, $column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,6 +354,89 @@ abstract class Schema implements SchemaInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a SQL snippet for defining a table column, incorporating column type,
|
||||||
|
* default value, nullability, and auto-increment properties.
|
||||||
|
*
|
||||||
|
* @param string $table The table name to be used.
|
||||||
|
* @param string $field The field name in the table to generate SQL for.
|
||||||
|
*
|
||||||
|
* @return string|null The SQL snippet for the column definition.
|
||||||
|
* @since 3.2.1
|
||||||
|
* @throws \Exception If the schema details cannot be retrieved or the SQL statement cannot be constructed properly.
|
||||||
|
*/
|
||||||
|
protected function getColumnDefinition(string $table, string $field): ?string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Retrieve the database schema details for the specified table and field
|
||||||
|
if (($db = $this->table->get($table, $field, 'db')) === null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the column name
|
||||||
|
$column_name = $this->db->quoteName($field);
|
||||||
|
$db['name'] = $field;
|
||||||
|
|
||||||
|
// Prepare the type and default value SQL statement
|
||||||
|
$type = $db['type'] ?? 'TEXT';
|
||||||
|
$db_default = isset($db['default']) ? $db['default'] : null;
|
||||||
|
$default = $this->getDefaultValue($type, $db_default);
|
||||||
|
|
||||||
|
// Prepare the null switch, and auto increment statement
|
||||||
|
$null_switch = !empty($db['null_switch']) ? " " . $db['null_switch'] : '';
|
||||||
|
$auto_increment = !empty($db['auto_increment']) ? " AUTO_INCREMENT" : '';
|
||||||
|
|
||||||
|
$this->setKeys($db);
|
||||||
|
|
||||||
|
// Assemble the SQL snippet for the column definition
|
||||||
|
return "{$column_name} {$type}{$null_switch}{$default}{$auto_increment}";
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception("Error: failed to generate column definition for ($table.$field). " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check and Update the default values if needed, including existing data adjustments
|
||||||
|
*
|
||||||
|
* @param string $table The table to update.
|
||||||
|
* @param string $column The column/field to check.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @since 3.2.1
|
||||||
|
*/
|
||||||
|
protected function checkDefault(string $table, string $column): void
|
||||||
|
{
|
||||||
|
// Retrieve the expected column configuration
|
||||||
|
$expected = $this->table->get($table, $column, 'db');
|
||||||
|
|
||||||
|
// Skip updates if the column is auto_increment
|
||||||
|
if (isset($expected['auto_increment']) && $expected['auto_increment'])
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the current column configuration
|
||||||
|
$current = $this->columns[$column];
|
||||||
|
|
||||||
|
// Check if default should be empty and current default is null, skip processing
|
||||||
|
if (strtoupper($expected['default']) === 'EMPTY' && $current->Default === NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the new default value based on the expected settings
|
||||||
|
$type = $expected['type'] ?? 'TEXT';
|
||||||
|
$db_default = isset($expected['default']) ? $expected['default'] : null;
|
||||||
|
$newDefault = $this->getDefaultValue($type, $db_default, true);
|
||||||
|
|
||||||
|
// First, adjust existing rows to conform to the new default if necessary
|
||||||
|
if (is_numeric($newDefault) && $this->adjustExistingDefaults($table, $column, $current->Default, $newDefault))
|
||||||
|
{
|
||||||
|
$this->success[] = "Success: updated the ($column) defaults in $table table.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the data type of the given fields.
|
* Update the data type of the given fields.
|
||||||
*
|
*
|
||||||
@ -336,13 +461,120 @@ abstract class Schema implements SchemaInterface
|
|||||||
|
|
||||||
if ($this->updateColumnDataType($alterQuery, $table, $column))
|
if ($this->updateColumnDataType($alterQuery, $table, $column))
|
||||||
{
|
{
|
||||||
$current = (string) $types['current'] ?? 'error';
|
$current = $types['current'] ?? 'error';
|
||||||
$expected = (string) $types['expected'] ?? 'error';
|
$expected = $types['expected'] ?? 'error';
|
||||||
$this->success[] = "Success: updated ($column) column datatype $current to $expected in $table table.";
|
$this->success[] = "Success: updated ($column) column datatype $current to $expected in $table table.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the component name to get the full table name.
|
||||||
|
*
|
||||||
|
* @param string $table The table name.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @since 3.2.1
|
||||||
|
*/
|
||||||
|
protected function getTable(string $table): string
|
||||||
|
{
|
||||||
|
return $this->prefix . '_' . $table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the change in data type between two definitions is significant.
|
||||||
|
*
|
||||||
|
* This function checks if there's a significant difference between the current
|
||||||
|
* data type and the expected data type that would require updating the database schema.
|
||||||
|
* It ignores size and other modifiers for certain data types where MySQL considers
|
||||||
|
* these attributes irrelevant for storage.
|
||||||
|
*
|
||||||
|
* @param string $currentType The current data type from the database schema.
|
||||||
|
* @param string $expectedType The expected data type to validate against.
|
||||||
|
*
|
||||||
|
* @return bool Returns true if the data type change is significant, otherwise false.
|
||||||
|
* @since 3.2.1
|
||||||
|
*/
|
||||||
|
function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool
|
||||||
|
{
|
||||||
|
// we only do this for Joomla 4+
|
||||||
|
if ($this->currentVersion != 3)
|
||||||
|
{
|
||||||
|
// Normalize both input types to lowercase for case-insensitive comparison
|
||||||
|
$currentType = strtolower($currentType);
|
||||||
|
$expectedType = strtolower($expectedType);
|
||||||
|
|
||||||
|
// Define types where size or other modifiers are irrelevant
|
||||||
|
$sizeIrrelevantTypes = [
|
||||||
|
'int', 'tinyint', 'smallint', 'mediumint', 'bigint', // Standard integer types
|
||||||
|
'int unsigned', 'tinyint unsigned', 'smallint unsigned', 'mediumint unsigned', 'bigint unsigned', // Unsigned integer types
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check if the type involves size-irrelevant types
|
||||||
|
foreach ($sizeIrrelevantTypes as $type)
|
||||||
|
{
|
||||||
|
if (strpos($expectedType, $type) !== false)
|
||||||
|
{
|
||||||
|
// Remove any numeric sizes and modifiers for comparison
|
||||||
|
$pattern = '/\(\d+\)|unsigned|\s*/';
|
||||||
|
$cleanCurrentType = preg_replace($pattern, '', $currentType);
|
||||||
|
$cleanExpectedType = preg_replace($pattern, '', $expectedType);
|
||||||
|
|
||||||
|
// Compare the cleaned types
|
||||||
|
if ($cleanCurrentType === $cleanExpectedType)
|
||||||
|
{
|
||||||
|
return false; // No significant change
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform a standard case-insensitive comparison for other types
|
||||||
|
if (strcasecmp($currentType, $expectedType) == 0)
|
||||||
|
{
|
||||||
|
return false; // No significant change
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // Significant datatype change detected
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates existing rows in a column to a new default value
|
||||||
|
*
|
||||||
|
* @param string $table The table to update.
|
||||||
|
* @param string $column The column to update.
|
||||||
|
* @param mixed $currentDefault Current default value.
|
||||||
|
* @param mixed $newDefault The new default value to be set.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @since 3.2.1
|
||||||
|
* @throws \Exception If there is an error updating column defaults.
|
||||||
|
*/
|
||||||
|
protected function adjustExistingDefaults(string $table, string $column, $currentDefault, $newDefault): bool
|
||||||
|
{
|
||||||
|
// Determine if adjustment is needed based on new and current defaults
|
||||||
|
if ($newDefault !== $currentDefault)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Format the new default for SQL use
|
||||||
|
$sqlDefault = $this->db->quote($newDefault);
|
||||||
|
|
||||||
|
$updateTable = 'UPDATE ' . $this->db->quoteName($this->getTable($table));
|
||||||
|
$dbField = $this->db->quoteName($column);
|
||||||
|
|
||||||
|
// Update SQL to set new default on existing rows where the default is currently the old default
|
||||||
|
$sql = $updateTable . " SET $dbField = $sqlDefault WHERE $dbField IS NULL OR $dbField = ''";
|
||||||
|
|
||||||
|
// Execute the update
|
||||||
|
$this->db->setQuery($sql);
|
||||||
|
return $this->db->execute();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception("Error: failed to update ($column) column defaults in $table table. " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the data type of the given field.
|
* Update the data type of the given field.
|
||||||
*
|
*
|
||||||
@ -360,7 +592,7 @@ abstract class Schema implements SchemaInterface
|
|||||||
$this->db->setQuery($updateString);
|
$this->db->setQuery($updateString);
|
||||||
return $this->db->execute();
|
return $this->db->execute();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw new \Exception("Error: failed to update the datatype of ($field) column in $table table.", 0, $e);
|
throw new \Exception("Error: failed to update the datatype of ($field) column in $table table. " . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,6 +620,20 @@ abstract class Schema implements SchemaInterface
|
|||||||
return implode(', ', $keys);
|
return implode(', ', $keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to set the view keys
|
||||||
|
*
|
||||||
|
* @param string $column The field column database array values
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @since 3.2.1
|
||||||
|
*/
|
||||||
|
protected function setKeys(array $column): void
|
||||||
|
{
|
||||||
|
$this->setUniqueKey($column);
|
||||||
|
$this->setKey($column);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to set the unique key
|
* Function to set the unique key
|
||||||
*
|
*
|
||||||
@ -423,97 +669,34 @@ abstract class Schema implements SchemaInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the component name to get the full table name.
|
* Adjusts the default value SQL fragment for a database field based on its type and specific rules.
|
||||||
*
|
*
|
||||||
* @param string $table The table name.
|
* If the field is of type DATETIME and the Joomla version is not 3, it sets the default to CURRENT_TIMESTAMP
|
||||||
|
* if not explicitly specified otherwise. For all other types, or when a 'EMPTY' default is specified, it handles
|
||||||
|
* defaults by either leaving them unset or applying the provided default, properly quoted for SQL safety.
|
||||||
*
|
*
|
||||||
* @return void
|
* @param string $type The type of the database field (e.g., 'DATETIME').
|
||||||
* @since 3.2.1
|
* @param string|null $defaultValue Optional default value for the field, null if not provided.
|
||||||
*/
|
* @param bool $pure Optional to add the 'DEFAULT' string or not.
|
||||||
protected function getTable(string $table): string
|
|
||||||
{
|
|
||||||
return $this->prefix . '_' . $table;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a table exists in the database.
|
|
||||||
*
|
*
|
||||||
* @param string $table The name of the table to check.
|
* @return string The SQL fragment to set the default value for a field.
|
||||||
*
|
|
||||||
* @return bool True if table exists, False otherwise.
|
|
||||||
* @since 3.2.1
|
|
||||||
*/
|
|
||||||
private function tableExists(string $table): bool
|
|
||||||
{
|
|
||||||
return in_array($this->getTable($table), $this->tables);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch existing columns from a database table.
|
|
||||||
*
|
|
||||||
* @param string $table The name of the table.
|
|
||||||
*
|
|
||||||
* @return array An array of column names.
|
|
||||||
* @since 3.2.1
|
|
||||||
*/
|
|
||||||
private function getExistingColumns(string $table): array
|
|
||||||
{
|
|
||||||
$this->columns = $this->db->getTableColumns($this->getTable($table), false);
|
|
||||||
|
|
||||||
return array_keys($this->columns);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a SQL snippet for defining a table column, incorporating column type,
|
|
||||||
* default value, nullability, and auto-increment properties.
|
|
||||||
*
|
|
||||||
* @param string $table The table name to be used.
|
|
||||||
* @param string $field The field name in the table to generate SQL for.
|
|
||||||
*
|
|
||||||
* @return string|null The SQL snippet for the column definition.
|
|
||||||
* @since 3.2.1
|
* @since 3.2.1
|
||||||
* @throws \Exception If the schema details cannot be retrieved or the SQL statement cannot be constructed properly.
|
|
||||||
*/
|
*/
|
||||||
private function getColumnDefinition(string $table, string $field): ?string
|
protected function getDefaultValue(string $type, ?string $defaultValue, bool $pure = false): string
|
||||||
{
|
{
|
||||||
try {
|
if ($defaultValue === null || strtoupper($defaultValue) === 'EMPTY')
|
||||||
// Retrieve the database schema details for the specified table and field
|
{
|
||||||
if (($db = $this->table->get($table, $field, 'db')) === null)
|
return '';
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the column name
|
|
||||||
$column_name = $this->db->quoteName($field);
|
|
||||||
$db['name'] = $field;
|
|
||||||
|
|
||||||
// Prepare the default value SQL, null switch, and auto increment statement
|
|
||||||
$default = !empty($db['default']) ? " DEFAULT " . $this->db->quote($db['default']) : '';
|
|
||||||
$null_switch = !empty($db['null_switch']) ? " " . $db['null_switch'] : '';
|
|
||||||
$auto_increment = !empty($db['auto_increment']) ? " AUTO_INCREMENT" : '';
|
|
||||||
$type = !empty($db['type']) ? $db['type'] : 'TEXT';
|
|
||||||
|
|
||||||
$this->setKeys($db);
|
|
||||||
|
|
||||||
// Assemble the SQL snippet for the column definition
|
|
||||||
return "{$column_name} {$type}{$default}{$null_switch}{$auto_increment}";
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
throw new \Exception("Error: failed to generate column definition for $table.$field", 0, $e);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Set default for DATETIME fields in Joomla versions above 3
|
||||||
* Function to set the view keys
|
if (strtoupper($type) === 'DATETIME' && $this->currentVersion != 3)
|
||||||
*
|
{
|
||||||
* @param string $column The field column database array values
|
return $pure ? "CURRENT_TIMESTAMP" : " DEFAULT CURRENT_TIMESTAMP";
|
||||||
*
|
}
|
||||||
* @return void
|
|
||||||
* @since 3.2.1
|
// Apply and quote the default value
|
||||||
*/
|
return $pure ? $defaultValue : " DEFAULT " . $this->db->quote($defaultValue);
|
||||||
private function setKeys(array $column): void
|
|
||||||
{
|
|
||||||
$this->setUniqueKey($column);
|
|
||||||
$this->setKey($column);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +61,14 @@
|
|||||||
*/
|
*/
|
||||||
private array $success;
|
private array $success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Joomla Version We are IN
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 3.2.1
|
||||||
|
**/
|
||||||
|
protected $currentVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
@ -82,8 +90,11 @@
|
|||||||
|
|
||||||
// set the component table
|
// set the component table
|
||||||
$this->prefix = $this->db->getPrefix() . $this->getCode();
|
$this->prefix = $this->db->getPrefix() . $this->getCode();
|
||||||
|
|
||||||
|
// set the current version
|
||||||
|
$this->currentVersion = Version::MAJOR_VERSION;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw new \Exception("Error: failed to initialize schema class due to a database error.", 0, $e);
|
throw new \Exception("Error: failed to initialize schema class due to a database error.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +126,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw new \Exception("Error: updating database schema.", 0, $e);
|
throw new \Exception("Error: updating database schema. " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($this->success) == 1)
|
if (count($this->success) == 1)
|
||||||
@ -130,6 +141,63 @@
|
|||||||
return $this->success;
|
return $this->success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the targeted component code
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @since 3.2.1
|
||||||
|
*/
|
||||||
|
abstract protected function getCode(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a table exists in the database.
|
||||||
|
*
|
||||||
|
* @param string $table The name of the table to check.
|
||||||
|
*
|
||||||
|
* @return bool True if table exists, False otherwise.
|
||||||
|
* @since 3.2.1
|
||||||
|
*/
|
||||||
|
protected function tableExists(string $table): bool
|
||||||
|
{
|
||||||
|
return in_array($this->getTable($table), $this->tables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the schema of an existing table.
|
||||||
|
*
|
||||||
|
* @param string $table The table to update.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @since 3.2.1
|
||||||
|
* @throws \Exception If there is an error while updating the schema.
|
||||||
|
*/
|
||||||
|
public function updateSchema(string $table): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$existingColumns = $this->getExistingColumns($table);
|
||||||
|
$expectedColumns = $this->table->fields($table, true);
|
||||||
|
|
||||||
|
$missingColumns = array_diff($expectedColumns, $existingColumns);
|
||||||
|
|
||||||
|
if (!empty($missingColumns))
|
||||||
|
{
|
||||||
|
$this->addMissingColumns($table, $missingColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->checkColumnsDataType($table, $expectedColumns);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception("Error: updating schema for $table table. " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($missingColumns))
|
||||||
|
{
|
||||||
|
$column_s = (count($missingColumns) == 1) ? 'column' : 'columns';
|
||||||
|
$missingColumns = implode(', ', $missingColumns);
|
||||||
|
$this->success[] = "Success: added missing ($missingColumns) $column_s to $table table.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a table with all necessary fields.
|
* Create a table with all necessary fields.
|
||||||
*
|
*
|
||||||
@ -163,56 +231,27 @@
|
|||||||
$this->db->setQuery($createTableSql);
|
$this->db->setQuery($createTableSql);
|
||||||
$this->db->execute();
|
$this->db->execute();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw new \Exception("Error: failed to create missing $table table.", 0, $e);
|
throw new \Exception("Error: failed to create missing $table table. " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->success[] = "Success: created missing $table table.";
|
$this->success[] = "Success: created missing $table table.";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the schema of an existing table.
|
* Fetch existing columns from a database table.
|
||||||
*
|
*
|
||||||
* @param string $table The table to update.
|
* @param string $table The name of the table.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return array An array of column names.
|
||||||
* @since 3.2.1
|
* @since 3.2.1
|
||||||
* @throws \Exception If there is an error while updating the schema.
|
|
||||||
*/
|
*/
|
||||||
public function updateSchema(string $table): void
|
protected function getExistingColumns(string $table): array
|
||||||
{
|
{
|
||||||
try {
|
$this->columns = $this->db->getTableColumns($this->getTable($table), false);
|
||||||
$existingColumns = $this->getExistingColumns($table);
|
|
||||||
$expectedColumns = $this->table->fields($table, true);
|
|
||||||
|
|
||||||
$missingColumns = array_diff($expectedColumns, $existingColumns);
|
return array_keys($this->columns);
|
||||||
|
|
||||||
if (!empty($missingColumns))
|
|
||||||
{
|
|
||||||
$this->addMissingColumns($table, $missingColumns);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->checkColumnsDataType($table, $expectedColumns);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
throw new \Exception("Error: updating schema for $table table.", 0, $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($missingColumns))
|
|
||||||
{
|
|
||||||
$column_s = (count($missingColumns) == 1) ? 'column' : 'columns';
|
|
||||||
$missingColumns = implode(', ', $missingColumns);
|
|
||||||
$this->success[] = "Success: added missing ($missingColumns) $column_s to $table table.";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the targeted component code
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @since 3.2.1
|
|
||||||
*/
|
|
||||||
abstract protected function getCode(): string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add missing columns to a table.
|
* Add missing columns to a table.
|
||||||
*
|
*
|
||||||
@ -244,7 +283,7 @@
|
|||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$column_s = (count($columns) == 1) ? 'column' : 'columns';
|
$column_s = (count($columns) == 1) ? 'column' : 'columns';
|
||||||
$columns = implode(', ', $columns);
|
$columns = implode(', ', $columns);
|
||||||
throw new \Exception("Error: failed to add ($columns) $column_s to $table table.", 0, $e);
|
throw new \Exception("Error: failed to add ($columns) $column_s to $table table. " . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,18 +304,20 @@
|
|||||||
$current = $this->columns[$column] ?? null;
|
$current = $this->columns[$column] ?? null;
|
||||||
if ($current === null || ($expected = $this->table->get($table, $column, 'db')) === null)
|
if ($current === null || ($expected = $this->table->get($table, $column, 'db')) === null)
|
||||||
{
|
{
|
||||||
// this field is no longer part of the component and can be ignored
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the data type and size match
|
// check if the data type and size match
|
||||||
if (strcasecmp($current->Type, $expected['type']) != 0)
|
if ($this->isDataTypeChangeSignificant($current->Type, $expected['type']))
|
||||||
{
|
{
|
||||||
$requireUpdate[$column] = [
|
$requireUpdate[$column] = [
|
||||||
'column' => $column,
|
'column' => $column,
|
||||||
'current' => $current->Type,
|
'current' => $current->Type,
|
||||||
'expected' => $expected['type']
|
'expected' => $expected['type']
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// check if update of default values is needed
|
||||||
|
$this->checkDefault($table, $column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,6 +327,89 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a SQL snippet for defining a table column, incorporating column type,
|
||||||
|
* default value, nullability, and auto-increment properties.
|
||||||
|
*
|
||||||
|
* @param string $table The table name to be used.
|
||||||
|
* @param string $field The field name in the table to generate SQL for.
|
||||||
|
*
|
||||||
|
* @return string|null The SQL snippet for the column definition.
|
||||||
|
* @since 3.2.1
|
||||||
|
* @throws \Exception If the schema details cannot be retrieved or the SQL statement cannot be constructed properly.
|
||||||
|
*/
|
||||||
|
protected function getColumnDefinition(string $table, string $field): ?string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Retrieve the database schema details for the specified table and field
|
||||||
|
if (($db = $this->table->get($table, $field, 'db')) === null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the column name
|
||||||
|
$column_name = $this->db->quoteName($field);
|
||||||
|
$db['name'] = $field;
|
||||||
|
|
||||||
|
// Prepare the type and default value SQL statement
|
||||||
|
$type = $db['type'] ?? 'TEXT';
|
||||||
|
$db_default = isset($db['default']) ? $db['default'] : null;
|
||||||
|
$default = $this->getDefaultValue($type, $db_default);
|
||||||
|
|
||||||
|
// Prepare the null switch, and auto increment statement
|
||||||
|
$null_switch = !empty($db['null_switch']) ? " " . $db['null_switch'] : '';
|
||||||
|
$auto_increment = !empty($db['auto_increment']) ? " AUTO_INCREMENT" : '';
|
||||||
|
|
||||||
|
$this->setKeys($db);
|
||||||
|
|
||||||
|
// Assemble the SQL snippet for the column definition
|
||||||
|
return "{$column_name} {$type}{$null_switch}{$default}{$auto_increment}";
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception("Error: failed to generate column definition for ($table.$field). " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check and Update the default values if needed, including existing data adjustments
|
||||||
|
*
|
||||||
|
* @param string $table The table to update.
|
||||||
|
* @param string $column The column/field to check.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @since 3.2.1
|
||||||
|
*/
|
||||||
|
protected function checkDefault(string $table, string $column): void
|
||||||
|
{
|
||||||
|
// Retrieve the expected column configuration
|
||||||
|
$expected = $this->table->get($table, $column, 'db');
|
||||||
|
|
||||||
|
// Skip updates if the column is auto_increment
|
||||||
|
if (isset($expected['auto_increment']) && $expected['auto_increment'])
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the current column configuration
|
||||||
|
$current = $this->columns[$column];
|
||||||
|
|
||||||
|
// Check if default should be empty and current default is null, skip processing
|
||||||
|
if (strtoupper($expected['default']) === 'EMPTY' && $current->Default === NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the new default value based on the expected settings
|
||||||
|
$type = $expected['type'] ?? 'TEXT';
|
||||||
|
$db_default = isset($expected['default']) ? $expected['default'] : null;
|
||||||
|
$newDefault = $this->getDefaultValue($type, $db_default, true);
|
||||||
|
|
||||||
|
// First, adjust existing rows to conform to the new default if necessary
|
||||||
|
if (is_numeric($newDefault) && $this->adjustExistingDefaults($table, $column, $current->Default, $newDefault))
|
||||||
|
{
|
||||||
|
$this->success[] = "Success: updated the ($column) defaults in $table table.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the data type of the given fields.
|
* Update the data type of the given fields.
|
||||||
*
|
*
|
||||||
@ -310,13 +434,120 @@
|
|||||||
|
|
||||||
if ($this->updateColumnDataType($alterQuery, $table, $column))
|
if ($this->updateColumnDataType($alterQuery, $table, $column))
|
||||||
{
|
{
|
||||||
$current = (string) $types['current'] ?? 'error';
|
$current = $types['current'] ?? 'error';
|
||||||
$expected = (string) $types['expected'] ?? 'error';
|
$expected = $types['expected'] ?? 'error';
|
||||||
$this->success[] = "Success: updated ($column) column datatype $current to $expected in $table table.";
|
$this->success[] = "Success: updated ($column) column datatype $current to $expected in $table table.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the component name to get the full table name.
|
||||||
|
*
|
||||||
|
* @param string $table The table name.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @since 3.2.1
|
||||||
|
*/
|
||||||
|
protected function getTable(string $table): string
|
||||||
|
{
|
||||||
|
return $this->prefix . '_' . $table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the change in data type between two definitions is significant.
|
||||||
|
*
|
||||||
|
* This function checks if there's a significant difference between the current
|
||||||
|
* data type and the expected data type that would require updating the database schema.
|
||||||
|
* It ignores size and other modifiers for certain data types where MySQL considers
|
||||||
|
* these attributes irrelevant for storage.
|
||||||
|
*
|
||||||
|
* @param string $currentType The current data type from the database schema.
|
||||||
|
* @param string $expectedType The expected data type to validate against.
|
||||||
|
*
|
||||||
|
* @return bool Returns true if the data type change is significant, otherwise false.
|
||||||
|
* @since 3.2.1
|
||||||
|
*/
|
||||||
|
function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool
|
||||||
|
{
|
||||||
|
// we only do this for Joomla 4+
|
||||||
|
if ($this->currentVersion != 3)
|
||||||
|
{
|
||||||
|
// Normalize both input types to lowercase for case-insensitive comparison
|
||||||
|
$currentType = strtolower($currentType);
|
||||||
|
$expectedType = strtolower($expectedType);
|
||||||
|
|
||||||
|
// Define types where size or other modifiers are irrelevant
|
||||||
|
$sizeIrrelevantTypes = [
|
||||||
|
'int', 'tinyint', 'smallint', 'mediumint', 'bigint', // Standard integer types
|
||||||
|
'int unsigned', 'tinyint unsigned', 'smallint unsigned', 'mediumint unsigned', 'bigint unsigned', // Unsigned integer types
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check if the type involves size-irrelevant types
|
||||||
|
foreach ($sizeIrrelevantTypes as $type)
|
||||||
|
{
|
||||||
|
if (strpos($expectedType, $type) !== false)
|
||||||
|
{
|
||||||
|
// Remove any numeric sizes and modifiers for comparison
|
||||||
|
$pattern = '/\(\d+\)|unsigned|\s*/';
|
||||||
|
$cleanCurrentType = preg_replace($pattern, '', $currentType);
|
||||||
|
$cleanExpectedType = preg_replace($pattern, '', $expectedType);
|
||||||
|
|
||||||
|
// Compare the cleaned types
|
||||||
|
if ($cleanCurrentType === $cleanExpectedType)
|
||||||
|
{
|
||||||
|
return false; // No significant change
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform a standard case-insensitive comparison for other types
|
||||||
|
if (strcasecmp($currentType, $expectedType) == 0)
|
||||||
|
{
|
||||||
|
return false; // No significant change
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // Significant datatype change detected
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates existing rows in a column to a new default value
|
||||||
|
*
|
||||||
|
* @param string $table The table to update.
|
||||||
|
* @param string $column The column to update.
|
||||||
|
* @param mixed $currentDefault Current default value.
|
||||||
|
* @param mixed $newDefault The new default value to be set.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @since 3.2.1
|
||||||
|
* @throws \Exception If there is an error updating column defaults.
|
||||||
|
*/
|
||||||
|
protected function adjustExistingDefaults(string $table, string $column, $currentDefault, $newDefault): bool
|
||||||
|
{
|
||||||
|
// Determine if adjustment is needed based on new and current defaults
|
||||||
|
if ($newDefault !== $currentDefault)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Format the new default for SQL use
|
||||||
|
$sqlDefault = $this->db->quote($newDefault);
|
||||||
|
|
||||||
|
$updateTable = 'UPDATE ' . $this->db->quoteName($this->getTable($table));
|
||||||
|
$dbField = $this->db->quoteName($column);
|
||||||
|
|
||||||
|
// Update SQL to set new default on existing rows where the default is currently the old default
|
||||||
|
$sql = $updateTable . " SET $dbField = $sqlDefault WHERE $dbField IS NULL OR $dbField = ''";
|
||||||
|
|
||||||
|
// Execute the update
|
||||||
|
$this->db->setQuery($sql);
|
||||||
|
return $this->db->execute();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception("Error: failed to update ($column) column defaults in $table table. " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the data type of the given field.
|
* Update the data type of the given field.
|
||||||
*
|
*
|
||||||
@ -334,7 +565,7 @@
|
|||||||
$this->db->setQuery($updateString);
|
$this->db->setQuery($updateString);
|
||||||
return $this->db->execute();
|
return $this->db->execute();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw new \Exception("Error: failed to update the datatype of ($field) column in $table table.", 0, $e);
|
throw new \Exception("Error: failed to update the datatype of ($field) column in $table table. " . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,6 +593,20 @@
|
|||||||
return implode(', ', $keys);
|
return implode(', ', $keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to set the view keys
|
||||||
|
*
|
||||||
|
* @param string $column The field column database array values
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @since 3.2.1
|
||||||
|
*/
|
||||||
|
protected function setKeys(array $column): void
|
||||||
|
{
|
||||||
|
$this->setUniqueKey($column);
|
||||||
|
$this->setKey($column);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to set the unique key
|
* Function to set the unique key
|
||||||
*
|
*
|
||||||
@ -397,95 +642,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the component name to get the full table name.
|
* Adjusts the default value SQL fragment for a database field based on its type and specific rules.
|
||||||
*
|
*
|
||||||
* @param string $table The table name.
|
* If the field is of type DATETIME and the Joomla version is not 3, it sets the default to CURRENT_TIMESTAMP
|
||||||
|
* if not explicitly specified otherwise. For all other types, or when a 'EMPTY' default is specified, it handles
|
||||||
|
* defaults by either leaving them unset or applying the provided default, properly quoted for SQL safety.
|
||||||
*
|
*
|
||||||
* @return void
|
* @param string $type The type of the database field (e.g., 'DATETIME').
|
||||||
* @since 3.2.1
|
* @param string|null $defaultValue Optional default value for the field, null if not provided.
|
||||||
*/
|
* @param bool $pure Optional to add the 'DEFAULT' string or not.
|
||||||
protected function getTable(string $table): string
|
|
||||||
{
|
|
||||||
return $this->prefix . '_' . $table;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a table exists in the database.
|
|
||||||
*
|
*
|
||||||
* @param string $table The name of the table to check.
|
* @return string The SQL fragment to set the default value for a field.
|
||||||
*
|
|
||||||
* @return bool True if table exists, False otherwise.
|
|
||||||
* @since 3.2.1
|
|
||||||
*/
|
|
||||||
private function tableExists(string $table): bool
|
|
||||||
{
|
|
||||||
return in_array($this->getTable($table), $this->tables);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch existing columns from a database table.
|
|
||||||
*
|
|
||||||
* @param string $table The name of the table.
|
|
||||||
*
|
|
||||||
* @return array An array of column names.
|
|
||||||
* @since 3.2.1
|
|
||||||
*/
|
|
||||||
private function getExistingColumns(string $table): array
|
|
||||||
{
|
|
||||||
$this->columns = $this->db->getTableColumns($this->getTable($table), false);
|
|
||||||
|
|
||||||
return array_keys($this->columns);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a SQL snippet for defining a table column, incorporating column type,
|
|
||||||
* default value, nullability, and auto-increment properties.
|
|
||||||
*
|
|
||||||
* @param string $table The table name to be used.
|
|
||||||
* @param string $field The field name in the table to generate SQL for.
|
|
||||||
*
|
|
||||||
* @return string|null The SQL snippet for the column definition.
|
|
||||||
* @since 3.2.1
|
* @since 3.2.1
|
||||||
* @throws \Exception If the schema details cannot be retrieved or the SQL statement cannot be constructed properly.
|
|
||||||
*/
|
*/
|
||||||
private function getColumnDefinition(string $table, string $field): ?string
|
protected function getDefaultValue(string $type, ?string $defaultValue, bool $pure = false): string
|
||||||
{
|
{
|
||||||
try {
|
if ($defaultValue === null || strtoupper($defaultValue) === 'EMPTY')
|
||||||
// Retrieve the database schema details for the specified table and field
|
{
|
||||||
if (($db = $this->table->get($table, $field, 'db')) === null)
|
return '';
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the column name
|
|
||||||
$column_name = $this->db->quoteName($field);
|
|
||||||
$db['name'] = $field;
|
|
||||||
|
|
||||||
// Prepare the default value SQL, null switch, and auto increment statement
|
|
||||||
$default = !empty($db['default']) ? " DEFAULT " . $this->db->quote($db['default']) : '';
|
|
||||||
$null_switch = !empty($db['null_switch']) ? " " . $db['null_switch'] : '';
|
|
||||||
$auto_increment = !empty($db['auto_increment']) ? " AUTO_INCREMENT" : '';
|
|
||||||
$type = !empty($db['type']) ? $db['type'] : 'TEXT';
|
|
||||||
|
|
||||||
$this->setKeys($db);
|
|
||||||
|
|
||||||
// Assemble the SQL snippet for the column definition
|
|
||||||
return "{$column_name} {$type}{$default}{$null_switch}{$auto_increment}";
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
throw new \Exception("Error: failed to generate column definition for $table.$field", 0, $e);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Set default for DATETIME fields in Joomla versions above 3
|
||||||
* Function to set the view keys
|
if (strtoupper($type) === 'DATETIME' && $this->currentVersion != 3)
|
||||||
*
|
{
|
||||||
* @param string $column The field column database array values
|
return $pure ? "CURRENT_TIMESTAMP" : " DEFAULT CURRENT_TIMESTAMP";
|
||||||
*
|
}
|
||||||
* @return void
|
|
||||||
* @since 3.2.1
|
// Apply and quote the default value
|
||||||
*/
|
return $pure ? $defaultValue : " DEFAULT " . $this->db->quote($defaultValue);
|
||||||
private function setKeys(array $column): void
|
|
||||||
{
|
|
||||||
$this->setUniqueKey($column);
|
|
||||||
$this->setKey($column);
|
|
||||||
}
|
}
|
@ -20,6 +20,6 @@
|
|||||||
"namespace": "[[[NamespacePrefix]]]\\Joomla\\Abstraction.Schema",
|
"namespace": "[[[NamespacePrefix]]]\\Joomla\\Abstraction.Schema",
|
||||||
"description": "Schema Checking\r\n\r\n@since 3.2.1",
|
"description": "Schema Checking\r\n\r\n@since 3.2.1",
|
||||||
"licensing_template": "\/**\r\n * @package Joomla.Component.Builder\r\n *\r\n * @created 4th September, 2022\r\n * @author Llewellyn van der Merwe <https:\/\/dev.vdm.io>\r\n * @git Joomla Component Builder <https:\/\/git.vdm.dev\/joomla\/Component-Builder>\r\n * @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.\r\n * @license GNU General Public License version 2 or later; see LICENSE.txt\r\n *\/\r\n",
|
"licensing_template": "\/**\r\n * @package Joomla.Component.Builder\r\n *\r\n * @created 4th September, 2022\r\n * @author Llewellyn van der Merwe <https:\/\/dev.vdm.io>\r\n * @git Joomla Component Builder <https:\/\/git.vdm.dev\/joomla\/Component-Builder>\r\n * @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.\r\n * @license GNU General Public License version 2 or later; see LICENSE.txt\r\n *\/\r\n",
|
||||||
"head": "use Joomla\\CMS\\Factory;",
|
"head": "use Joomla\\CMS\\Factory;\r\nuse Joomla\\CMS\\Version;",
|
||||||
"composer": ""
|
"composer": ""
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user