Skip to content

Commit 4110133

Browse files
msrathore-dbclaude
andcommitted
feat(csharp): implement SEA metadata — GetObjects, GetTableSchema, GetTableTypes
Core SEA metadata implementation with connection-level and statement-level support: Connection-level: - Implement IGetObjectsDataProvider for SEA (GetCatalogs, GetSchemas, GetTables, PopulateColumnInfo via SHOW SQL commands) - Implement GetObjects using shared GetObjectsResultBuilder - Implement GetTableSchema via SHOW COLUMNS + shared GetArrowType - Implement GetTableTypes returning TABLE and VIEW - Implement GetInfo via shared MetadataSchemaFactory.BuildGetInfoResult - Add CancellationToken support with configurable timeout - Add activity tracing on all metadata methods Statement-level: - Add metadata command routing (GetCatalogs/Schemas/Tables/Columns/ ColumnsExtended/PrimaryKeys/CrossReference/TableTypes) - Reuse DatabricksStatement.CreateExtendedColumnsResult for GetColumnsExtended - Wire NormalizeSparkCatalog for SPARK catalog compatibility New files: - MetadataCommandBase + Show*Command classes (SQL builders) - MetadataUtilities (catalog normalization, PK/FK validation) - Refactor DescTableExtendedResult to use shared ColumnMetadataHelper - Wire DatabricksStatement to shared MetadataSchemaFactory Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4780d28 commit 4110133

11 files changed

+719
-285
lines changed

csharp/src/DatabricksStatement.cs

Lines changed: 13 additions & 175 deletions
Large diffs are not rendered by default.

