delete_orphaned_values(); $this->migrate_duplicate_attribute_values(DECIMAL); $this->migrate_duplicate_attribute_values(DATE); // Select all attributes that have data in more than one column $builder = $this->db->table('attribute_values'); $builder->select('attribute_id, attribute_value, attribute_decimal, attribute_date'); $builder->groupStart(); $builder->where('attribute_value IS NOT NULL'); $builder->where('attribute_date IS NOT NULL'); $builder->groupEnd(); $builder->orGroupStart(); $builder->where('attribute_value IS NOT NULL'); $builder->where('attribute_decimal IS NOT NULL'); $builder->groupEnd(); $attribute_values = $builder->get(); $this->db->transStart(); // Clean up Attribute values table where there is an attribute value and an attribute_date/attribute_decimal foreach ($attribute_values->getResultArray() as $attribute_value) { $builder = $this->db->table('attribute_values'); $builder->delete(['attribute_id' => $attribute_value['attribute_id']]); $builder = $this->db->table('attribute_links'); $builder->select('links.definition_id, links.item_id, links.attribute_id, defs.definition_type'); $builder->join('attribute_definitions defs', 'defs.definition_id = links.definition_id'); $builder->where('attribute_id', $attribute_value['attribute_id']); $attribute_links = $builder->get(); if ($attribute_links) { $builder = $this->db->table('attribute_links'); $attribute_links = $attribute_links->getResultArray() ?: []; foreach ($attribute_links->getResultArray() as $attribute_link) { $builder->where('attribute_id', $attribute_link['attribute_id']); $builder->where('item_id', $attribute_link['item_id']); $builder->delete(); switch ($attribute_link['definition_type']) { case DECIMAL: $value = $attribute_value['attribute_decimal']; break; case DATE: $config = config(OSPOS::class)->settings; $attribute_date = DateTime::createFromFormat('Y-m-d', $attribute_value['attribute_date']); $value = $attribute_date->format($config['dateformat']); break; default: $value = $attribute_value['attribute_value']; break; } $attribute->saveAttributeValue($value, $attribute_link['definition_id'], $attribute_link['item_id'], false, $attribute_link['definition_type']); } } } $this->db->transComplete(); helper('migration'); execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.4.0_database_optimizations.sql'); error_log('Migrating database_optimizations completed'); } /** * Given the type of attribute, deletes any duplicates it finds in the attribute_values table and reassigns those */ private function migrate_duplicate_attribute_values($attribute_type): void { // Remove duplicate attribute values needed to make attribute_decimals and attribute_dates unique $this->db->transStart(); $column = 'attribute_' . strtolower($attribute_type); $builder = $this->db->table('attribute_values'); $builder->select("$column"); $builder->groupBy($column); $builder->having("COUNT($column) > 1"); $duplicated_values = $builder->get(); foreach ($duplicated_values->getResultArray() as $duplicated_value) { $subquery_builder = $this->db->table('attribute_values'); $subquery_builder->select('attribute_id'); $subquery_builder->where($column, $duplicated_value[$column]); $subquery = $subquery_builder->getCompiledSelect(); $builder = $this->db->table('attribute_values'); $builder->select('attribute_id'); $builder->where($column, $duplicated_value[$column]); $builder->where("attribute_id IN ($subquery)", null, false); $attribute_ids_to_fix = $builder->get(); $this->reassign_duplicate_attribute_values($attribute_ids_to_fix, $duplicated_value); } $this->db->transComplete(); } /** * Updates the attribute_id in all attribute_link rows with duplicated attribute_ids then deletes unneeded rows from attribute_values * * @param ResultInterface $attribute_ids_to_fix All attribute_ids that need to parsed * @param array $attribute_value The attribute value in question. */ private function reassign_duplicate_attribute_values(ResultInterface $attribute_ids_to_fix, array $attribute_value): void { $attribute_ids = $attribute_ids_to_fix->getResultArray(); $retain_attribute_id = $attribute_ids[0]['attribute_id']; foreach ($attribute_ids as $attribute_id) { // Update attribute_link with the attribute_id we are keeping $builder = $this->db->table('attribute_links'); $builder->where('attribute_id', $attribute_id['attribute_id']); $builder->update(['attribute_id' => $retain_attribute_id]); // Delete the row from attribute_values if it isn't our keeper if ($attribute_id['attribute_id'] !== $retain_attribute_id) { $builder = $this->db->table('attribute_values'); $builder->delete(['attribute_id' => $attribute_id['attribute_id']]); } } } /** * Revert a migration step. */ public function down(): void {} }