This is an automated email from the ASF dual-hosted git repository.

curth pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new 191402012 feat(csharp/src/Drivers/Apache): Add escape underscore 
parameter to metadata command (#2920)
191402012 is described below

commit 191402012ff7da41a614cad536104e1b9ee27c79
Author: eric-wang-1990 <[email protected]>
AuthorDate: Thu Jun 5 12:59:50 2025 -0700

    feat(csharp/src/Drivers/Apache): Add escape underscore parameter to 
metadata command (#2920)
    
    # Add support for escaping underscores in metadata queries
    
    ## Why
    For current Power BI ODBC connection we are escaping all the underscore.
    I've thinking about change in the connector side to always pass in the
    escaped name, but it is not feasible since later we will introduce sql
    text based metadata query like DESCRIBE TABLE `xxx` as json, where we
    would expect xxx not be escaped since it will be treated as text.
    Thus the best way is to introduce a new parameter in the metadata api to
    specify whether the client want to escape the underscore, then on the
    driver side based on different calling method(Thrift API or sql text) we
    can perform differently.
    
    ## Description
    This PR adds support for escaping underscores in metadata query
    parameters through a new parameter
    `adbc.get_metadata.escape_underscore`. When enabled, underscores in
    catalog, schema, table, and column names will be treated as literal
    characters rather than SQL wildcards.
    
    
    ## Changes
    - Added new parameter `EscapeUnderscore` to control underscore escaping
    behavior
    - Added `EscapeUnderscoreInName` helper method to handle underscore
    escaping
    - Updated all metadata query methods to use the escaping functionality:
      - GetCatalogsAsync
      - GetSchemasAsync
      - GetTablesAsync
      - GetColumnsAsync
      - GetPrimaryKeysAsync
      - GetCrossReferenceAsync
      - GetCrossReferenceAsForeignTableAsync
    
    ## Testing
    - Added test case in `CanGetColumnsExtended` to verify the escaping
    functionality
    - Verified that null values are handled correctly
    - Verified that escaping only occurs when the flag is enabled
    
    ## Usage
    To enable underscore escaping, set the parameter:
    ```csharp
    statement.SetOption(ApacheParameters.EscapeUnderscore, "true");
    ```
    
    ## Impact
    This change allows users to query metadata for objects that contain
    underscores in their names without the underscore being interpreted as a
    SQL wildcard character.
---
 csharp/src/Drivers/Apache/ApacheParameters.cs      |  5 ++
 .../Drivers/Apache/Hive2/HiveServer2Statement.cs   | 56 ++++++++++++++--------
 csharp/test/Drivers/Databricks/StatementTests.cs   |  1 +
 3 files changed, 41 insertions(+), 21 deletions(-)

diff --git a/csharp/src/Drivers/Apache/ApacheParameters.cs 
b/csharp/src/Drivers/Apache/ApacheParameters.cs
index 4629df98f..c63442772 100644
--- a/csharp/src/Drivers/Apache/ApacheParameters.cs
+++ b/csharp/src/Drivers/Apache/ApacheParameters.cs
@@ -75,5 +75,10 @@ namespace Apache.Arrow.Adbc.Drivers.Apache
         /// The table name (or pattern) of the foreign (child) table for 
GetCrossReference metadata command query.
         /// </summary>
         public const string ForeignTableName = 
"adbc.get_metadata.foreign_target_table";
+
+        /// <summary>
+        /// Whether to escape underscore to treat as literal rather than 
wildcard. Default to false.
+        /// </summary>
+        public const string EscapeUnderscore = 
"adbc.get_metadata.escape_underscore";
     }
 }
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2Statement.cs 
b/csharp/src/Drivers/Apache/Hive2/HiveServer2Statement.cs
index e4d272d0e..3203502e5 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2Statement.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2Statement.cs
@@ -254,6 +254,12 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
                 case ApacheParameters.ForeignTableName:
                     this.ForeignTableName = value;
                     break;
+                case ApacheParameters.EscapeUnderscore:
+                    if (ApacheUtility.BooleanIsValid(key, value, out bool 
escapeUnderscore))
+                    {
+                        this.EscapeUnderscore = escapeUnderscore;
+                    }
+                    break;
                 default:
                     throw AdbcException.NotImplemented($"Option '{key}' is not 
implemented.");
             }
@@ -311,6 +317,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
         protected internal string? ForeignCatalogName { get; set; }
         protected internal string? ForeignSchemaName { get; set; }
         protected internal string? ForeignTableName { get; set; }
+        protected internal bool EscapeUnderscore { get; set; } = false;
         protected internal TSparkDirectResults? _directResults { get; set; }
 
         public HiveServer2Connection Connection { get; private set; }