csharp/src/MetadataUtilities.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (c) 2025 ADBC Drivers Contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using System;
18+
19+
namespace AdbcDrivers.Databricks
20+
{
21+
internal static class MetadataUtilities
22+
{
23+
internal static string? NormalizeSparkCatalog(string? catalogName)
24+
{
25+
if (string.IsNullOrEmpty(catalogName))
26+
return catalogName;
27+
28+
if (string.Equals(catalogName, "SPARK", StringComparison.OrdinalIgnoreCase))
29+
return null;
30+
31+
return catalogName;
32+
}
33+
34+
internal static bool IsInvalidPKFKCatalog(string? catalogName)
35+
{
36+
return string.IsNullOrEmpty(catalogName) ||
37+
string.Equals(catalogName, "SPARK", StringComparison.OrdinalIgnoreCase) ||
38+
string.Equals(catalogName, "hive_metastore", StringComparison.OrdinalIgnoreCase);
39+
}
40+
41+
internal static bool ShouldReturnEmptyPKFKResult(string? catalogName, string? foreignCatalogName, bool enablePKFK)
42+
{
43+
if (!enablePKFK)
44+
return true;
45+
46+
return IsInvalidPKFKCatalog(catalogName) && IsInvalidPKFKCatalog(foreignCatalogName);
47+
}
48+
49+
internal static string? BuildQualifiedTableName(string? catalogName, string? schemaName, string? tableName)
50+
{
51+
if (string.IsNullOrEmpty(tableName))
52+
return tableName;
53+
54+
var parts = new System.Collections.Generic.List<string>();
55+
56+
if (!string.IsNullOrEmpty(schemaName))
57+
{
58+
if (!string.IsNullOrEmpty(catalogName) && !catalogName!.Equals("SPARK", StringComparison.OrdinalIgnoreCase))
59+
{
60+
parts.Add($"`{catalogName.Replace("`", "``")}`");
61+
}
62+
parts.Add($"`{schemaName!.Replace("`", "``")}`");
63+
}
64+
65+
parts.Add($"`{tableName!.Replace("`", "``")}`");
66+
67+
return string.Join(".", parts);
68+
}
69+
}
70+
}

csharp/src/Result/DescTableExtendedResult.cs

Lines changed: 19 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
using System.Collections.Generic;
2626
using System.Text.Json.Serialization;
2727
using System.Text.RegularExpressions;
28+
using AdbcDrivers.HiveServer2.Hive2;
2829
using static AdbcDrivers.HiveServer2.Hive2.HiveServer2Connection;
2930

3031
namespace AdbcDrivers.Databricks.Result
@@ -88,112 +89,38 @@ public ColumnTypeId DataType
8889
{
8990
get
9091
{
91-
string normalizedTypeName = Type.Name.Trim().ToUpper();
92-
93-
return normalizedTypeName switch
94-
{
95-
"BOOLEAN" => ColumnTypeId.BOOLEAN,
96-
"TINYINT" or "BYTE" => ColumnTypeId.TINYINT,
97-
"SMALLINT" or "SHORT" => ColumnTypeId.SMALLINT,
98-
"INT" or "INTEGER" => ColumnTypeId.INTEGER,
99-
"BIGINT" or "LONG" => ColumnTypeId.BIGINT,
100-
"FLOAT" or "REAL" => ColumnTypeId.FLOAT,
101-
"DOUBLE" => ColumnTypeId.DOUBLE,
102-
"DECIMAL" or "NUMERIC" => ColumnTypeId.DECIMAL,
103-
104-
"CHAR" => ColumnTypeId.CHAR,
105-
"STRING" or "VARCHAR" => ColumnTypeId.VARCHAR,
106-
"BINARY" => ColumnTypeId.BINARY,
107-
108-
"TIMESTAMP" => ColumnTypeId.TIMESTAMP,
109-
"TIMESTAMP_LTZ" => ColumnTypeId.TIMESTAMP,
110-
"TIMESTAMP_NTZ" => ColumnTypeId.TIMESTAMP,
111-
"DATE" => ColumnTypeId.DATE,
112-
113-
"ARRAY" => ColumnTypeId.ARRAY,
114-
"MAP" => ColumnTypeId.JAVA_OBJECT,
115-
"STRUCT" => ColumnTypeId.STRUCT,
116-
"INTERVAL" => ColumnTypeId.OTHER, // Intervals don't have a direct JDBC mapping
117-
"VOID" => ColumnTypeId.NULL,
118-
"VARIANT" => ColumnTypeId.OTHER,
119-
_ => ColumnTypeId.OTHER // Default fallback for unknown types
120-
};
92+
var code = (ColumnTypeId)ColumnMetadataHelper.GetDataTypeCode(Type.Name);
93+
// REAL maps to FLOAT for backward compatibility
94+
return code == ColumnTypeId.REAL ? ColumnTypeId.FLOAT : code;
12195
}
12296
}
12397

12498
[JsonIgnore]
125-
public bool IsNumber
126-
{
127-
get
128-
{
129-
return DataType switch
130-
{
131-
ColumnTypeId.TINYINT or ColumnTypeId.SMALLINT or ColumnTypeId.INTEGER or
132-
ColumnTypeId.BIGINT or ColumnTypeId.FLOAT or ColumnTypeId.DOUBLE or
133-
ColumnTypeId.DECIMAL or ColumnTypeId.NUMERIC => true,
134-
_ => false
135-
};
136-
}
137-
}
99+
public bool IsNumber => ColumnMetadataHelper.GetNumPrecRadix(Type.Name) != null;
138100

139101
[JsonIgnore]
140-
public int DecimalDigits
141-
{
142-
get
143-
{
144-
return DataType switch
145-
{
146-
ColumnTypeId.DECIMAL or ColumnTypeId.NUMERIC => Type.Scale ?? 0,
147-
ColumnTypeId.DOUBLE => 15,
148-
ColumnTypeId.FLOAT or ColumnTypeId.REAL => 7,
149-
ColumnTypeId.TIMESTAMP => 6,
150-
_ => 0
151-
};
152-
}
153-
}
102+
public int DecimalDigits => ColumnMetadataHelper.GetDecimalDigitsDefault(Type.FullTypeName) ?? 0;
154103

155-
/// <summary>
156-
/// Get column size
157-
///
158-
/// Currently the query `DESC TABLE EXTNEDED AS JSON` does not return the column size,
159-
/// we can calculate it based on the data type and some type specific properties
160-
/// </summary>
161104
[JsonIgnore]
162105
public int? ColumnSize
163106
{
164107
get
165108
{
166-
return DataType switch
109+
// For INTERVAL types, FullTypeName may not include the qualifier
110+
// when only StartUnit is set. Use StartUnit directly in that case.
111+
if (Type.Name.Trim().Equals("INTERVAL", StringComparison.OrdinalIgnoreCase)
112+
&& !string.IsNullOrEmpty(Type.StartUnit)
113+
&& Type.EndUnit == null)
167114
{
168-
ColumnTypeId.TINYINT or ColumnTypeId.BOOLEAN => 1,
169-
ColumnTypeId.SMALLINT => 2,
170-
ColumnTypeId.INTEGER or ColumnTypeId.FLOAT or ColumnTypeId.DATE => 4,
171-
ColumnTypeId.BIGINT or ColumnTypeId.DOUBLE or ColumnTypeId.TIMESTAMP or ColumnTypeId.TIMESTAMP_WITH_TIMEZONE => 8,
172-
ColumnTypeId.CHAR => Type.Length,
173-
ColumnTypeId.VARCHAR => Type.Name.Trim().ToUpper() == "STRING" ? int.MaxValue : Type.Length,
174-
ColumnTypeId.DECIMAL => Type.Precision ?? 0,
175-
ColumnTypeId.NULL => 1,
176-
_ => Type.Name.Trim().ToUpper() == "INTERVAL" ? GetIntervalSize() : 0
177-
};
178-
}
179-
}
180-
181-
private int GetIntervalSize()
182-
{
183-
if (String.IsNullOrEmpty(Type.StartUnit))
184-
{
185-
return 0;
115+
return Type.StartUnit!.ToUpper() switch
116+
{
117+
"YEAR" or "MONTH" => 4,
118+
"DAY" or "HOUR" or "MINUTE" or "SECOND" => 8,
119+
_ => 4
120+
};
121+
}
122+
return ColumnMetadataHelper.GetColumnSizeDefault(Type.FullTypeName);
186123
}
187-
188-
// Check whether interval is yearMonthIntervalQualifier or dayTimeIntervalQualifier
189-
// yearMonthIntervalQualifier size is 4, dayTimeIntervalQualifier size is 8
190-
// see https://docs.databricks.com/aws/en/sql/language-manual/data-types/interval-type
191-
return Type.StartUnit!.ToUpper() switch
192-
{
193-
"YEAR" or "MONTH" => 4,
194-
"DAY" or "HOUR" or "MINUTE" or "SECOND" => 8,
195-
_ => 4
196-
};
197124
}
198125
}
199126

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright (c) 2025 ADBC Drivers Contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using System.Text;
18+
19+
namespace AdbcDrivers.Databricks.StatementExecution
20+
{
21+
internal abstract class MetadataCommandBase
22+
{
23+
protected const string InAllCatalogs = " IN ALL CATALOGS";
24+
protected const string LikeFormat = " LIKE '{0}'";
25+
protected const string SchemaLikeFormat = " SCHEMA LIKE '{0}'";
26+
protected const string TableLikeFormat = " TABLE LIKE '{0}'";
27+
protected const string InCatalogFormat = " IN CATALOG {0}";
28+
protected const string InSchemaFormat = " IN SCHEMA {0}";
29+
protected const string InTableFormat = " IN TABLE {0}";
30+
31+
public abstract string Build();
32+
33+
protected static string QuoteIdentifier(string identifier)
34+
{
35+
return $"`{identifier.Replace("`", "``")}`";
36+
}
37+
38+
protected static string ConvertPattern(string? pattern)
39+
{
40+
if (string.IsNullOrEmpty(pattern))
41+
return "*";
42+
43+
var result = new StringBuilder(pattern!.Length);
44+
bool escapeNext = false;
45+
46+
for (int i = 0; i < pattern.Length; i++)
47+
{
48+
char c = pattern[i];
49+
50+
if (c == '\\')
51+
{
52+
if (i + 1 < pattern.Length && pattern[i + 1] == '\\')
53+
{
54+
result.Append("\\\\");
55+
i++;
56+
}
57+
else
58+
{
59+
escapeNext = !escapeNext;
60+
if (!escapeNext)
61+
result.Append('\\');
62+
}
63+
}
64+
else if (escapeNext)
65+
{
66+
result.Append(c);
67+
escapeNext = false;
68+
}
69+
else if (c == '%')
70+
{
71+
result.Append('*');
72+
}
73+
else if (c == '_')
74+
{
75+
result.Append('.');
76+
}
77+
else if (c == '\'')
78+
{
79+
result.Append("''");
80+
}
81+
else
82+
{
83+
result.Append(c);
84+
}
85+
}
86+
87+
return result.ToString();
88+
}
89+
90+
protected static void AppendCatalogScope(StringBuilder sql, string? catalog)
91+
{
92+
if (catalog == null)
93+
sql.Append(InAllCatalogs);
94+
else
95+
sql.Append(string.Format(InCatalogFormat, QuoteIdentifier(catalog)));
96+
}
97+
}
98+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) 2025 ADBC Drivers Contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using System.Text;
18+
19+
namespace AdbcDrivers.Databricks.StatementExecution
20+
{
21+
internal class ShowCatalogsCommand : MetadataCommandBase
22+
{
23+
private readonly string? _catalogPattern;
24+
25+
public ShowCatalogsCommand(string? catalogPattern = null)
26+
{
27+
_catalogPattern = catalogPattern;
28+
}
29+
30+
public override string Build()
31+
{
32+
var sql = new StringBuilder("SHOW CATALOGS");
33+
if (_catalogPattern != null)
34+
sql.Append(string.Format(LikeFormat, ConvertPattern(_catalogPattern)));
35+
return sql.ToString();
36+
}
37+
}
38+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) 2025 ADBC Drivers Contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using System.Text;
18+
19+
namespace AdbcDrivers.Databricks.StatementExecution
20+
{
21+
internal class ShowColumnsCommand : MetadataCommandBase
22+
{
23+
private readonly string? _catalog;
24+
private readonly string? _schemaPattern;
25+
private readonly string? _tablePattern;
26+
private readonly string? _columnPattern;
27+
28+
public ShowColumnsCommand(string? catalog, string? schemaPattern = null,
29+
string? tablePattern = null, string? columnPattern = null)
30+
{
31+
_catalog = catalog;
32+
_schemaPattern = schemaPattern;
33+
_tablePattern = tablePattern;
34+
_columnPattern = columnPattern;
35+
}
36+
37+
public override string Build()
38+
{
39+
var sql = new StringBuilder("SHOW COLUMNS");
40+
AppendCatalogScope(sql, _catalog);
41+
if (_schemaPattern != null)
42+
sql.Append(string.Format(SchemaLikeFormat, ConvertPattern(_schemaPattern)));
43+
if (_tablePattern != null)
44+
sql.Append(string.Format(TableLikeFormat, ConvertPattern(_tablePattern)));
45+
if (_columnPattern != null && _columnPattern != "%")
46+
sql.Append(string.Format(LikeFormat, ConvertPattern(_columnPattern)));
47+
return sql.ToString();
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)