Bug #115053 Infinite foreigns keys
Submitted: 17 May 2024 16:34 Modified: 20 May 2024 9:49
Reporter: Izabel Santos Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: DDL Severity:S3 (Non-critical)
Version:8.0 OS:Windows
Assigned to: CPU Architecture:Any

[17 May 2024 16:34] Izabel Santos
Description:
I checked a bug in mysql: The MySQL doesn't manage foreign keys, recreating them even when they already exist. As if it were a parasitic code, it ignores patterns with "create if it doesn't exist", rendering all control in the bank useless. I will send three procedures and a script that will show the problem with Mysql. I'm coming from SQL_Server and I need this problem to be reviewed as soon as possible:

CREATE DATABASE IF NOT EXISTS BazarDB;
USE BazarDB;

-- PROCEDURE CREATE TABLES

DELIMITER //

CREATE PROCEDURE sp_CreateTables()
BEGIN

	-- Tables
	CREATE TABLE IF NOT EXISTS Clients (
		client_id INT PRIMARY KEY AUTO_INCREMENT,
		client_name VARCHAR(80) NOT NULL,
		client_email VARCHAR(50) NOT NULL,
		client_cpf VARCHAR(30) NOT NULL,
		client_phone_number VARCHAR(30) NOT NULL
	);

	CREATE TABLE IF NOT EXISTS Products (
		product_id INT PRIMARY KEY AUTO_INCREMENT,
		product_name VARCHAR(150) NOT NULL,
		product_sku VARCHAR(50) NOT NULL,
		product_upc VARCHAR(50) NOT NULL,
		product_item_price DECIMAL(18,2) NOT NULL
	);

	CREATE TABLE IF NOT EXISTS Orders (
		order_ID INT PRIMARY KEY AUTO_INCREMENT,
		client_id INT NOT NULL,
		order_def_id INT NOT NULL,
		order_purchase_date DATETIME NOT NULL,
		order_payments_date DATETIME NOT NULL,
		order_ship_city VARCHAR(50) NOT NULL,
		order_ship_state VARCHAR(50) NOT NULL,
		order_ship_postal_code VARCHAR(10) NOT NULL,
		order_ship_country VARCHAR(30) NOT NULL,
		order_currency VARCHAR(10) NOT NULL,
		order_ship_service_level VARCHAR(20) DEFAULT 'Standard' CHECK (order_ship_service_level IN ('Standard', 'Express', 'Priority')),
		order_status_delivery VARCHAR(20) NOT NULL DEFAULT 'Processing' CHECK (order_status_delivery IN ('Processing', 'Shipped', 'Delivered', 'In Transit', 'Returned')),
		FOREIGN KEY (client_id) REFERENCES Clients(client_id) ON DELETE CASCADE ON UPDATE CASCADE
	);

	CREATE TABLE IF NOT EXISTS OrderItems (
		oi_order_ID INT PRIMARY KEY AUTO_INCREMENT,
		order_id INT NOT NULL,
		product_id INT NOT NULL,
		oi_order_item_id INT NOT NULL,
		oi_product_price DECIMAL(18,2) NOT NULL,
		oi_quantity_purchased INT NOT NULL,
		oi_item_status VARCHAR(20) NOT NULL DEFAULT 'Processing' CHECK (oi_item_status IN ('Processing', 'Shipped', 'Delivered', 'In Transit', 'Returned')),
		FOREIGN KEY (order_id) REFERENCES Orders(order_ID) ON DELETE CASCADE ON UPDATE CASCADE,
		FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE CASCADE ON UPDATE CASCADE
	);

	CREATE TABLE IF NOT EXISTS Purchase_Requests (
		pr_id INT PRIMARY KEY AUTO_INCREMENT,
		product_id INT NOT NULL,
		pr_supply INT NOT NULL,
		pr_quantity INT NOT NULL,
		pr_unit_price DECIMAL(18,2) NOT NULL,
		pr_total_price DECIMAL(18,2) NOT NULL,
		pr_purchase DATETIME NOT NULL,
		pr_backorder BIT DEFAULT 0
	);

	CREATE TABLE IF NOT EXISTS Internal_Storage (
		is_id INT PRIMARY KEY AUTO_INCREMENT,
		product_id INT NOT NULL,
		is_actual_qte INT DEFAULT 0 NOT NULL,
		is_minimal_qte INT DEFAULT 0 NOT NULL,
		FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE CASCADE ON UPDATE CASCADE
	);

	CREATE TABLE IF NOT EXISTS Suppliers (
		s_id INT PRIMARY KEY AUTO_INCREMENT,
		s_name VARCHAR(50) NOT NULL,
		s_cnpj VARCHAR(30) NOT NULL UNIQUE,
		s_email VARCHAR(50) NOT NULL,
		s_city VARCHAR(50) NOT NULL,
		s_country VARCHAR(30) NOT NULL
	);

END//
DELIMITER ; 

-- PROCEDURE CREATE FOREIGNS

