Skip to content

Commit aa1053a

Browse files
authored
feat(csharp/src/Drivers/Apache): Add support for native metadata queries using statement options (apache#2665)
This feature adds support for making native metadata queries to data source. The caller sets statement options to determine the metadata command and parameters. ### Statement Options | Option | Description | Default | | :--- | :--- | :--- | | `adbc.apache.statement.is_metadata_command` | Indicate that the value of `AdbcStatement.SqlQuery` contains the name of a native metadata command. If set to `True`, it indicates a metadata command query whereas a value of `False` indicates a SQL command query. <br><br>Supported metadata commands include: `GetPrimaryKeys`, `GetCrossReference`, `GetCatalogs`, `GetSchemas`, `GetTables`, and `GetColumns`. | `False` | | `adbc.apache.catalog_name` | The catalog name (or pattern) when used with a metadata command query. <br><br>Supported metadata commands include: `GetPrimaryKeys`, `GetCrossReference`, `GetSchemas`, `GetTables`, and `GetColumns`. | | | `adbc.apache.schema_name` | The schema name (or pattern) when used with a metadata command query. <br><br>Supported metadata commands include: `GetPrimaryKeys`, `GetCrossReference`, `GetSchemas`, `GetTables`, and `GetColumns`. | | | `adbc.apache.table_name` | The table name (or pattern) when used with a metadata command query. <br><br>Supported metadata commands include: `GetPrimaryKeys`, `GetCrossReference`, `GetSchemas`, `GetTables`, and `GetColumns`. | | | `adbc.apache.table_types` | The comma-separated list of table types when used with a metadata command query. <br><br>Supported metadata commands include: `GetTables`. | | | `adbc.apache.column_name` | The column name (or pattern) when used with a metadata command query. <br><br>Supported metadata commands include: `GetColumns`. | | | `adbc.apache.foreign_catalog_name` | The foreign (i.e., child) catalog name (or pattern) when used with a metadata command query. <br><br>Supported metadata commands include: `GetCrossReference`. | | | `adbc.apache.foreign_schema_name` | The foreign (i.e., child) schema name (or pattern) when used with a metadata command query. <br><br>Supported metadata commands include: `GetCrossReference`. | | | `adbc.apache.foreign_table_name` | The foreign (i.e., child) table name (or pattern) when used with a metadata command query. <br><br>Supported metadata commands include: `GetCrossReference`. | |
1 parent 0a9d8c1 commit aa1053a

File tree

18 files changed

+1095
-22
lines changed

18 files changed

+1095
-22
lines changed

csharp/src/Drivers/Apache/ApacheParameters.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,55 @@ public class ApacheParameters
2525
public const string PollTimeMilliseconds = "adbc.apache.statement.polltime_ms";
2626
public const string BatchSize = "adbc.apache.statement.batch_size";
2727
public const string QueryTimeoutSeconds = "adbc.apache.statement.query_timeout_s";
28+
29+
/// <summary>
30+
/// The indicator of whether the <c>AdbcStatement.ExecuteQuery[Async]</c> should execute a metadata command query.
31+
/// In the case this indicator is set to <c>True</c>, the method will execute a metadata command using the native API where
32+
/// the name of the command is given in the <c>AdbcStatement.SqlQuery</c> property value.
33+
/// <para>
34+
/// Use the <c>adbc.get_metadata.*</c> options to set the input parameters for the native metadata command query.
35+
/// </para>
36+
/// </summary>
37+
public const string IsMetadataCommand = "adbc.apache.statement.is_metadata_command";
38+
39+
/// <summary>
40+
/// The catalog name (or pattern) of the table for GetSchemas, Get* metadata command queries.
41+
/// </summary>
42+
public const string CatalogName = "adbc.get_metadata.target_catalog";
43+
44+
/// <summary>
45+
/// The schema name (or pattern) of the table for GetSchemas, GetTables, ... metadata command queries.
46+
/// </summary>
47+
public const string SchemaName = "adbc.get_metadata.target_db_schema";
48+
49+
/// <summary>
50+
/// The table name (or pattern) of the table for GetSchemas, GetTables, ... metadata command queries.
51+
/// </summary>
52+
public const string TableName = "adbc.get_metadata.target_table";
53+
54+
/// <summary>
55+
/// The comma-separted list of the table types for GetTables metadata command query.
56+
/// </summary>
57+
public const string TableTypes = "adbc.get_metadata.target_table_types";
58+
59+
/// <summary>
60+
/// The column name (or pattern) in the table for GetColumns metadata command query.
61+
/// </summary>
62+
public const string ColumnName = "adbc.get_metadata.target_column";
63+
64+
/// <summary>
65+
/// The catalog name (or pattern) of the foreign (child) table for GetCrossReference metadata command query.
66+
/// </summary>
67+
public const string ForeignCatalogName = "adbc.get_metadata.foreign_target_catalog";
68+
69+
/// <summary>
70+
/// The schema name (or pattern) of the foreign (child) table for GetCrossReference metadata command query.
71+
/// </summary>
72+
public const string ForeignSchemaName = "adbc.get_metadata.foreign_target_db_schema";
73+
74+
/// <summary>
75+
/// The table name (or pattern) of the foreign (child) table for GetCrossReference metadata command query.
76+
/// </summary>
77+
public const string ForeignTableName = "adbc.get_metadata.foreign_target_table";
2878
}
2979
}

