From eff3b79cce4c3fbc066c7567ebb46c372a5512db Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Wed, 20 May 2026 02:52:34 -0700 Subject: [PATCH] fix(csharp): canonicalize identifier case in SEA get_primary_keys (PECO-3045) [SEA] The SEA path of GetPrimaryKeysAsync used the user-provided input names (_metadataCatalogName / _metadataSchemaName / _metadataTableName) verbatim when building result rows. When callers pass non-canonical case (e.g. catalog="MAIN" for a server-lowercase catalog "main") the result columns echo back the input case instead of the canonical lowercase form. The Thrift path returns canonical names from the server and JDBC reads the catalogName/namespace/tableName columns from SHOW KEYS, so SEA was the outlier. Fix: prefer the catalogName / namespace / tableName columns returned by SHOW KEYS for each row, falling back to a lowercased copy of the user input only if those columns are missing or null. Mirrors how SHOW FOREIGN KEYS already populates parent identifiers in the same file. PECO-3045 --- .../StatementExecutionStatement.cs | 14 ++++++++++- .../StatementExecution/SeaMetadataE2ETests.cs | 25 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/csharp/src/StatementExecution/StatementExecutionStatement.cs b/csharp/src/StatementExecution/StatementExecutionStatement.cs index b910097af..36b308b09 100644 --- a/csharp/src/StatementExecution/StatementExecutionStatement.cs +++ b/csharp/src/StatementExecution/StatementExecutionStatement.cs @@ -1492,8 +1492,17 @@ private async Task GetPrimaryKeysAsync(CancellationToken cancellati var keys = new List<(string, string, string, string, int, string)>(); int seq = 0; + // PECO-3045: prefer canonical identifiers from the SHOW KEYS server response over the + // (possibly non-canonical) user input. JDBC spec calls for canonical names in the + // result columns; Thrift returns them natively, so SEA must match. + string fallbackCatalog = _metadataCatalogName!.ToLowerInvariant(); + string fallbackSchema = _metadataSchemaName!.ToLowerInvariant(); + string fallbackTable = _metadataTableName!.ToLowerInvariant(); foreach (var batch in batches) { + var catalogArray = TryGetColumn(batch, "catalogName"); + var schemaArray = TryGetColumn(batch, "namespace"); + var tableNameArray = TryGetColumn(batch, "tableName"); var colNameArray = TryGetColumn(batch, "col_name"); var keyNameArray = TryGetColumn(batch, "constraintName"); var keySeqArray = TryGetColumn(batch, "keySeq"); @@ -1503,7 +1512,10 @@ private async Task GetPrimaryKeysAsync(CancellationToken cancellati if (colNameArray.IsNull(i)) continue; int keySeq = keySeqArray != null && !keySeqArray.IsNull(i) ? keySeqArray.GetValue(i)!.Value : ++seq; string pkName = keyNameArray != null && !keyNameArray.IsNull(i) ? keyNameArray.GetString(i) : ""; - keys.Add((_metadataCatalogName!, _metadataSchemaName!, _metadataTableName!, + string catalog = catalogArray != null && !catalogArray.IsNull(i) ? catalogArray.GetString(i) : fallbackCatalog; + string schemaName = schemaArray != null && !schemaArray.IsNull(i) ? schemaArray.GetString(i) : fallbackSchema; + string tableName = tableNameArray != null && !tableNameArray.IsNull(i) ? tableNameArray.GetString(i) : fallbackTable; + keys.Add((catalog, schemaName, tableName, colNameArray.GetString(i), keySeq, pkName)); } } diff --git a/csharp/test/E2E/StatementExecution/SeaMetadataE2ETests.cs b/csharp/test/E2E/StatementExecution/SeaMetadataE2ETests.cs index a7405ff2b..b9d775ed4 100644 --- a/csharp/test/E2E/StatementExecution/SeaMetadataE2ETests.cs +++ b/csharp/test/E2E/StatementExecution/SeaMetadataE2ETests.cs @@ -330,6 +330,31 @@ public async Task SEA_GetPrimaryKeys_ReturnsPKColumns() Assert.Equal("c_int", rows[1]["COLUMN_NAME"]); } + // PECO-3045 — SEA echoed input case in result columns; JDBC spec says columns + // should be canonical (lowercase). Call get_primary_keys with uppercase + // catalog/schema/table and assert the returned TABLE_CAT / TABLE_SCHEM / TABLE_NAME + // columns are the canonical lowercase form. + [SkippableFact] + public async Task SEA_GetPrimaryKeys_ReturnsCanonicalCase_InResultColumns() + { + SkipIfNotConfigured(); + using var conn = CreateSeaConnection(); + // Pass non-canonical (uppercase) input — server-side identifiers are lowercase. + var rows = await ReadMetadata( + conn, + "GetPrimaryKeys", + TestCatalog.ToUpperInvariant(), + TestSchema.ToUpperInvariant(), + TestTable.ToUpperInvariant()); + Assert.Equal(2, rows.Count); + foreach (var row in rows) + { + Assert.Equal(TestCatalog, row["TABLE_CAT"]); + Assert.Equal(TestSchema, row["TABLE_SCHEM"]); + Assert.Equal(TestTable, row["TABLE_NAME"]); + } + } + // --- GetTableSchema --- [SkippableFact]