DELIMITER //
CREATE PROCEDURE sp_CreateForeignKeys()
BEGIN
	-- Adicionar chaves estrangeiras
    SELECT COUNT(*)
	INTO @constraint_count_1
	FROM information_schema.TABLE_CONSTRAINTS 
	WHERE TABLE_SCHEMA = 'BazarDB' 
	AND TABLE_NAME = 'Orders' 
	AND CONSTRAINT_NAME = 'FK_OR_CLI';
    IF @constraint_count_1 = 0 THEN
			ALTER TABLE Orders ADD CONSTRAINT FK_OR_CLI FOREIGN KEY (client_id) REFERENCES Clients(client_id) ON DELETE CASCADE ON UPDATE CASCADE;
	END IF;
    
    SELECT COUNT(*)
	INTO @constraint_count_2
	FROM information_schema.TABLE_CONSTRAINTS 
	WHERE TABLE_SCHEMA = 'BazarDB' 
	AND TABLE_NAME = 'Internal_Storage' 
	AND CONSTRAINT_NAME = 'FK_IS_PROD';
    IF @constraint_count_2 = 0 THEN
    	ALTER TABLE Internal_Storage ADD CONSTRAINT FK_IS_PROD FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE CASCADE ON UPDATE CASCADE;
    END IF;
    
	SELECT COUNT(*)
	INTO @constraint_count_3
	FROM information_schema.TABLE_CONSTRAINTS 
	WHERE TABLE_SCHEMA = 'BazarDB' 
	AND TABLE_NAME = 'OrderItems' 
	AND CONSTRAINT_NAME = 'FK_OI_ORD';
    IF @constraint_count_3 = 0 THEN    
		ALTER TABLE OrderItems ADD CONSTRAINT FK_OI_ORD FOREIGN KEY (order_id) REFERENCES Orders(order_ID) ON DELETE CASCADE ON UPDATE CASCADE;
	END IF;
    
    SELECT COUNT(*)
	INTO @constraint_count_4
	FROM information_schema.TABLE_CONSTRAINTS 
	WHERE TABLE_SCHEMA = 'BazarDB' 
	AND TABLE_NAME = 'OrderItems' 
	AND CONSTRAINT_NAME = 'FK_OI_PROD';
    IF @constraint_count_4 = 0 THEN   
		ALTER TABLE OrderItems ADD CONSTRAINT FK_OI_PROD FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE CASCADE ON UPDATE CASCADE;
	END IF;
END//
DELIMITER ;

-- SCRIPT: VERIFY FOREIGNS

SELECT
    TABLE_NAME,
    COLUMN_NAME,
    CONSTRAINT_NAME,
    REFERENCED_TABLE_NAME,
    REFERENCED_COLUMN_NAME
FROM
    INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE
    CONSTRAINT_SCHEMA = 'BazarDB'
    AND REFERENCED_TABLE_NAME IS NOT NULL;

resulting this table: 

internal_storage |product_id	|FK_IS_PROD               |products     |product_id
internal_storage |product_id	|internal_storage_ibfk_1  |products     |product_id
orderitems	 |order_id	|FK_OI_ORD	          |orders	|order_ID
orderitems	 |product_id	|FK_OI_PROD	          |products	|product_id
orderitems	 |order_id	|orderitems_ibfk_1	  |orders	|order_ID
orderitems	 |product_id	|orderitems_ibfk_2	  |products	|product_id
orders	         |client_id	|FK_OR_CLI	          |clients	|client_id
orders	         |client_id	|orders_ibfk_1	          |clients	|client_id

How to repeat:
just run the two procedures above and the script below. There's no way to go wrong. There's no way you can worry about naming a constraint if mysql creates another name without you asking. You lose control of the database, because if this is in a procedure, how many times will mysql recreate the name of the constraint? There must be a solution for this

Suggested fix:

internal_storage |product_id	|FK_IS_PROD               |products     |product_id
orderitems	 |order_id	|FK_OI_ORD	          |orders	|order_ID
orderitems	 |product_id	|FK_OI_PROD	          |products	|product_id
orders	         |client_id	|FK_OR_CLI	          |clients	|client_id

Mysql should block if the constraint is not named: do not create automatically. If anyone misses, let there be an "ENABLE auto_generated_foreign_keys_name" instruction;

Check if the constraint does not exist; if it exists, create it!
[20 May 2024 9:49] MySQL Verification Team
HI Mrs. Santos,

Thank you for your bug report.

We have managed to repeat your results with 8.0.37 and 8.4.0:

Internal_Storage	product_id	FK_IS_PROD	Products	product_id
Internal_Storage	product_id	internal_storage_ibfk_1	Products	product_id
OrderItems	order_id	FK_OI_ORD	Orders	order_ID
OrderItems	product_id	FK_OI_PROD	Products	product_id
OrderItems	order_id	orderitems_ibfk_1	Orders	order_ID
OrderItems	product_id	orderitems_ibfk_2	Products	product_id
Orders	client_id	FK_OR_CLI	Clients	client_id
Orders	client_id	orders_ibfk_1	Clients	client_id

Verified as reported.