mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2025-12-23 17:47:54 -05:00
Bugfix: constraint migration fixes (#4230)
- Refactored function names for PSR-12 compliance - Programmatically cascade delete attribute_link rows when a drop-down attribute is deleted but leave attribute_link rows associated with transactions. - Added `WHERE item_id IS NOT NULL` to migration to prevent failure on MySQL databases during migration - Retroactive correction of migration to prevent MySQL databases from failing. - Refactored generic functions to helper - Reverted attribute_links foreign key to ON DELETE RESTRICT which is required for a unique constraint on this table. Cascading deletes are now handled programmatically. - Migration Session table to match Code Igniter 4.6 - Add index to attribute_links to prevent query timeout in items view on large databases - Added overridePrefix() function to the migration_helper. Any time QueryBuilder is adding a prefix to the query when we don't want it to, this query can be used to override the prefix then set it back after you're done. - Added dropAllForeignKeyConstraints() helper function. - Added deleteIndex() helper function. - Added indexExists() helper function. - Added primaryKeyExists() helper function. - Added recreateForeignKeyConstraints() helper function. - Added CRUD section headings to the Attribute model. - Replaced `==` with `===` to prevent type juggling. - Removed unused delete_value function. - Reworked deleteDefinition() and deleteDefinitionList() functions to delete rows from the attribute_links table which are associated. - Added deleteAttributeLinksByDefinitionId() function Implement Cascading Delete - Function to delete attribute links with one or more attribute definitions. - Call function to implement an effective cascading delete. - Refactor function naming to meet PSR-12 conventions Fix Migration - Add drop of Generated Column to prevent failure of migration on MySQL databases. Fix Migration - Removed blank lines - Refactored function naming for PSR compliance - Reformatted code for PSR compliance - Added logic to drop dependent foreign key constraints before deleting an index then recreating them. Migrate ospos_sessions table - DROP and CREATE session table to prevent migration problems on populated databases Fixed Bug in Migration - In the event that item_id = null (e.g., it's a dropdown) it should not be included in the results. Fixed bug in Dropdown deletes - Removed delete_value function in Attributes Controller as it is unused. - Renamed postDelete_attribute_value function for PSR-12 compliance. - Renamed delete_value Attribute model function for PSR-12 compliance. - Refactored out function to getAttributeIdByValue - Replaced == with === to prevent type juggling - Reorganized parts of model to make it easier to find CRUD functions. Refactoring - PSR-12 Compliance formatting changes - Refactored several generic functions into the migration_helper.php - First check if primary key exists before attempting to create it. - Grouped functions together in migration_helper.php - phpdoc commenting functions Optimizing Indices - There are two queries run while opening the Items view which time out on large databases with weak hardware. These indices cut the query execution in half or better. Add Unique constraint back into attribute_links - This migration reverts ospos_attribute_links_ibfk_1 and 2 to ON DELETE RESTRICT. Cascade delete is done programmatically. This is needed to have a unique column on the attribute_links table which prevents duplicate attributes from begin created with the same item_id-attribute_id-definition_id combination Correct spacing after if for PSR-12 Minor code cleanup. - Removed Comments separating sections of code in Attribute model - Removed extra log line to prevent cluttering of the log
This commit is contained in:
@@ -12,4 +12,4 @@ trim_trailing_whitespace = true
|
||||
max_line_length = 120
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
trim_trailing_whitespace = false
|
||||
@@ -78,9 +78,9 @@ class Attributes extends Secure_Controller
|
||||
* @return void
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postDelete_attribute_value(): void
|
||||
public function postDeleteDropdownAttributeValue(): void
|
||||
{
|
||||
$success = $this->attribute->delete_value(
|
||||
$success = $this->attribute->deleteDropdownAttributeValue(
|
||||
html_entity_decode($this->request->getPost('attribute_value')),
|
||||
$this->request->getPost('definition_id', FILTER_SANITIZE_NUMBER_INT)
|
||||
);
|
||||
@@ -215,18 +215,6 @@ class Attributes extends Secure_Controller
|
||||
echo view('attributes/form', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX called function to delete an attribute value. This is called when a dropdown item is removed.
|
||||
*
|
||||
* @param string $attribute_value
|
||||
* @return bool
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function delete_value(string $attribute_value): bool
|
||||
{
|
||||
return $this->attribute->delete_value($attribute_value, NO_DEFINITION_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an attribute definition
|
||||
* @return void
|
||||
@@ -235,7 +223,7 @@ class Attributes extends Secure_Controller
|
||||
{
|
||||
$attributes_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
if ($this->attribute->delete_definition_list($attributes_to_delete)) {
|
||||
if($this->attribute->deleteDefinitionList($attributes_to_delete)) {
|
||||
$message = lang('Attributes.definition_successful_deleted') . ' ' . count($attributes_to_delete) . ' ' . lang('Attributes.definition_one_or_multiple');
|
||||
echo json_encode(['success' => true, 'message' => $message]);
|
||||
} else {
|
||||
|
||||
@@ -402,7 +402,7 @@ class Config extends Secure_Controller
|
||||
|
||||
$this->attribute->save_definition($definition_data, CATEGORY_DEFINITION_ID);
|
||||
} elseif ($batch_save_data['category_dropdown'] == NO_DEFINITION_ID) {
|
||||
$this->attribute->delete_definition(CATEGORY_DEFINITION_ID);
|
||||
$this->attribute->deleteDefinition(CATEGORY_DEFINITION_ID);
|
||||
}
|
||||
|
||||
$success = $this->appconfig->batch_save($batch_save_data);
|
||||
|
||||
@@ -35,6 +35,7 @@ class Migration_remove_duplicate_links extends Migration
|
||||
$builder->select('item_id, definition_id, attribute_id, COUNT(*) as count');
|
||||
$builder->where('sale_id', null);
|
||||
$builder->where('receiving_id', null);
|
||||
$builder->where('item_id IS NOT NULL');
|
||||
$builder->groupBy('item_id');
|
||||
$builder->groupBy('definition_id');
|
||||
$builder->groupBy('attribute_id');
|
||||
|
||||
@@ -12,10 +12,22 @@ class Migration_fix_keys_for_db_upgrade extends Migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$this->db->query("ALTER TABLE `ospos_tax_codes` MODIFY `deleted` tinyint(1) DEFAULT 0 NOT NULL;");
|
||||
helper('migration');
|
||||
|
||||
if (!$this->index_exists('ospos_customers', 'company_name')) {
|
||||
$this->db->query("ALTER TABLE `ospos_customers` ADD INDEX(`company_name`)");
|
||||
$forge = Database::forge();
|
||||
$fields = [
|
||||
'deleted' => [
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 0,
|
||||
'null' => false,
|
||||
],
|
||||
];
|
||||
$forge->modifyColumn('tax_codes', $fields);
|
||||
|
||||
if (!indexExists('customers', 'company_name')) {
|
||||
$forge->addKey('company_name', false, false, 'company_name');
|
||||
$forge->processIndexes('customers');
|
||||
}
|
||||
|
||||
$checkSql = "SELECT CONSTRAINT_NAME FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = DATABASE() AND TABLE_NAME = '" . $this->db->prefixTable('sales_items_taxes') . "' AND CONSTRAINT_NAME = 'ospos_sales_items_taxes_ibfk_1'";
|
||||
@@ -29,10 +41,9 @@ class Migration_fix_keys_for_db_upgrade extends Migration
|
||||
. ' ADD CONSTRAINT ospos_sales_items_taxes_ibfk_1 FOREIGN KEY (sale_id, item_id, line) '
|
||||
. ' REFERENCES ' . $this->db->prefixTable('sales_items') . ' (sale_id, item_id, line)');
|
||||
|
||||
|
||||
$this->create_primary_key('customers', 'person_id');
|
||||
$this->create_primary_key('employees', 'person_id');
|
||||
$this->create_primary_key('suppliers', 'person_id');
|
||||
createPrimaryKey('customers', 'person_id');
|
||||
createPrimaryKey('employees', 'person_id');
|
||||
createPrimaryKey('suppliers', 'person_id');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,31 +62,4 @@ class Migration_fix_keys_for_db_upgrade extends Migration
|
||||
. ' ADD CONSTRAINT ospos_sales_items_taxes_ibfk_1 FOREIGN KEY (sale_id) '
|
||||
. ' REFERENCES ' . $this->db->prefixTable('sales_items') . ' (sale_id)');
|
||||
}
|
||||
|
||||
private function create_primary_key(string $table, string $index): void
|
||||
{
|
||||
$result = $this->db->query('SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name= \'' . $this->db->getPrefix() . "$table' AND column_key = '$index'");
|
||||
|
||||
if (! $result->getRowArray()) {
|
||||
$this->delete_index($table, $index);
|
||||
$forge = Database::forge();
|
||||
$forge->addPrimaryKey($table, '');
|
||||
}
|
||||
}
|
||||
|
||||
private function index_exists(string $table, string $index): bool
|
||||
{
|
||||
$result = $this->db->query('SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = \'' . $this->db->getPrefix() . "$table' AND index_name = '$index'");
|
||||
$row_array = $result->getRowArray();
|
||||
return $row_array && $row_array['COUNT(*)'] > 0;
|
||||
}
|
||||
|
||||
private function delete_index(string $table, string $index): void
|
||||
{
|
||||
|
||||
if ($this->index_exists($table, $index)) {
|
||||
$forge = Database::forge();
|
||||
$forge->dropKey($table, $index, FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class fix_duplicate_attributes extends Migration
|
||||
'ospos_attribute_links_ibfk_5'
|
||||
];
|
||||
|
||||
drop_foreign_key_constraints($foreignKeys, 'ospos_attribute_links');
|
||||
dropForeignKeyConstraints($foreignKeys, 'attribute_links');
|
||||
|
||||
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.4.0_attribute_links_unique_constraint.sql');
|
||||
}
|
||||
|
||||
@@ -14,7 +14,12 @@ class Migration_Attributes_fix_cascading_delete extends Migration
|
||||
public function up(): void
|
||||
{
|
||||
helper('migration');
|
||||
drop_foreign_key_constraints(['ospos_attribute_links_ibfk_1', 'ospos_attribute_links_ibfk_2'], 'ospos_attribute_links');
|
||||
|
||||
$this->db->query("ALTER TABLE `ospos_attribute_links` DROP INDEX `attribute_links_uq3`");
|
||||
$this->db->query("ALTER TABLE `ospos_attribute_links` DROP COLUMN `generated_unique_column`");
|
||||
|
||||
dropForeignKeyConstraints(['ospos_attribute_links_ibfk_1', 'ospos_attribute_links_ibfk_2'], 'attribute_links');
|
||||
|
||||
$this->db->query("ALTER TABLE `ospos_attribute_links` ADD CONSTRAINT `ospos_attribute_links_ibfk_1` FOREIGN KEY (`definition_id`) REFERENCES `ospos_attribute_definitions` (`definition_id`) ON DELETE CASCADE;");
|
||||
$this->db->query("ALTER TABLE `ospos_attribute_links` ADD CONSTRAINT `ospos_attribute_links_ibfk_2` FOREIGN KEY (`attribute_id`) REFERENCES `ospos_attribute_values` (`attribute_id`) ON DELETE CASCADE;");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class Migration_sessions_migration extends Migration
|
||||
{
|
||||
/**
|
||||
* Perform a migration step.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
error_log('Migrating sessions table');
|
||||
|
||||
helper('migration');
|
||||
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.4.1_migrate_sessions_table.sql');
|
||||
|
||||
error_log('Migrating sessions table');
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert a migration step.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Config\Database;
|
||||
|
||||
class MigrationOptimizationIndices extends Migration
|
||||
{
|
||||
/**
|
||||
* Perform a migration step.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
error_log('Migrating Optimization Indices');
|
||||
|
||||
helper('migration');
|
||||
$forge = Database::forge();
|
||||
|
||||
if (!indexExists('attribute_links', 'attribute_links_uq2')) {
|
||||
$columns = [
|
||||
'item_id',
|
||||
'receiving_id',
|
||||
'sale_id',
|
||||
'definition_id',
|
||||
'attribute_id'
|
||||
];
|
||||
$forge->addKey($columns, false, true, 'attribute_links_uq2');
|
||||
$forge->processIndexes('attribute_links');
|
||||
}
|
||||
|
||||
if (!indexExists('inventory', 'trans_items_trans_date')) {
|
||||
$forge->addKey(['trans_items', 'trans_date'], false, false, 'trans_items_trans_date');
|
||||
$forge->processIndexes('inventory');
|
||||
}
|
||||
|
||||
error_log('Migrating Optimization Indices');
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert a migration step.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Config\Database;
|
||||
|
||||
class AttributeLinksUniqueConstraint extends Migration
|
||||
{
|
||||
/**
|
||||
* Perform a migration step.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
error_log('Migrating attribute_links unique constraint started');
|
||||
|
||||
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.4.1_attribute_links_unique_constraint.sql');
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert a migration step.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
ALTER TABLE `ospos_attribute_links` DROP CONSTRAINT `ospos_attribute_links_ibfk_1`;
|
||||
ALTER TABLE `ospos_attribute_links` DROP CONSTRAINT `ospos_attribute_links_ibfk_2`;
|
||||
|
||||
# Prevents duplicate attribute links with the same definition_id and item_id.
|
||||
# This accounts for dropdown rows (null item_id) and rows associated with sales or receivings.
|
||||
ALTER TABLE `ospos_attribute_links`
|
||||
ADD COLUMN `generated_unique_column` VARCHAR(255) GENERATED ALWAYS AS (
|
||||
CASE
|
||||
WHEN `sale_id` IS NULL AND `receiving_id` IS NULL AND `item_id` IS NOT NULL THEN CONCAT(`definition_id`, '-', `item_id`)
|
||||
ELSE NULL
|
||||
END
|
||||
) STORED,
|
||||
ADD UNIQUE INDEX `attribute_links_uq3` (`generated_unique_column`);
|
||||
|
||||
ALTER TABLE `ospos_attribute_links` ADD CONSTRAINT `ospos_attribute_links_ibfk_1` FOREIGN KEY (`definition_id`) REFERENCES `ospos_attribute_definitions` (`definition_id`) ON DELETE RESTRICT;
|
||||
ALTER TABLE `ospos_attribute_links` ADD CONSTRAINT `ospos_attribute_links_ibfk_2` FOREIGN KEY (`attribute_id`) REFERENCES `ospos_attribute_values` (`attribute_id`) ON DELETE RESTRICT;
|
||||
@@ -0,0 +1,10 @@
|
||||
DROP TABLE IF EXISTS `ospos_sessions`;
|
||||
CREATE TABLE IF NOT EXISTS `ospos_sessions` (
|
||||
`id` varchar(128) NOT null,
|
||||
`ip_address` varchar(45) NOT null,
|
||||
`timestamp` timestamp DEFAULT CURRENT_TIMESTAMP NOT null,
|
||||
`data` blob NOT null,
|
||||
KEY `ospos_sessions_timestamp` (`timestamp`)
|
||||
);
|
||||
|
||||
ALTER TABLE ospos_sessions ADD PRIMARY KEY (id, ip_address);
|
||||
@@ -30,32 +30,178 @@ function execute_script(string $path): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the foreign key constraints from the attribute_links table.
|
||||
* Drops provided foreign key constraints from given table.
|
||||
* This is required to successfully create the generated unique constraint.
|
||||
*
|
||||
* @param array $foreignKeys names of the foreign key constraints to drop
|
||||
* @param string $table name of the table the foreign key constraints are on
|
||||
* @return void
|
||||
*/
|
||||
function drop_foreign_key_constraints(array $foreignKeys, string $table): void
|
||||
function dropForeignKeyConstraints(array $foreignKeys, string $table): void
|
||||
{
|
||||
$db = Database::connect();
|
||||
|
||||
$current_prefix = $db->getPrefix();
|
||||
$db->setPrefix('');
|
||||
$database_name = $db->database;
|
||||
$forge = Database::forge();
|
||||
|
||||
foreach ($foreignKeys as $fk) {
|
||||
$builder = $db->table('INFORMATION_SCHEMA.TABLE_CONSTRAINTS');
|
||||
$builder->select('CONSTRAINT_NAME');
|
||||
$builder->where('TABLE_SCHEMA', $database_name);
|
||||
$builder->where('TABLE_NAME', $table);
|
||||
$builder->where('CONSTRAINT_TYPE', 'FOREIGN KEY');
|
||||
$builder->where('CONSTRAINT_NAME', $fk);
|
||||
$query = $builder->get();
|
||||
if(foreignKeyExists($fk, $table)) {
|
||||
$forge->dropForeignKey($table, $fk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($query->getNumRows() > 0) {
|
||||
$db->query("ALTER TABLE `$table` DROP FOREIGN KEY `$fk`");
|
||||
|
||||
/**
|
||||
* Removes the database prefix from the current database connection.
|
||||
* TODO: This function should be moved to a more global location since it may be needed outside of migrations.
|
||||
* @return string The prefix before overriding.
|
||||
*/
|
||||
function overridePrefix(string $prefix = ''): string {
|
||||
$db = Database::connect();
|
||||
|
||||
$originalPrefix = $db->getPrefix();
|
||||
$db->setPrefix($prefix);
|
||||
|
||||
return $originalPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a primary key on the specified table and index column.
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $index
|
||||
* @return void
|
||||
*/
|
||||
function createPrimaryKey(string $table, string $index): void {
|
||||
if (! primaryKeyExists($table)) {
|
||||
$constraints = dropAllForeignKeyConstraints($table, $index);
|
||||
deleteIndex($table, $index);
|
||||
$forge = Database::forge();
|
||||
$forge->addPrimaryKey($index,'PRIMARY');
|
||||
$forge->processIndexes($table);
|
||||
recreateForeignKeyConstraints($constraints);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops all foreign key constraints that reference the provided table and column.
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $column
|
||||
* @return array containing the deleted constraints in case they need to be recreated after.
|
||||
*/
|
||||
|
||||
function dropAllForeignKeyConstraints(string $table, string $column): array {
|
||||
$db = Database::connect();
|
||||
$result = $db->query("
|
||||
SELECT DISTINCT
|
||||
kcu.CONSTRAINT_NAME,
|
||||
kcu.TABLE_NAME,
|
||||
kcu.COLUMN_NAME,
|
||||
kcu.REFERENCED_TABLE_NAME,
|
||||
kcu.REFERENCED_COLUMN_NAME,
|
||||
rc.DELETE_RULE,
|
||||
rc.UPDATE_RULE
|
||||
FROM information_schema.KEY_COLUMN_USAGE kcu
|
||||
LEFT JOIN information_schema.REFERENTIAL_CONSTRAINTS rc
|
||||
ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
|
||||
AND kcu.TABLE_NAME = rc.TABLE_NAME
|
||||
WHERE kcu.TABLE_SCHEMA = DATABASE()
|
||||
AND ((kcu.REFERENCED_TABLE_NAME = '" . $db->getPrefix() . "$table' AND kcu.REFERENCED_COLUMN_NAME = '$column')
|
||||
OR (kcu.TABLE_NAME = '" . $db->getPrefix() . "$table' AND kcu.COLUMN_NAME = '$column'))
|
||||
");
|
||||
|
||||
$deletedConstraints = [];
|
||||
|
||||
foreach ($result->getResultArray() as $constraint) {
|
||||
$deletedConstraints[] = [
|
||||
'constraintName' => $constraint['CONSTRAINT_NAME'],
|
||||
'tableName' => str_replace($db->DBPrefix, '', $constraint['TABLE_NAME']),
|
||||
'columnName' => $constraint['COLUMN_NAME'],
|
||||
'referencedTable' => str_replace($db->DBPrefix, '', $constraint['REFERENCED_TABLE_NAME']),
|
||||
'referencedColumn' => $constraint['REFERENCED_COLUMN_NAME'],
|
||||
'onDelete' => $constraint['DELETE_RULE'],
|
||||
'onUpdate' => $constraint['UPDATE_RULE'],
|
||||
];
|
||||
}
|
||||
|
||||
if ($deletedConstraints) {
|
||||
$forge = Database::forge();
|
||||
foreach ($deletedConstraints as $foreignKey) {
|
||||
$forge->dropForeignKey($foreignKey['tableName'], $foreignKey['constraintName']);
|
||||
}
|
||||
}
|
||||
|
||||
$db->setPrefix($current_prefix);
|
||||
return $deletedConstraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the specified index from the specified table.
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $index
|
||||
* @return void
|
||||
*/
|
||||
function deleteIndex(string $table, string $index): void {
|
||||
if (indexExists($table, $index)) {
|
||||
$forge = Database::forge();
|
||||
$forge->dropKey($table, $index, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified index exists on the specified table.
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $index
|
||||
* @return bool
|
||||
*/
|
||||
function indexExists(string $table, string $index): bool {
|
||||
$db = Database::connect();
|
||||
$result = $db->query('SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = \'' . $db->getPrefix() . "$table' AND index_name = '$index'");
|
||||
$row_array = $result->getRowArray();
|
||||
|
||||
return $row_array && $row_array['COUNT(*)'] > 0;
|
||||
}
|
||||
|
||||
function primaryKeyExists(string $table): bool {
|
||||
$db = Database::connect();
|
||||
$result = $db->query('SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_schema = DATABASE() AND table_name = \'' . $db->getPrefix() . "$table' AND constraint_type = 'PRIMARY KEY'");
|
||||
$row_array = $result->getRowArray();
|
||||
|
||||
return $row_array && $row_array['COUNT(*)'] > 0;
|
||||
}
|
||||
|
||||
function recreateForeignKeyConstraints(array $constraints): void {
|
||||
if ($constraints) {
|
||||
$forge = Database::forge();
|
||||
foreach ($constraints as $constraint) {
|
||||
$forge->addForeignKey($constraint['columnName'], $constraint['referencedTable'], $constraint['referencedColumn'], $constraint['onUpdate'], $constraint['onDelete'], $constraint['constraintName']);
|
||||
$forge->processIndexes($constraint['tableName']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a foreign key constraint exists in the specified table.
|
||||
*
|
||||
* @param string $constraintName
|
||||
* @param string $tableName
|
||||
* @return bool true when the constraint exists, false otherwise.
|
||||
*/
|
||||
function foreignKeyExists(string $constraintName, string $tableName): bool {
|
||||
|
||||
$prefix = overridePrefix();
|
||||
|
||||
$db = Database::connect();
|
||||
$builder = $db->table('INFORMATION_SCHEMA.TABLE_CONSTRAINTS');
|
||||
$builder->select('CONSTRAINT_NAME');
|
||||
$builder->where('TABLE_SCHEMA', $db->database);
|
||||
$builder->where('TABLE_NAME', $prefix . $tableName);
|
||||
$builder->where('CONSTRAINT_TYPE', 'FOREIGN KEY');
|
||||
$builder->where('CONSTRAINT_NAME', $constraintName);
|
||||
$query = $builder->get();
|
||||
|
||||
overridePrefix($prefix);
|
||||
|
||||
return $query->getNumRows() > 0;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,21 @@ class Attribute extends Model
|
||||
public const SHOW_IN_ITEMS = 1; // TODO: These need to be moved to constants.php
|
||||
public const SHOW_IN_SALES = 2;
|
||||
public const SHOW_IN_RECEIVINGS = 4;
|
||||
public function deleteDropdownAttributeValue(string $attribute_value, int $definition_id): bool
|
||||
{
|
||||
$attribute_id = $this->getAttributeIdByValue($attribute_value);
|
||||
$this->deleteAttributeLinksByDefinitionIdAndAttributeId($definition_id, $attribute_id);
|
||||
|
||||
//Delete attribute value if not linked other attributes
|
||||
$subQuery = $this->db->table('attribute_links');
|
||||
$subQuery->select('attribute_id');
|
||||
|
||||
$builder = $this->db->table('attribute_values');
|
||||
$builder->where('attribute_value', $attribute_value);
|
||||
$builder->whereNotIn('attribute_id', $subQuery);
|
||||
|
||||
return $builder->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
@@ -60,7 +75,7 @@ class Attribute extends Model
|
||||
$builder->where('definition_id', $definition_id);
|
||||
$builder->where('deleted', $deleted);
|
||||
|
||||
return ($builder->get()->getNumRows() == 1); // TODO: ===
|
||||
return ($builder->get()->getNumRows() === 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -842,29 +857,16 @@ class Attribute extends Model
|
||||
return $attribute_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute_value
|
||||
* @param int $definition_id
|
||||
* @return bool
|
||||
*/
|
||||
public function delete_value(string $attribute_value, int $definition_id): bool
|
||||
{
|
||||
// QueryBuilder does not support multi-table delete.
|
||||
$query = 'DELETE atrv, atrl ';
|
||||
$query .= 'FROM ' . $this->db->prefixTable('attribute_values') . ' atrv, ' . $this->db->prefixTable('attribute_links') . ' atrl ';
|
||||
$query .= 'WHERE atrl.attribute_id = atrv.attribute_id AND atrv.attribute_value = ' . $this->db->escape($attribute_value);
|
||||
$query .= ' AND atrl.definition_id = ' . $this->db->escape($definition_id);
|
||||
return $this->db->query($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an Attribute definition from the database and associated column in the items_import.csv
|
||||
*
|
||||
* @param int $definition_id Attribute definition ID to remove.
|
||||
* @return boolean true if successful and false if there is a failure
|
||||
*/
|
||||
public function delete_definition(int $definition_id): bool
|
||||
public function deleteDefinition(int $definition_id): bool
|
||||
{
|
||||
$this->deleteAttributeLinksByDefinitionId($definition_id);
|
||||
|
||||
$builder = $this->db->table('attribute_definitions');
|
||||
$builder->where('definition_id', $definition_id);
|
||||
|
||||
@@ -875,14 +877,33 @@ class Attribute extends Model
|
||||
* @param array $definition_ids
|
||||
* @return bool
|
||||
*/
|
||||
public function delete_definition_list(array $definition_ids): bool
|
||||
public function deleteDefinitionList(array $definition_ids): bool
|
||||
{
|
||||
$this->deleteAttributeLinksByDefinitionId($definition_ids);
|
||||
|
||||
$builder = $this->db->table('attribute_definitions');
|
||||
$builder->whereIn('definition_id', $definition_ids);
|
||||
|
||||
return $builder->update(['deleted' => DELETED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes attribute links by definition ID
|
||||
*
|
||||
* @param int|array $definition_id
|
||||
*/
|
||||
public function deleteAttributeLinksByDefinitionId(int|array $definition_id): void
|
||||
{
|
||||
if(!is_array($definition_id))
|
||||
{
|
||||
$definition_id = [$definition_id];
|
||||
}
|
||||
|
||||
$builder = $this->db->table('attribute_links');
|
||||
$builder->whereIn('definition_id', $definition_id);
|
||||
$builder->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes any attribute_links for a specific definition that do not have an item_id associated with them and are not DROPDOWN types
|
||||
*
|
||||
@@ -987,4 +1008,34 @@ class Attribute extends Model
|
||||
|
||||
return $builder->get()->getResultArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute_value
|
||||
* @return int
|
||||
*/
|
||||
private function getAttributeIdByValue(string $attribute_value): int
|
||||
{
|
||||
$builder = $this->db->table('attribute_values');
|
||||
$builder->select('attribute_id');
|
||||
$builder->where('attribute_value', $attribute_value);
|
||||
return $builder->get()->getRow('attribute_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes Attribute Links associated with a specific definition ID and attribute ID.
|
||||
* Does not delete rows where sale_id or receiving_id has a value to retain records.
|
||||
*
|
||||
* @param int $definitionId
|
||||
* @param int $attributeId
|
||||
* @return \CodeIgniter\Database\BaseBuilder
|
||||
*/
|
||||
private function deleteAttributeLinksByDefinitionIdAndAttributeId(int $definitionId, int $attributeId): void
|
||||
{
|
||||
$builder = $this->db->table('attribute_links');
|
||||
$builder->where('sale_id', null);
|
||||
$builder->where('receiving_id', null);
|
||||
$builder->where('definition_id', $definitionId);
|
||||
$builder->where('attribute_id', $attributeId);
|
||||
$builder->delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
if (is_new) {
|
||||
values.splice($.inArray(value, values), 1);
|
||||
} else {
|
||||
$.post('<?= esc("$controller_name/delete_attribute_value/") ?>', {
|
||||
$.post('<?= esc("$controller_name/DeleteDropdownAttributeValue/") ?>', {
|
||||
definition_id: definition_id,
|
||||
attribute_value: value
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user