fix school year for all tables
Tests / PHPUnit (push) Failing after 1m20s

This commit is contained in:
root
2026-07-18 14:45:38 -04:00
parent 4db8334a3c
commit 716cc8b8d3
125 changed files with 2315 additions and 81 deletions
@@ -8,6 +8,10 @@ class AddEventCategoryToEvents extends Migration
{
public function up()
{
if (! $this->db->tableExists('events')) {
return;
}
$fields = $this->db->getFieldNames('events');
if (!in_array('event_category', $fields, true)) {
@@ -25,6 +29,10 @@ class AddEventCategoryToEvents extends Migration
public function down()
{
if (! $this->db->tableExists('events')) {
return;
}
$fields = $this->db->getFieldNames('events');
if (in_array('event_category', $fields, true)) {
@@ -8,6 +8,10 @@ class AddBatchNumberToReimbursements extends Migration
{
public function up()
{
if (! $this->db->tableExists('reimbursements') || $this->db->fieldExists('batch_number', 'reimbursements')) {
return;
}
$fields = [
'batch_number' => [
'type' => 'INT',
@@ -24,7 +28,10 @@ class AddBatchNumberToReimbursements extends Migration
public function down()
{
if (! $this->db->tableExists('reimbursements') || ! $this->db->fieldExists('batch_number', 'reimbursements')) {
return;
}
$this->forge->dropColumn('reimbursements', 'batch_number');
}
}
@@ -8,6 +8,10 @@ class AddYearlyBatchNumberToReimbursementBatches extends Migration
{
public function up()
{
if (! $this->db->tableExists('reimbursement_batches') || $this->db->fieldExists('yearly_batch_number', 'reimbursement_batches')) {
return;
}
$fields = [
'yearly_batch_number' => [
'type' => 'INT',
@@ -26,6 +30,10 @@ class AddYearlyBatchNumberToReimbursementBatches extends Migration
public function down()
{
if (! $this->db->tableExists('reimbursement_batches') || ! $this->db->fieldExists('yearly_batch_number', 'reimbursement_batches')) {
return;
}
$this->forge->dropColumn('reimbursement_batches', 'yearly_batch_number');
}
@@ -24,7 +24,9 @@ class CreateStaffAttendance extends Migration
]);
$this->forge->addKey('id', true);
$this->forge->addUniqueKey(['user_id','date','semester','school_year']);
$this->forge->addForeignKey('user_id','users','id','CASCADE','CASCADE');
if ($this->db->tableExists('users')) {
$this->forge->addForeignKey('user_id','users','id','CASCADE','CASCADE');
}
$this->forge->createTable('staff_attendance', true);
}
@@ -43,7 +43,9 @@ class CreateFamiliesTables extends Migration
$this->forge->addKey('id', true);
$this->forge->addUniqueKey(['family_id', 'student_id']);
$this->forge->addForeignKey('family_id', 'families', 'id', 'CASCADE', 'CASCADE');
$this->forge->addForeignKey('student_id', 'students', 'id', 'CASCADE', 'CASCADE');
if ($this->db->tableExists('students')) {
$this->forge->addForeignKey('student_id', 'students', 'id', 'CASCADE', 'CASCADE');
}
$this->forge->createTable('family_students', true);
// family_guardians
@@ -62,7 +64,9 @@ class CreateFamiliesTables extends Migration
$this->forge->addKey('id', true);
$this->forge->addUniqueKey(['family_id', 'user_id']);
$this->forge->addForeignKey('family_id', 'families', 'id', 'CASCADE', 'CASCADE');
$this->forge->addForeignKey('user_id', 'users', 'id', 'CASCADE', 'CASCADE');
if ($this->db->tableExists('users')) {
$this->forge->addForeignKey('user_id', 'users', 'id', 'CASCADE', 'CASCADE');
}
$this->forge->createTable('family_guardians', true);
// family_comm_prefs
@@ -90,4 +94,3 @@ class CreateFamiliesTables extends Migration
$this->forge->dropTable('families', true);
}
}
@@ -9,6 +9,10 @@ class AddNoSchoolToCalendarEvents extends Migration
public function up()
{
$db = \Config\Database::connect();
if (! $db->tableExists('calendar_events')) {
return;
}
$cols = [];
try {
$cols = $db->getFieldNames('calendar_events');
@@ -31,6 +35,10 @@ class AddNoSchoolToCalendarEvents extends Migration
public function down()
{
if (! $this->db->tableExists('calendar_events')) {
return;
}
try {
$this->forge->dropColumn('calendar_events', 'no_school');
} catch (\Throwable $e) {
@@ -38,4 +46,3 @@ class AddNoSchoolToCalendarEvents extends Migration
}
}
}
@@ -85,8 +85,12 @@ class CreateParentAttendanceReports extends Migration
$this->forge->addKey('id', true);
$this->forge->addKey(['report_date']);
$this->forge->addKey(['student_id', 'report_date']);
$this->forge->addForeignKey('parent_id', 'users', 'id', 'CASCADE', 'CASCADE');
$this->forge->addForeignKey('student_id', 'students', 'id', 'CASCADE', 'CASCADE');
if ($this->db->tableExists('users')) {
$this->forge->addForeignKey('parent_id', 'users', 'id', 'CASCADE', 'CASCADE');
}
if ($this->db->tableExists('students')) {
$this->forge->addForeignKey('student_id', 'students', 'id', 'CASCADE', 'CASCADE');
}
$this->forge->createTable('parent_attendance_reports', true);
}
@@ -97,4 +101,3 @@ class CreateParentAttendanceReports extends Migration
}
}
}
@@ -11,6 +11,10 @@ class AddDonationCategoryToExpenses extends Migration
*/
public function up()
{
if (! $this->db->tableExists('expenses')) {
return;
}
$this->forge->modifyColumn('expenses', [
'category' => [
'type' => "ENUM('Expense','Purchase','Reimbursement','Donation')",
@@ -24,6 +28,10 @@ class AddDonationCategoryToExpenses extends Migration
*/
public function down()
{
if (! $this->db->tableExists('expenses')) {
return;
}
// Convert any donation rows to Expense to keep rollback safe.
$this->db->table('expenses')
->where('category', 'Donation')
@@ -61,9 +61,13 @@ class CreatePrintRequests extends Migration
],
]);
$this->forge->addKey('id', true);
$this->forge->addForeignKey('teacher_id', 'users', 'id', 'CASCADE', 'CASCADE');
$this->forge->addForeignKey('admin_id', 'users', 'id', 'CASCADE', 'SET NULL');
$this->forge->addForeignKey('class_id', 'classes', 'id', 'CASCADE', 'CASCADE');
if ($this->db->tableExists('users')) {
$this->forge->addForeignKey('teacher_id', 'users', 'id', 'CASCADE', 'CASCADE');
$this->forge->addForeignKey('admin_id', 'users', 'id', 'CASCADE', 'SET NULL');
}
if ($this->db->tableExists('classes')) {
$this->forge->addForeignKey('class_id', 'classes', 'id', 'CASCADE', 'CASCADE');
}
$this->forge->createTable('print_requests');
}
@@ -8,8 +8,16 @@ class FixPrintRequestsForeignKey extends Migration
{
public function up()
{
if (! $this->db->tableExists('print_requests')) {
return;
}
// Drop the old foreign key if it exists
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
if (! $this->db->tableExists('class_sections')) {
return;
@@ -24,8 +32,20 @@ class FixPrintRequestsForeignKey extends Migration
public function down()
{
if (! $this->db->tableExists('print_requests')) {
return;
}
// Drop the new foreign key
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
if (! $this->db->tableExists('classes')) {
return;
}
// Re-add the old foreign key
$this->forge->addForeignKey('class_id', 'classes', 'id', 'CASCADE', 'CASCADE');
@@ -8,16 +8,28 @@ class RevertPrintRequestsForeignKey extends Migration
{
public function up()
{
if (! $this->db->tableExists('print_requests')) {
return;
}
// Drop the old foreign key if it exists
$db = \Config\Database::connect();
$keys = $db->getForeignKeyData('print_requests');
foreach ($keys as $key) {
if ($key->constraint_name === 'print_requests_class_id_foreign') {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
break;
}
}
if (! $this->db->tableExists('classes')) {
return;
}
// Add the new foreign key
$this->forge->addForeignKey('class_id', 'classes', 'id', 'CASCADE', 'CASCADE');
@@ -27,8 +39,20 @@ class RevertPrintRequestsForeignKey extends Migration
public function down()
{
if (! $this->db->tableExists('print_requests')) {
return;
}
// Drop the new foreign key
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
if (! $this->db->tableExists('class_sections')) {
return;
}
// Re-add the old foreign key
$this->forge->addForeignKey('class_id', 'class_sections', 'id', 'CASCADE', 'CASCADE');
@@ -8,12 +8,24 @@ class DropPrintRequestsForeignKey extends Migration
{
public function up()
{
if (! $this->db->tableExists('print_requests')) {
return;
}
// Drop the old foreign key if it exists
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
}
public function down()
{
if (! $this->db->tableExists('print_requests') || ! $this->db->tableExists('class_sections')) {
return;
}
// Re-add the old foreign key
$this->forge->addForeignKey('class_id', 'class_sections', 'id', 'CASCADE', 'CASCADE');
@@ -8,21 +8,41 @@ class FixPrintRequestsForeignKeyOnceAndForAll extends Migration
{
public function up()
{
if (! $this->db->tableExists('print_requests')) {
return;
}
$db = \Config\Database::connect();
$keys = $db->getForeignKeyData('print_requests');
foreach ($keys as $key) {
if ($key->constraint_name === 'print_requests_class_id_foreign') {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
break;
}
}
if (! $this->db->tableExists('classSection')) {
return;
}
$this->forge->addForeignKey('class_id', 'classSection', 'id', 'CASCADE', 'CASCADE');
$this->forge->processIndexes('print_requests');
}
public function down()
{
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
if (! $this->db->tableExists('print_requests')) {
return;
}
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
}
}
@@ -8,6 +8,10 @@ class FixPrintRequestsForeignKeyAgain extends Migration
{
public function up()
{
if (! $this->db->tableExists('print_requests') || ! $this->db->tableExists('classSection')) {
return;
}
$db = \Config\Database::connect();
$keys = $db->getForeignKeyData('print_requests');
foreach ($keys as $key) {
@@ -22,6 +26,14 @@ class FixPrintRequestsForeignKeyAgain extends Migration
public function down()
{
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
if (! $this->db->tableExists('print_requests')) {
return;
}
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
}
}
@@ -9,6 +9,9 @@ class AddEventTypeToCalendarEvents extends Migration
public function up()
{
$db = \Config\Database::connect();
if (! $db->tableExists('calendar_events')) {
return;
}
if (!$db->fieldExists('event_type', 'calendar_events')) {
$this->forge->addColumn('calendar_events', [
@@ -26,6 +29,10 @@ class AddEventTypeToCalendarEvents extends Migration
public function down()
{
$db = \Config\Database::connect();
if (! $db->tableExists('calendar_events')) {
return;
}
if ($db->fieldExists('event_type', 'calendar_events')) {
$this->forge->dropColumn('calendar_events', 'event_type');
}
@@ -8,6 +8,10 @@ class AddEventPaidFlagToCharges extends Migration
{
public function up()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
$fields = [
'event_paid' => [
'type' => 'TINYINT',
@@ -17,11 +21,15 @@ class AddEventPaidFlagToCharges extends Migration
],
];
$this->forge->addColumn('event_charges', $fields);
if (! $this->db->fieldExists('event_paid', 'event_charges')) {
$this->forge->addColumn('event_charges', $fields);
}
}
public function down()
{
$this->forge->dropColumn('event_charges', 'event_paid');
if ($this->db->tableExists('event_charges') && $this->db->fieldExists('event_paid', 'event_charges')) {
$this->forge->dropColumn('event_charges', 'event_paid');
}
}
}
@@ -8,6 +8,10 @@ class AddClassSectionToEventCharges extends Migration
{
public function up()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
$fields = [
'class_section_id' => [
'type' => 'INT',
@@ -25,7 +29,7 @@ class AddClassSectionToEventCharges extends Migration
public function down()
{
if ($this->db->fieldExists('class_section_id', 'event_charges')) {
if ($this->db->tableExists('event_charges') && $this->db->fieldExists('class_section_id', 'event_charges')) {
$this->forge->dropColumn('event_charges', 'class_section_id');
}
}
@@ -8,6 +8,10 @@ class AddEventPaymentRefToEventCharges extends Migration
{
public function up()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
$fields = [
'event_payment_id' => [
'type' => 'INT',
@@ -25,7 +29,7 @@ class AddEventPaymentRefToEventCharges extends Migration
public function down()
{
if ($this->db->fieldExists('event_payment_id', 'event_charges')) {
if ($this->db->tableExists('event_charges') && $this->db->fieldExists('event_payment_id', 'event_charges')) {
$this->forge->dropColumn('event_charges', 'event_payment_id');
}
}
@@ -8,6 +8,10 @@ class AddExternalParticipantFieldsToEventCharges extends Migration
{
public function up()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
$fields = [
'external_firstname' => [
'type' => 'VARCHAR',
@@ -34,6 +38,10 @@ class AddExternalParticipantFieldsToEventCharges extends Migration
public function down()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
foreach (['external_firstname', 'external_lastname', 'external_note'] as $field) {
if ($this->db->fieldExists($field, 'event_charges')) {
$this->forge->dropColumn('event_charges', $field);
@@ -8,6 +8,10 @@ class AddExternalParentInfoToEventCharges extends Migration
{
public function up()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
$fields = [
'external_parent_firstname' => [
'type' => 'VARCHAR',
@@ -34,6 +38,10 @@ class AddExternalParentInfoToEventCharges extends Migration
public function down()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
foreach (['external_parent_firstname', 'external_parent_lastname', 'external_parent_phone'] as $field) {
if ($this->db->fieldExists($field, 'event_charges')) {
$this->forge->dropColumn('event_charges', $field);
@@ -8,6 +8,10 @@ class AddExternalParentEmailToEventCharges extends Migration
{
public function up()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
$fields = [
'external_parent_email' => [
'type' => 'VARCHAR',
@@ -24,9 +28,8 @@ class AddExternalParentEmailToEventCharges extends Migration
public function down()
{
if ($this->db->fieldExists('external_parent_email', 'event_charges')) {
if ($this->db->tableExists('event_charges') && $this->db->fieldExists('external_parent_email', 'event_charges')) {
$this->forge->dropColumn('event_charges', 'external_parent_email');
}
}
}
@@ -8,6 +8,10 @@ class AddWaiverSignedToEventCharges extends Migration
{
public function up()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
$fields = [
'waiver_signed' => [
'type' => 'TINYINT',
@@ -24,7 +28,7 @@ class AddWaiverSignedToEventCharges extends Migration
public function down()
{
if ($this->db->fieldExists('waiver_signed', 'event_charges')) {
if ($this->db->tableExists('event_charges') && $this->db->fieldExists('waiver_signed', 'event_charges')) {
$this->forge->dropColumn('event_charges', 'waiver_signed');
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddDescriptionToInvoices extends Migration
{
public function up(): void
{
if (! $this->db->tableExists('invoices') || $this->db->fieldExists('description', 'invoices')) {
return;
}
$this->forge->addColumn('invoices', [
'description' => [
'type' => 'TEXT',
'null' => true,
'after' => 'status',
],
]);
}
public function down(): void
{
if ($this->db->tableExists('invoices') && $this->db->fieldExists('description', 'invoices')) {
$this->forge->dropColumn('invoices', 'description');
}
}
}
@@ -0,0 +1,336 @@
<?php
declare(strict_types=1);
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
use RuntimeException;
final class AlignSchemaToScoolViewDump extends Migration
{
/**
* Columns present in school_prod.sql.zip but absent from scool_view.sql.zip.
*
* This migration intentionally makes the live database match the scool_view
* dump captured on 2026-07-17.
*/
private array $columnsToDrop = [
'classSection' => ['semester'],
'contactus' => ['semester'],
'discount_vouchers' => ['semester'],
'emergency_contacts' => ['semester', 'school_year'],
'ip_attempts' => ['semester'],
'login_activity' => ['semester'],
'notification_recipients' => ['semester'],
'notifications' => ['semester'],
'parents' => ['semester', 'school_year'],
'payment_error' => ['semester'],
'payment_transactions' => ['semester'],
'payments' => ['semester'],
'paypal_transactions' => ['semester'],
'preferences' => ['school_year'],
'refunds' => ['semester'],
'reimbursement_batch_items' => ['semester'],
'reimbursement_batches' => ['semester'],
'reimbursements' => ['semester'],
'students' => ['semester', 'school_year'],
'support_requests' => ['semester'],
'user_notifications' => ['semester'],
'users' => ['semester', 'school_year'],
'whatsapp_group_links' => ['semester'],
'whatsapp_group_memberships' => ['semester'],
];
/**
* Tables that should own a required school_year value after aligning the schema.
*
* @var list<string>
*/
private array $schoolYearTables = [
'admin_notification_subjects',
'attendance_comment_template',
'class_progress_attachments',
'competition_class_winners',
'competition_scores',
'competition_winners',
'inventory_categories',
'paypal_payments',
'placement_scores',
'promotion_queue',
'qcmquestions',
'school_year_closing_batches',
'school_year_closing_items',
'school_year_transition_logs',
'staff',
'whatsapp_group_links',
'whatsapp_invites_log',
];
public function up(): void
{
if ($this->db->DBDriver !== 'MySQLi') {
throw new RuntimeException('This migration requires MySQL/MariaDB through the MySQLi driver.');
}
$this->dropIndexIfExists('competition_winners', 'competition_id_class_section_id_rank');
$this->dropIndexIfExists('refunds', 'idx_refunds_parent_year_semester_status');
$this->dropIndexIfExists('teacher_class', 'idx_sy_teacher_class_school_year_223511c0');
$this->dropIndexIfExists('whatsapp_group_links', 'uq_section_term');
$this->dropIndexIfExists('whatsapp_group_memberships', 'uniq_whatsapp_membership');
$this->dropIndexIfExists('whatsapp_group_memberships', 'class_section_id_school_year_semester');
foreach ($this->columnsToDrop as $table => $columns) {
foreach ($columns as $column) {
$this->dropColumnIfExists($table, $column);
}
}
$this->db->resetDataCache();
foreach ($this->schoolYearTables as $table) {
$this->ensureRequiredSchoolYearColumn($table);
}
$this->db->resetDataCache();
$this->ensureIndex(
'refunds',
'idx_refunds_parent_year_semester_status',
['parent_id', 'school_year', 'status']
);
$this->ensureIndex(
'teacher_class',
'unique_teacher_assignment',
['teacher_id', 'class_section_id', 'school_year'],
true
);
$this->ensureIndex(
'whatsapp_group_links',
'uq_section_term',
['class_section_id'],
true
);
$this->ensureIndex(
'whatsapp_group_memberships',
'uniq_whatsapp_membership',
['class_section_id', 'school_year', 'subject_type', 'subject_id'],
true
);
$this->ensureIndex(
'whatsapp_group_memberships',
'class_section_id_school_year_semester',
['class_section_id', 'school_year']
);
if ($this->db->tableExists('whatsapp_group_links')) {
$this->db->query(
'ALTER TABLE `whatsapp_group_links` ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci'
);
}
}
public function down(): void
{
throw new RuntimeException(
'This migration is intentionally irreversible because matching scool_view drops columns and their data.'
);
}
private function ensureRequiredSchoolYearColumn(string $table): void
{
if (! $this->db->tableExists($table)) {
return;
}
if (! $this->db->fieldExists('school_year', $table)) {
$this->forge->addColumn($table, [
'school_year' => [
'type' => 'VARCHAR',
'constraint' => 9,
'null' => true,
],
]);
}
$year = $this->currentSchoolYear();
$this->db->query(
sprintf(
'UPDATE %s SET `school_year` = ? WHERE `school_year` IS NULL OR TRIM(`school_year`) = \'\'',
$this->quoteIdentifier($table)
),
[$year]
);
$this->db->query(sprintf(
'ALTER TABLE %s MODIFY `school_year` VARCHAR(9) NOT NULL',
$this->quoteIdentifier($table)
));
}
private function currentSchoolYear(): string
{
if ($this->db->tableExists('configuration')) {
$row = $this->db->table('configuration')
->select('config_value')
->whereIn('config_key', ['school_year', 'current_school_year'])
->where('config_value IS NOT NULL', null, false)
->where('TRIM(config_value) <>', '')
->orderBy("FIELD(config_key, 'school_year', 'current_school_year')", '', false)
->get(1)
->getRowArray();
if (isset($row['config_value']) && preg_match('/^\d{4}-\d{4}$/', (string) $row['config_value'])) {
return (string) $row['config_value'];
}
}
$year = (int) date('Y');
return $year . '-' . ($year + 1);
}
private function dropColumnIfExists(string $table, string $column): void
{
if (! $this->db->tableExists($table) || ! $this->db->fieldExists($column, $table)) {
return;
}
$this->dropForeignKeysContainingColumn($table, $column);
$this->dropIndexesContainingColumn($table, $column);
$this->dropChecksContainingColumn($table, $column);
$this->forge->dropColumn($table, $column);
}
private function ensureIndex(string $table, string $index, array $columns, bool $unique = false): void
{
if (! $this->db->tableExists($table) || ! $this->hasColumns($table, $columns)) {
return;
}
if ($this->indexExists($table, $index)) {
return;
}
$keyword = $unique ? 'UNIQUE INDEX' : 'INDEX';
$columnList = implode(', ', array_map([$this, 'quoteIdentifier'], $columns));
$this->db->query(sprintf(
'ALTER TABLE %s ADD %s %s (%s)',
$this->quoteIdentifier($table),
$keyword,
$this->quoteIdentifier($index),
$columnList
));
}
private function dropIndexIfExists(string $table, string $index): void
{
if (! $this->db->tableExists($table) || ! $this->indexExists($table, $index)) {
return;
}
$this->db->query(sprintf(
'ALTER TABLE %s DROP INDEX %s',
$this->quoteIdentifier($table),
$this->quoteIdentifier($index)
));
}
private function dropIndexesContainingColumn(string $table, string $column): void
{
$indexes = $this->db->query(
'SELECT DISTINCT INDEX_NAME
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND COLUMN_NAME = ?
AND INDEX_NAME <> \'PRIMARY\'',
[$table, $column]
)->getResult();
foreach ($indexes as $index) {
$this->dropIndexIfExists($table, $index->INDEX_NAME);
}
}
private function dropForeignKeysContainingColumn(string $table, string $column): void
{
$constraints = $this->db->query(
'SELECT DISTINCT CONSTRAINT_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND COLUMN_NAME = ?
AND REFERENCED_TABLE_NAME IS NOT NULL',
[$table, $column]
)->getResult();
foreach ($constraints as $constraint) {
$this->db->query(sprintf(
'ALTER TABLE %s DROP FOREIGN KEY %s',
$this->quoteIdentifier($table),
$this->quoteIdentifier($constraint->CONSTRAINT_NAME)
));
}
}
private function dropChecksContainingColumn(string $table, string $column): void
{
$checks = $this->db->query(
'SELECT tc.CONSTRAINT_NAME
FROM information_schema.TABLE_CONSTRAINTS tc
INNER JOIN information_schema.CHECK_CONSTRAINTS cc
ON cc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
AND cc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
WHERE tc.CONSTRAINT_SCHEMA = DATABASE()
AND tc.TABLE_NAME = ?
AND tc.CONSTRAINT_TYPE = \'CHECK\'
AND cc.CHECK_CLAUSE LIKE ?',
[$table, '%' . $column . '%']
)->getResult();
foreach ($checks as $check) {
$this->db->query(sprintf(
'ALTER TABLE %s DROP CHECK %s',
$this->quoteIdentifier($table),
$this->quoteIdentifier($check->CONSTRAINT_NAME)
));
}
}
private function indexExists(string $table, string $index): bool
{
$row = $this->db->query(
'SELECT COUNT(*) AS aggregate
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND INDEX_NAME = ?',
[$table, $index]
)->getRow();
return (int) ($row->aggregate ?? 0) > 0;
}
private function hasColumns(string $table, array $columns): bool
{
foreach ($columns as $column) {
if (! $this->db->fieldExists($column, $table)) {
return false;
}
}
return true;
}
private function quoteIdentifier(string $identifier): string
{
return '`' . str_replace('`', '``', $identifier) . '`';
}
}