From b9b94e871f087f1ec034cfb56ae90e39bac47e9a Mon Sep 17 00:00:00 2001 From: iamlinjunhong <49111204+iamlinjunhong@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:55:06 +0800 Subject: [PATCH 1/3] fix: return MatrixOne foreign key metadata --- driver/catalog.cc | 26 +++++++++++++---- test/mo_odbc_deep.cc | 69 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 78 insertions(+), 17 deletions(-) diff --git a/driver/catalog.cc b/driver/catalog.cc index a0ac64f6..e07a8a08 100644 --- a/driver/catalog.cc +++ b/driver/catalog.cc @@ -1622,6 +1622,9 @@ SQLRETURN foreign_keys_i_s(SQLHSTMT hstmt, MYSQL *mysql= stmt->dbc->mysql; char tmpbuff[1024]; /* This should be big enough. */ const char *update_rule, *delete_rule, *ref_constraints_join; + const bool is_matrixone = + mysql->server_version != nullptr && + strstr(mysql->server_version, "MatrixOne") != nullptr; SQLRETURN rc; std::string query, pk_db, fk_db, order_by; query.reserve(4096); @@ -1691,12 +1694,22 @@ SQLRETURN foreign_keys_i_s(SQLHSTMT hstmt, "A.CONSTRAINT_NAME AS FK_NAME," "'PRIMARY' AS PK_NAME," "7 AS DEFERRABILITY" - " FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE A" - " JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE D" - " ON (D.TABLE_SCHEMA=A.REFERENCED_TABLE_SCHEMA AND D.TABLE_NAME=A.REFERENCED_TABLE_NAME" - " AND D.COLUMN_NAME=A.REFERENCED_COLUMN_NAME)"); - query.append(ref_constraints_join).append(" WHERE D.CONSTRAINT_NAME"); - query.append(" IS NOT NULL "); + " FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE A"); + if (!is_matrixone) + { + /* MySQL bug #51422 can expose non-key referenced columns, so retain the + historical self-join there. MatrixOne does not expose primary-key rows + in KEY_COLUMN_USAGE; its foreign-key rows already identify both sides. */ + query.append(" JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE D" + " ON (D.TABLE_SCHEMA=A.REFERENCED_TABLE_SCHEMA AND D.TABLE_NAME=A.REFERENCED_TABLE_NAME" + " AND D.COLUMN_NAME=A.REFERENCED_COLUMN_NAME)"); + } + query.append(ref_constraints_join); + if (is_matrixone) + query.append(" WHERE A.REFERENCED_TABLE_NAME IS NOT NULL" + " AND A.REFERENCED_COLUMN_NAME IS NOT NULL "); + else + query.append(" WHERE D.CONSTRAINT_NAME IS NOT NULL "); if (pk_table && pk_table[0]) { @@ -1751,6 +1764,7 @@ SQLRETURN foreign_keys_i_s(SQLHSTMT hstmt, } query.append(order_by); + MYLOG_QUERY(stmt, query.c_str()); rc= MySQLPrepare(hstmt, (SQLCHAR *)query.c_str(), (SQLINTEGER)(query.length()), true, false); diff --git a/test/mo_odbc_deep.cc b/test/mo_odbc_deep.cc index d5c7605a..df8f1a9e 100644 --- a/test/mo_odbc_deep.cc +++ b/test/mo_odbc_deep.cc @@ -297,8 +297,16 @@ INSERT INTO mo_odbc_deep.type_matrix VALUES "mo_odbc_deep.type_matrix(c_varchar)"); execute(dbc, "CREATE VIEW mo_odbc_deep.type_view AS SELECT id,c_varchar " "FROM mo_odbc_deep.type_matrix"); - execute(dbc, "CREATE TABLE mo_odbc_deep.meta_under_score (id INT)"); - execute(dbc, "CREATE TABLE mo_odbc_deep.metaXunderXscore (id INT)"); + execute(dbc, "CREATE TABLE mo_odbc_deep.meta_under_score (id INT)"); + execute(dbc, "CREATE TABLE mo_odbc_deep.metaXunderXscore (id INT)"); + execute(dbc, "CREATE TABLE mo_odbc_deep.fk_parent (id INT PRIMARY KEY)"); + execute(dbc, R"SQL( +CREATE TABLE mo_odbc_deep.fk_child ( + id INT PRIMARY KEY, + parent_id INT, + CONSTRAINT fk_deep_parent FOREIGN KEY (parent_id) + REFERENCES mo_odbc_deep.fk_parent(id) +))SQL"); execute(dbc, R"SQL( CREATE TABLE mo_odbc_deep.param_values ( id BIGINT PRIMARY KEY, @@ -344,9 +352,10 @@ void test_connection_capabilities(SQLHDBC dbc) { expect(!driver_version.empty(), "SQL_DRIVER_VER is empty"); for (SQLUSMALLINT api : {SQL_API_SQLBINDPARAMETER, SQL_API_SQLDESCRIBEPARAM, - SQL_API_SQLNUMPARAMS, SQL_API_SQLTABLES, - SQL_API_SQLCOLUMNS, SQL_API_SQLPRIMARYKEYS, - SQL_API_SQLSTATISTICS, SQL_API_SQLGETTYPEINFO}) { + SQL_API_SQLNUMPARAMS, SQL_API_SQLTABLES, + SQL_API_SQLCOLUMNS, SQL_API_SQLPRIMARYKEYS, + SQL_API_SQLFOREIGNKEYS, SQL_API_SQLSTATISTICS, + SQL_API_SQLGETTYPEINFO}) { SQLUSMALLINT supported = SQL_FALSE; check(SQLGetFunctions(dbc, api, &supported), SQL_HANDLE_DBC, dbc, "SQLGetFunctions " + std::to_string(api)); @@ -388,9 +397,9 @@ SQLLEN count_rows(SQLHSTMT stmt) { void test_catalog_metadata(SQLHDBC dbc) { const bool unicode_driver = info_string(dbc, SQL_DRIVER_NAME).find("9w") != std::string::npos; - { - Statement stmt(dbc); - SQLCHAR catalog[] = "mo_odbc_deep"; + { + Statement stmt(dbc); + SQLCHAR catalog[] = "mo_odbc_deep"; SQLCHAR table[] = "type_matrix"; SQLCHAR type[] = "TABLE"; check(SQLTables(stmt.handle(), catalog, SQL_NTS, nullptr, 0, table, @@ -542,9 +551,47 @@ void test_catalog_metadata(SQLHDBC dbc) { SQL_HANDLE_STMT, stmt.handle(), "SQLPrimaryKeys COLUMN_NAME"); expect(std::string(reinterpret_cast(column)) == "id", "SQLPrimaryKeys did not report id"); - expect(SQLFetch(stmt.handle()) == SQL_NO_DATA, - "SQLPrimaryKeys returned more than one key column"); - } + expect(SQLFetch(stmt.handle()) == SQL_NO_DATA, + "SQLPrimaryKeys returned more than one key column"); + } + { + Statement stmt(dbc); + SQLCHAR catalog[] = "mo_odbc_deep"; + SQLCHAR table[] = "fk_child"; + check(SQLForeignKeys(stmt.handle(), nullptr, 0, nullptr, 0, nullptr, 0, + catalog, SQL_NTS, nullptr, 0, table, SQL_NTS), + SQL_HANDLE_STMT, stmt.handle(), "SQLForeignKeys"); + check(SQLFetch(stmt.handle()), SQL_HANDLE_STMT, stmt.handle(), + "SQLForeignKeys fetch"); + + auto get_text = [&](SQLUSMALLINT column) { + SQLCHAR value[256] = {}; + SQLLEN length = 0; + check(SQLGetData(stmt.handle(), column, SQL_C_CHAR, value, sizeof(value), + &length), + SQL_HANDLE_STMT, stmt.handle(), + "SQLForeignKeys column " + std::to_string(column)); + return std::string(reinterpret_cast(value)); + }; + SQLSMALLINT key_seq = 0; + SQLLEN key_seq_length = 0; + expect(get_text(3) == "fk_parent", + "SQLForeignKeys reported the wrong primary-key table"); + expect(get_text(4) == "id", + "SQLForeignKeys reported the wrong primary-key column"); + expect(get_text(7) == "fk_child", + "SQLForeignKeys reported the wrong foreign-key table"); + expect(get_text(8) == "parent_id", + "SQLForeignKeys reported the wrong foreign-key column"); + check(SQLGetData(stmt.handle(), 9, SQL_C_SSHORT, &key_seq, + sizeof(key_seq), &key_seq_length), + SQL_HANDLE_STMT, stmt.handle(), "SQLForeignKeys KEY_SEQ"); + expect(key_seq == 1, "SQLForeignKeys KEY_SEQ should be 1"); + expect(get_text(12) == "fk_deep_parent", + "SQLForeignKeys reported the wrong foreign-key name"); + expect(SQLFetch(stmt.handle()) == SQL_NO_DATA, + "SQLForeignKeys returned more than one key column"); + } { Statement stmt(dbc); SQLCHAR catalog[] = "mo_odbc_deep"; From 9a64725a246959ff4e982e80319e5e9d5c2c1319 Mon Sep 17 00:00:00 2001 From: iamlinjunhong <49111204+iamlinjunhong@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:03:31 +0800 Subject: [PATCH 2/3] fix: avoid referential constraint dependency --- driver/catalog.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/driver/catalog.cc b/driver/catalog.cc index e07a8a08..7d63b185 100644 --- a/driver/catalog.cc +++ b/driver/catalog.cc @@ -1638,7 +1638,14 @@ SQLRETURN foreign_keys_i_s(SQLHSTMT hstmt, /* With 5.1, we can use REFERENTIAL_CONSTRAINTS to get even more info. */ - if (is_minimum_version(mysql->server_version, "5.1")) + if (is_matrixone) + { + /* MatrixOne currently supports NO ACTION semantics and older releases do + not consistently expose REFERENTIAL_CONSTRAINTS. */ + update_rule= delete_rule= "3"; + ref_constraints_join= ""; + } + else if (is_minimum_version(mysql->server_version, "5.1")) { update_rule= "CASE" " WHEN R.UPDATE_RULE = 'CASCADE' THEN 0" From e6dbcae53b3bda3649a2ec515c5fc83271474c4d Mon Sep 17 00:00:00 2001 From: iamlinjunhong <49111204+iamlinjunhong@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:07:34 +0800 Subject: [PATCH 3/3] test: distinguish missing server foreign key metadata --- test/mo_odbc_deep.cc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/mo_odbc_deep.cc b/test/mo_odbc_deep.cc index df8f1a9e..97153816 100644 --- a/test/mo_odbc_deep.cc +++ b/test/mo_odbc_deep.cc @@ -561,7 +561,18 @@ void test_catalog_metadata(SQLHDBC dbc) { check(SQLForeignKeys(stmt.handle(), nullptr, 0, nullptr, 0, nullptr, 0, catalog, SQL_NTS, nullptr, 0, table, SQL_NTS), SQL_HANDLE_STMT, stmt.handle(), "SQLForeignKeys"); - check(SQLFetch(stmt.handle()), SQL_HANDLE_STMT, stmt.handle(), + SQLRETURN foreign_key_fetch = SQLFetch(stmt.handle()); + if (foreign_key_fetch == SQL_NO_DATA && + scalar_int( + dbc, + "SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE " + "WHERE TABLE_SCHEMA='mo_odbc_deep' AND TABLE_NAME='fk_child' " + "AND REFERENCED_TABLE_NAME IS NOT NULL") == 0) { + throw KnownIssue( + "older MatrixOne releases do not expose foreign-key rows through " + "INFORMATION_SCHEMA.KEY_COLUMN_USAGE; matrixorigin/matrixone#25103"); + } + check(foreign_key_fetch, SQL_HANDLE_STMT, stmt.handle(), "SQLForeignKeys fetch"); auto get_text = [&](SQLUSMALLINT column) {