@@ -328,6 +335,13 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
             ? batchSize
             : throw new ArgumentOutOfRangeException(key, value, $"The value 
'{value}' for option '{key}' is invalid. Must be a numeric value greater than 
zero.");
 
+        private string? EscapeUnderscoreInName(string? name)
+        {
+            if (!EscapeUnderscore || name == null)
+                return name;
+            return name.Replace("_", "\\_");
+        }
+
         public override void Dispose()
         {
             if (OperationHandle != null)
@@ -382,9 +396,9 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
                 null,
                 null,
                 null,
-                CatalogName,
-                SchemaName,
-                TableName,
+                EscapeUnderscoreInName(CatalogName),
+                EscapeUnderscoreInName(SchemaName),
+                EscapeUnderscoreInName(TableName),
                 cancellationToken);
             OperationHandle = resp.OperationHandle;
 
@@ -394,12 +408,12 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
         protected virtual async Task<QueryResult> 
GetCrossReferenceAsync(CancellationToken cancellationToken = default)
         {
             TGetCrossReferenceResp resp = await 
Connection.GetCrossReferenceAsync(
-                CatalogName,
-                SchemaName,
-                TableName,
-                ForeignCatalogName,
-                ForeignSchemaName,
-                ForeignTableName,
+                EscapeUnderscoreInName(CatalogName),
+                EscapeUnderscoreInName(SchemaName),
+                EscapeUnderscoreInName(TableName),
+                EscapeUnderscoreInName(ForeignCatalogName),
+                EscapeUnderscoreInName(ForeignSchemaName),
+                EscapeUnderscoreInName(ForeignTableName),
                 cancellationToken);
             OperationHandle = resp.OperationHandle;
 
@@ -409,9 +423,9 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
         protected virtual async Task<QueryResult> 
GetPrimaryKeysAsync(CancellationToken cancellationToken = default)
         {
             TGetPrimaryKeysResp resp = await Connection.GetPrimaryKeysAsync(
-                CatalogName,
-                SchemaName,
-                TableName,
+                EscapeUnderscoreInName(CatalogName),
+                EscapeUnderscoreInName(SchemaName),
+                EscapeUnderscoreInName(TableName),
                 cancellationToken);
             OperationHandle = resp.OperationHandle;
 
@@ -429,8 +443,8 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
         protected virtual async Task<QueryResult> 
GetSchemasAsync(CancellationToken cancellationToken = default)
         {
             TGetSchemasResp resp = await Connection.GetSchemasAsync(
-                CatalogName,
-                SchemaName,
+                EscapeUnderscoreInName(CatalogName),
+                EscapeUnderscoreInName(SchemaName),
                 cancellationToken);
             OperationHandle = resp.OperationHandle;
 
@@ -441,9 +455,9 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
         {
             List<string>? tableTypesList = 
this.TableTypes?.Split(',').ToList();
             TGetTablesResp resp = await Connection.GetTablesAsync(
-                CatalogName,
-                SchemaName,
-                TableName,
+                EscapeUnderscoreInName(CatalogName),
+                EscapeUnderscoreInName(SchemaName),
+                EscapeUnderscoreInName(TableName),
                 tableTypesList,
                 cancellationToken);
             OperationHandle = resp.OperationHandle;
@@ -454,10 +468,10 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
         protected virtual async Task<QueryResult> 
GetColumnsAsync(CancellationToken cancellationToken = default)
         {
             TGetColumnsResp resp = await Connection.GetColumnsAsync(
-                CatalogName,
-                SchemaName,
-                TableName,
-                ColumnName,
+                EscapeUnderscoreInName(CatalogName),
+                EscapeUnderscoreInName(SchemaName),
+                EscapeUnderscoreInName(TableName),
+                EscapeUnderscoreInName(ColumnName),
                 cancellationToken);
             OperationHandle = resp.OperationHandle;
 
diff --git a/csharp/test/Drivers/Databricks/StatementTests.cs 
b/csharp/test/Drivers/Databricks/StatementTests.cs
index cd5254a60..d6ac50e1d 100644
--- a/csharp/test/Drivers/Databricks/StatementTests.cs
+++ b/csharp/test/Drivers/Databricks/StatementTests.cs
@@ -343,6 +343,7 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Databricks
             statement.SetOption(ApacheParameters.CatalogName, 
TestConfiguration.Metadata.Catalog);
             statement.SetOption(ApacheParameters.SchemaName, 
TestConfiguration.Metadata.Schema);
             statement.SetOption(ApacheParameters.TableName, 
TestConfiguration.Metadata.Table);
+            statement.SetOption(ApacheParameters.EscapeUnderscore, "true");
             statement.SqlQuery = "GetColumnsExtended";
 
             QueryResult queryResult = await statement.ExecuteQueryAsync();

Reply via email to