diff --git a/go/test/endtoend/vtgate/foreignkey/fk_test.go b/go/test/endtoend/vtgate/foreignkey/fk_test.go index 23b71002fe6..cb4594db8c2 100644 --- a/go/test/endtoend/vtgate/foreignkey/fk_test.go +++ b/go/test/endtoend/vtgate/foreignkey/fk_test.go @@ -30,9 +30,10 @@ import ( "vitess.io/vitess/go/test/endtoend/cluster" "vitess.io/vitess/go/test/endtoend/utils" "vitess.io/vitess/go/vt/log" + "vitess.io/vitess/go/vt/vtgate/vtgateconn" + binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" topodatapb "vitess.io/vitess/go/vt/proto/topodata" - "vitess.io/vitess/go/vt/vtgate/vtgateconn" ) // TestInsertWithFK tests that insertions work as expected when foreign key management is enabled in Vitess. @@ -1505,6 +1506,45 @@ create table temp2(id bigint auto_increment primary key, col varchar(20) not nul mcmp.ExecAllowAndCompareError(`insert into temp1(col) values('d') `, utils.CompareOptions{}) } +// TestForeignKeyWithKeyspaceQualifier tests that CREATE TABLE with foreign key references +// that include keyspace qualifiers work correctly. This addresses bug #18889 where keyspace +// names were not being stripped before being sent to MySQL, causing failures because MySQL +// expects database names (vt_) not keyspace names. +func TestForeignKeyWithKeyspaceQualifier(t *testing.T) { + mcmp, closer := start(t) + defer closer() + + utils.Exec(t, mcmp.VtConn, `use uks`) + + // Create the parent table. + utils.Exec(t, mcmp.VtConn, `create table fk_parent(id bigint primary key)`) + + // Create the child table with keyspace-qualified foreign key reference. + utils.Exec(t, mcmp.VtConn, `create table fk_child(id bigint primary key, parent_id bigint, foreign key (parent_id) references uks.fk_parent(id))`) + + // Verify that the foreign key constraint works. + utils.Exec(t, mcmp.VtConn, `insert into fk_parent(id) values (1), (2)`) + utils.Exec(t, mcmp.VtConn, `insert into fk_child(id, parent_id) values (100, 1)`) + + // This should fail due to FK constraint. + _, err := utils.ExecAllowError(t, mcmp.VtConn, `insert into fk_child(id, parent_id) values (101, 999)`) + assert.ErrorContains(t, err, "Cannot add or update a child row: a foreign key constraint fails") + + // Test ALTER TABLE with keyspace-qualified foreign key. + utils.Exec(t, mcmp.VtConn, `create table fk_child2(id bigint primary key, parent_id bigint)`) + utils.Exec(t, mcmp.VtConn, `alter table fk_child2 add foreign key (parent_id) references uks.fk_parent(id)`) + + // Verify the constraint works for the altered table. + utils.Exec(t, mcmp.VtConn, `insert into fk_child2(id, parent_id) values (200, 2)`) + _, err = utils.ExecAllowError(t, mcmp.VtConn, `insert into fk_child2(id, parent_id) values (201, 888)`) + assert.ErrorContains(t, err, "Cannot add or update a child row: a foreign key constraint fails") + + // Clean up. + utils.Exec(t, mcmp.VtConn, `drop table fk_child`) + utils.Exec(t, mcmp.VtConn, `drop table fk_child2`) + utils.Exec(t, mcmp.VtConn, `drop table fk_parent`) +} + // TestRestrictFkOnNonStandardKey verifies that restrict_fk_on_non_standard_key is set to off func TestRestrictFkOnNonStandardKey(t *testing.T) { mcmp, closer := start(t) diff --git a/go/vt/vtgate/planbuilder/ddl.go b/go/vt/vtgate/planbuilder/ddl.go index dd135154f9f..a50b45235fd 100644 --- a/go/vt/vtgate/planbuilder/ddl.go +++ b/go/vt/vtgate/planbuilder/ddl.go @@ -109,6 +109,8 @@ func buildDDLPlans(ctx context.Context, sql string, ddlStatement sqlparser.DDLSt if err != nil { return nil, nil, err } + // Remove keyspace qualifiers from all table references (including foreign key references). + sqlparser.RemoveSpecificKeyspace(ddlStatement, keyspace.Name) err = checkFKError(vschema, ddlStatement, keyspace) case *sqlparser.CreateView: destination, keyspace, err = buildCreateViewCommon(ctx, vschema, reservedVars, cfg, ddl.Select, ddl) diff --git a/go/vt/vtgate/planbuilder/testdata/ddl_cases.json b/go/vt/vtgate/planbuilder/testdata/ddl_cases.json index 3b84004ca99..b51de04ff47 100644 --- a/go/vt/vtgate/planbuilder/testdata/ddl_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/ddl_cases.json @@ -683,5 +683,105 @@ "main.function_default" ] } + }, + { + "comment": "create table with foreign key reference without keyspace qualifier", + "query": "create table t1(id bigint, t2_id bigint, primary key(id), foreign key (t2_id) references t2(id))", + "plan": { + "Type": "DirectDDL", + "QueryType": "DDL", + "Original": "create table t1(id bigint, t2_id bigint, primary key(id), foreign key (t2_id) references t2(id))", + "Instructions": { + "OperatorType": "DDL", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "Query": "create table t1 (\n\tid bigint,\n\tt2_id bigint,\n\tprimary key (id),\n\tforeign key (t2_id) references t2 (id)\n)" + }, + "TablesUsed": [ + "main.t1" + ] + } + }, + { + "comment": "create table with foreign key reference with keyspace qualifier", + "query": "create table user.t1(id bigint, t2_id bigint, primary key(id), foreign key (t2_id) references user.t2(id))", + "plan": { + "Type": "DirectDDL", + "QueryType": "DDL", + "Original": "create table user.t1(id bigint, t2_id bigint, primary key(id), foreign key (t2_id) references user.t2(id))", + "Instructions": { + "OperatorType": "DDL", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "create table t1 (\n\tid bigint,\n\tt2_id bigint,\n\tprimary key (id),\n\tforeign key (t2_id) references t2 (id)\n)" + }, + "TablesUsed": [ + "user.t1" + ] + } + }, + { + "comment": "create table with multiple foreign keys with keyspace qualifiers", + "query": "create table user.orders(order_id bigint, customer_id bigint, product_id bigint, primary key(order_id), foreign key (customer_id) references user.customers(id), foreign key (product_id) references user.products(id))", + "plan": { + "Type": "DirectDDL", + "QueryType": "DDL", + "Original": "create table user.orders(order_id bigint, customer_id bigint, product_id bigint, primary key(order_id), foreign key (customer_id) references user.customers(id), foreign key (product_id) references user.products(id))", + "Instructions": { + "OperatorType": "DDL", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "create table orders (\n\torder_id bigint,\n\tcustomer_id bigint,\n\tproduct_id bigint,\n\tprimary key (order_id),\n\tforeign key (customer_id) references customers (id),\n\tforeign key (product_id) references products (id)\n)" + }, + "TablesUsed": [ + "user.orders" + ] + } + }, + { + "comment": "alter table add foreign key with keyspace qualifier", + "query": "alter table user.t1 add foreign key (t2_id) references user.t2(id)", + "plan": { + "Type": "DirectDDL", + "QueryType": "DDL", + "Original": "alter table user.t1 add foreign key (t2_id) references user.t2(id)", + "Instructions": { + "OperatorType": "DDL", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "alter table t1 add foreign key (t2_id) references t2 (id)" + }, + "TablesUsed": [ + "user.t1" + ] + } + }, + { + "comment": "create table with foreign key with ON DELETE and ON UPDATE clauses and keyspace qualifier", + "query": "create table user.employees(emp_id bigint, dept_id bigint, primary key(emp_id), foreign key (dept_id) references user.departments(dept_id) on delete set null on update cascade)", + "plan": { + "Type": "DirectDDL", + "QueryType": "DDL", + "Original": "create table user.employees(emp_id bigint, dept_id bigint, primary key(emp_id), foreign key (dept_id) references user.departments(dept_id) on delete set null on update cascade)", + "Instructions": { + "OperatorType": "DDL", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "create table employees (\n\temp_id bigint,\n\tdept_id bigint,\n\tprimary key (emp_id),\n\tforeign key (dept_id) references departments (dept_id) on delete set null on update cascade\n)" + }, + "TablesUsed": [ + "user.employees" + ] + } } ]