csharp/src/Drivers/Apache/ApacheUtility.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ public static bool QueryTimeoutIsValid(string key, string value, out int queryTi
7474
}
7575
}
7676

77+
public static bool BooleanIsValid(string key, string value, out bool booleanValue)
78+
{
79+
if (bool.TryParse(value, out booleanValue))
80+
{
81+
return true;
82+
}
83+
else
84+
{
85+
throw new ArgumentOutOfRangeException(key, nameof(value), $"Invalid value for {key}: {value}. Expected a boolean value.");
86+
}
87+
}
88+
7789
public static bool ContainsException<T>(Exception exception, out T? containedException) where T : Exception
7890
{
7991
if (exception is AggregateException aggregateException)

csharp/src/Drivers/Apache/Hive2/HiveServer2Connection.cs

Lines changed: 252 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ internal struct ColumnsMetadataColumnNames
8383
public string TableName { get; internal set; }
8484
public string ColumnName { get; internal set; }
8585
public string DataType { get; internal set; }
86-
public string TypeName { get; internal set; }
86+
public string TypeName { get; internal set; }
8787
public string Nullable { get; internal set; }
8888
public string ColumnDef { get; internal set; }
8989
public string OrdinalPosition { get; internal set; }
@@ -779,12 +779,14 @@ protected IReadOnlyDictionary<string, int> GetColumnIndexMap(List<TColumnDesc> c
779779
protected abstract Task<TRowSet> GetRowSetAsync(TGetTablesResp response, CancellationToken cancellationToken = default);
780780
protected abstract Task<TRowSet> GetRowSetAsync(TGetCatalogsResp getCatalogsResp, CancellationToken cancellationToken = default);
781781
protected abstract Task<TRowSet> GetRowSetAsync(TGetSchemasResp getSchemasResp, CancellationToken cancellationToken = default);
782+
protected internal abstract Task<TRowSet> GetRowSetAsync(TGetPrimaryKeysResp response, CancellationToken cancellationToken = default);
782783
protected abstract Task<TGetResultSetMetadataResp> GetResultSetMetadataAsync(TGetSchemasResp response, CancellationToken cancellationToken = default);
783784
protected abstract Task<TGetResultSetMetadataResp> GetResultSetMetadataAsync(TGetCatalogsResp response, CancellationToken cancellationToken = default);
784785
protected abstract Task<TGetResultSetMetadataResp> GetResultSetMetadataAsync(TGetColumnsResp response, CancellationToken cancellationToken = default);
785786
protected abstract Task<TGetResultSetMetadataResp> GetResultSetMetadataAsync(TGetTablesResp response, CancellationToken cancellationToken = default);
787+
protected internal abstract Task<TGetResultSetMetadataResp> GetResultSetMetadataAsync(TGetPrimaryKeysResp response, CancellationToken cancellationToken = default);
786788

787-
protected virtual bool AreResultsAvailableDirectly() => false;
789+
protected internal virtual bool AreResultsAvailableDirectly() => false;
788790

789791
protected virtual void SetDirectResults(TGetColumnsReq request) => throw new System.NotImplementedException();
790792

@@ -796,6 +798,10 @@ protected IReadOnlyDictionary<string, int> GetColumnIndexMap(List<TColumnDesc> c
796798

797799
protected virtual void SetDirectResults(TGetTableTypesReq request) => throw new System.NotImplementedException();
798800

801+
protected virtual void SetDirectResults(TGetPrimaryKeysReq request) => throw new System.NotImplementedException();
802+
803+
protected virtual void SetDirectResults(TGetCrossReferenceReq request) => throw new System.NotImplementedException();
804+
799805
protected internal abstract int PositionRequiredOffset { get; }
800806

801807
protected abstract string InfoDriverName { get; }
@@ -804,7 +810,7 @@ protected IReadOnlyDictionary<string, int> GetColumnIndexMap(List<TColumnDesc> c
804810

805811
protected abstract string ProductVersion { get; }
806812

807-
protected abstract bool GetObjectsPatternsRequireLowerCase { get; }
813+
protected abstract bool GetObjectsPatternsRequireLowerCase { get; }
808814

809815
protected abstract bool IsColumnSizeValidForDecimal { get; }
810816

@@ -909,6 +915,249 @@ private static StructArray GetTableSchemas(
909915
nullBitmapBuffer.Build());
910916
}
911917

918+
internal async Task<TGetCatalogsResp> GetCatalogsAsync(CancellationToken cancellationToken)
919+
{
920+
if (SessionHandle == null)
921+
{
922+
throw new InvalidOperationException("Invalid session");
923+
}
924+
925+
TGetCatalogsReq req = new TGetCatalogsReq(SessionHandle);
926+
if (AreResultsAvailableDirectly())
927+
{
928+
SetDirectResults(req);
929+
}
930+
931+
TGetCatalogsResp resp = await Client.GetCatalogs(req, cancellationToken);
932+
if (resp.Status.StatusCode != TStatusCode.SUCCESS_STATUS)
933+
{
934+
throw new HiveServer2Exception(resp.Status.ErrorMessage)
935+
.SetNativeError(resp.Status.ErrorCode)
936+
.SetSqlState(resp.Status.SqlState);
937+
}
938+
939+
return resp;
940+
}
941+
942+
internal async Task<TGetSchemasResp> GetSchemasAsync(
943+
string? catalogName,
944+
string? schemaName,
945+
CancellationToken cancellationToken)
946+
{
947+
if (SessionHandle == null)
948+
{
949+
throw new InvalidOperationException("Invalid session");
950+
}
951+
952+
TGetSchemasReq req = new(SessionHandle);
953+
if (AreResultsAvailableDirectly())
954+
{
955+
SetDirectResults(req);
956+
}
957+
if (catalogName != null)
958+
{
959+
req.CatalogName = catalogName;
960+
}
961+
if (schemaName != null)
962+
{
963+
req.SchemaName = schemaName;
964+
}
965+
966+
TGetSchemasResp resp = await Client.GetSchemas(req, cancellationToken);
967+
if (resp.Status.StatusCode != TStatusCode.SUCCESS_STATUS)
968+
{
969+
throw new HiveServer2Exception(resp.Status.ErrorMessage)
970+
.SetNativeError(resp.Status.ErrorCode)
971+
.SetSqlState(resp.Status.SqlState);
972+
}
973+
974+
return resp;
975+
}
976+
977+
internal async Task<TGetTablesResp> GetTablesAsync(
978+
string? catalogName,
979+
string? schemaName,
980+
string? tableName,
981+
List<string>? tableTypes,
982+
CancellationToken cancellationToken)
983+
{
984+
if (SessionHandle == null)
985+
{
986+
throw new InvalidOperationException("Invalid session");
987+
}
988+
989+
TGetTablesReq req = new(SessionHandle);
990+
if (AreResultsAvailableDirectly())
991+
{
992+
SetDirectResults(req);
993+
}
994+
if (catalogName != null)
995+
{
996+
req.CatalogName = catalogName;
997+
}
998+
if (schemaName != null)
999+
{
1000+
req.SchemaName = schemaName;
1001+
}
1002+
if (tableName != null)
1003+
{
1004+
req.TableName = tableName;
1005+
}
1006+
if (tableTypes != null && tableTypes.Count > 0)
1007+
{
1008+
req.TableTypes = tableTypes;
1009+
}
1010+
1011+
TGetTablesResp resp = await Client.GetTables(req, cancellationToken);
1012+
if (resp.Status.StatusCode != TStatusCode.SUCCESS_STATUS)
1013+
{
1014+
throw new HiveServer2Exception(resp.Status.ErrorMessage)
1015+
.SetNativeError(resp.Status.ErrorCode)
1016+
.SetSqlState(resp.Status.SqlState);
1017+
}
1018+
1019+
return resp;
1020+
}
1021+
1022+
internal async Task<TGetColumnsResp> GetColumnsAsync(
1023+
string? catalogName,
1024+
string? schemaName,
1025+
string? tableName,
1026+
string? columnName,
1027+
CancellationToken cancellationToken)
1028+
{
1029+
if (SessionHandle == null)
1030+
{
1031+
throw new InvalidOperationException("Invalid session");
1032+
}
1033+
1034+
TGetColumnsReq req = new(SessionHandle);
1035+
if (AreResultsAvailableDirectly())
1036+
{
1037+
SetDirectResults(req);
1038+
}
1039+
if (catalogName != null)
1040+
{
1041+
req.CatalogName = catalogName;
1042+
}
1043+
if (schemaName != null)
1044+
{
1045+
req.SchemaName = schemaName;
1046+
}
1047+
if (tableName != null)
1048+
{
1049+
req.TableName = tableName;
1050+
}
1051+
if (columnName != null)
1052+
{
1053+
req.ColumnName = columnName;
1054+
}
1055+
1056+
TGetColumnsResp resp = await Client.GetColumns(req, cancellationToken);
1057+
if (resp.Status.StatusCode != TStatusCode.SUCCESS_STATUS)
1058+
{
1059+
throw new HiveServer2Exception(resp.Status.ErrorMessage)
1060+
.SetNativeError(resp.Status.ErrorCode)
1061+
.SetSqlState(resp.Status.SqlState);
1062+
}
1063+
1064+
return resp;
1065+
}
1066+
1067+
internal async Task<TGetPrimaryKeysResp> GetPrimaryKeysAsync(
1068+
string? catalogName,
1069+
string? schemaName,
1070+
string? tableName,
1071+
CancellationToken cancellationToken = default)
1072+
{
1073+
if (SessionHandle == null)
1074+
{
1075+
throw new InvalidOperationException("Invalid session");
1076+
}
1077+
1078+
TGetPrimaryKeysReq req = new(SessionHandle);
1079+
if (AreResultsAvailableDirectly())
1080+
{
1081+
SetDirectResults(req);
1082+
}
1083+
if (catalogName != null)
1084+
{
1085+
req.CatalogName = catalogName!;
1086+
}
1087+
if (schemaName != null)
1088+
{
1089+
req.SchemaName = schemaName!;
1090+
}
1091+
if (tableName != null)
1092+
{
1093+
req.TableName = tableName!;
1094+
}
1095+
1096+
TGetPrimaryKeysResp resp = await Client.GetPrimaryKeys(req, cancellationToken);
1097+
if (resp.Status.StatusCode != TStatusCode.SUCCESS_STATUS)
1098+
{
1099+
throw new HiveServer2Exception(resp.Status.ErrorMessage)
1100+
.SetNativeError(resp.Status.ErrorCode)
1101+
.SetSqlState(resp.Status.SqlState);
1102+
}
1103+
1104+
return resp;
1105+
}
1106+
1107+
internal async Task<TGetCrossReferenceResp> GetCrossReferenceAsync(
1108+
string? catalogName,
1109+
string? schemaName,
1110+
string? tableName,
1111+
string? foreignCatalogName,
1112+
string? foreignSchemaName,
1113+
string? foreignTableName,
1114+
CancellationToken cancellationToken = default)
1115+
{
1116+
if (SessionHandle == null)
1117+
{
1118+
throw new InvalidOperationException("Invalid session");
1119+
}
1120+
1121+
TGetCrossReferenceReq req = new(SessionHandle);
1122+
if (AreResultsAvailableDirectly())
1123+
{
1124+
SetDirectResults(req);
1125+
}
1126+
if (catalogName != null)
1127+
{
1128+
req.ParentCatalogName = catalogName!;
1129+
}
1130+
if (schemaName != null)
1131+
{
1132+
req.ParentSchemaName = schemaName!;
1133+
}
1134+
if (tableName != null)
1135+
{
1136+
req.ParentTableName = tableName!;
1137+
}
1138+
if (foreignCatalogName != null)
1139+
{
1140+
req.ForeignCatalogName = foreignCatalogName!;
1141+
}
1142+
if (schemaName != null)
1143+
{
1144+
req.ForeignSchemaName = foreignSchemaName!;
1145+
}
1146+
if (tableName != null)
1147+
{
1148+
req.ForeignTableName = foreignTableName!;
1149+
}
1150+
1151+
TGetCrossReferenceResp resp = await Client.GetCrossReference(req, cancellationToken);
1152+
if (resp.Status.StatusCode != TStatusCode.SUCCESS_STATUS)
1153+
{
1154+
throw new HiveServer2Exception(resp.Status.ErrorMessage)
1155+
.SetNativeError(resp.Status.ErrorCode)
1156+
.SetSqlState(resp.Status.SqlState);
1157+
}
1158+
return resp;
1159+
}
1160+
9121161
private static StructArray GetColumnSchema(TableInfo tableInfo)
9131162
{
9141163
StringArray.Builder columnNameBuilder = new StringArray.Builder();

csharp/src/Drivers/Apache/Hive2/HiveServer2HttpConnection.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,8 @@ protected override Task<TGetResultSetMetadataResp> GetResultSetMetadataAsync(TGe
301301
GetResultSetMetadataAsync(response.OperationHandle, Client, cancellationToken);
302302
protected override Task<TGetResultSetMetadataResp> GetResultSetMetadataAsync(TGetTablesResp response, CancellationToken cancellationToken = default) =>
303303
GetResultSetMetadataAsync(response.OperationHandle, Client, cancellationToken);
304+
protected internal override Task<TGetResultSetMetadataResp> GetResultSetMetadataAsync(TGetPrimaryKeysResp response, CancellationToken cancellationToken = default) =>
305+
GetResultSetMetadataAsync(response.OperationHandle, Client, cancellationToken);
304306
protected override Task<TRowSet> GetRowSetAsync(TGetTableTypesResp response, CancellationToken cancellationToken = default) =>
305307
FetchResultsAsync(response.OperationHandle, cancellationToken: cancellationToken);
306308
protected override Task<TRowSet> GetRowSetAsync(TGetColumnsResp response, CancellationToken cancellationToken = default) =>
@@ -311,6 +313,8 @@ protected override Task<TRowSet> GetRowSetAsync(TGetCatalogsResp response, Cance
311313
FetchResultsAsync(response.OperationHandle, cancellationToken: cancellationToken);
312314
protected override Task<TRowSet> GetRowSetAsync(TGetSchemasResp response, CancellationToken cancellationToken = default) =>
313315
FetchResultsAsync(response.OperationHandle, cancellationToken: cancellationToken);
316+
protected internal override Task<TRowSet> GetRowSetAsync(TGetPrimaryKeysResp response, CancellationToken cancellationToken = default) =>
317+
FetchResultsAsync(response.OperationHandle, cancellationToken: cancellationToken);
314318

315319
protected internal override int PositionRequiredOffset => 0;
316320

0 commit comments

Comments
 (0)