Skip to content

Commit d333690

Browse files
authored
Implement Connection.GetSchema (#185)
* WIP Connection.GetSchema * Code cleanup * Implement ReservedWords * Ooops * Fix tests * Add test for ArgumentOutOfRangeException * Refactoring * Use DbMetaDataCollectionNames && DbMetaDataColumnNames where possible * Remove redundant code * Implement Columns schema * Add Columns schema metadata * Add ForeignKeys schema * Implement Indexes metadata * Fix indexes test * Use constant * Extract restrictions arrays
1 parent e6fb730 commit d333690

File tree

4 files changed

+421
-0
lines changed

4 files changed

+421
-0
lines changed

DuckDB.NET.Data/DuckDBConnection.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,13 @@ public DuckDBConnection Duplicate()
215215

216216
return duplicatedConnection;
217217
}
218+
219+
public override DataTable GetSchema() =>
220+
GetSchema(DbMetaDataCollectionNames.MetaDataCollections);
221+
222+
public override DataTable GetSchema(string collectionName) =>
223+
GetSchema(collectionName, null);
224+
225+
public override DataTable GetSchema(string collectionName, string?[]? restrictionValues) =>
226+
DuckDBSchema.GetSchema(this, collectionName, restrictionValues);
218227
}

DuckDB.NET.Data/DuckDBSchema.cs

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
using System;
2+
using System.Data;
3+
using System.Data.Common;
4+
using System.Linq;
5+
using System.Text;
6+
7+
namespace DuckDB.NET.Data;
8+
9+
internal static class DuckDBSchema
10+
{
11+
private static readonly string[] TableRestrictions =
12+
["table_catalog", "table_schema", "table_name", "table_type"];
13+
14+
private static readonly string[] ColumnRestrictions =
15+
["table_catalog", "table_schema", "table_name", "column_name"];
16+
17+
private static readonly string[] ForeignKeyRestrictions =
18+
["constraint_catalog", "constraint_schema", "table_name", "constraint_name"];
19+
20+
private static readonly string[] IndexesRestrictions =
21+
["index_catalog", "index_schema", "table_name", "index_name"];
22+
23+
public static DataTable GetSchema(DuckDBConnection connection, string collectionName, string?[]? restrictionValues) =>
24+
collectionName.ToUpperInvariant() switch
25+
{
26+
"METADATACOLLECTIONS" => GetMetaDataCollections(),
27+
"RESTRICTIONS" => GetRestrictions(),
28+
"RESERVEDWORDS" => GetReservedWords(connection),
29+
"TABLES" => GetTables(connection, restrictionValues),
30+
"COLUMNS" => GetColumns(connection, restrictionValues),
31+
"FOREIGNKEYS" => GetForeignKeys(connection, restrictionValues),
32+
"INDEXES" => GetIndexes(connection, restrictionValues),
33+
_ => throw new ArgumentOutOfRangeException(nameof(collectionName), collectionName, "Invalid collection name.")
34+
};
35+
36+
private static DataTable GetMetaDataCollections() =>
37+
new(DbMetaDataCollectionNames.MetaDataCollections)
38+
{
39+
Columns =
40+
{
41+
{ DbMetaDataColumnNames.CollectionName, typeof(string) },
42+
{ DbMetaDataColumnNames.NumberOfRestrictions, typeof(int) },
43+
{ DbMetaDataColumnNames.NumberOfIdentifierParts, typeof(int) }
44+
},
45+
Rows =
46+
{
47+
{ DbMetaDataCollectionNames.MetaDataCollections, 0, 0 },
48+
{ DbMetaDataCollectionNames.Restrictions, 0, 0 },
49+
{ DbMetaDataCollectionNames.ReservedWords, 0, 0 },
50+
{ DuckDbMetaDataCollectionNames.Tables, TableRestrictions.Length, 3 },
51+
{ DuckDbMetaDataCollectionNames.Columns, ColumnRestrictions.Length, 4 },
52+
{ DuckDbMetaDataCollectionNames.ForeignKeys, ForeignKeyRestrictions.Length, 3 },
53+
{ DuckDbMetaDataCollectionNames.Indexes, IndexesRestrictions.Length, 3 },
54+
}
55+
};
56+
57+
private static DataTable GetRestrictions() =>
58+
new(DbMetaDataCollectionNames.Restrictions)
59+
{
60+
Columns =
61+
{
62+
{ "CollectionName", typeof(string) },
63+
{ "RestrictionName", typeof(string) },
64+
{ "RestrictionDefault", typeof(string) },
65+
{ "RestrictionNumber", typeof(int) }
66+
},
67+
Rows =
68+
{
69+
{ DuckDbMetaDataCollectionNames.Tables, "Catalog", "table_catalog", 1 },
70+
{ DuckDbMetaDataCollectionNames.Tables, "Schema", "table_schema", 2 },
71+
{ DuckDbMetaDataCollectionNames.Tables, "Table", "table_name", 3 },
72+
{ DuckDbMetaDataCollectionNames.Tables, "TableType", "table_type", 4 },
73+
74+
{ DuckDbMetaDataCollectionNames.Columns, "Catalog", "table_catalog", 1 },
75+
{ DuckDbMetaDataCollectionNames.Columns, "Schema", "table_schema", 2 },
76+
{ DuckDbMetaDataCollectionNames.Columns, "Table", "table_name", 3 },
77+
{ DuckDbMetaDataCollectionNames.Columns, "Column", "column_name", 4 },
78+
79+
{ DuckDbMetaDataCollectionNames.ForeignKeys, "Catalog", "constraint_catalog", 1 },
80+
{ DuckDbMetaDataCollectionNames.ForeignKeys, "Schema", "constraint_schema", 2 },
81+
{ DuckDbMetaDataCollectionNames.ForeignKeys, "Table", "table_name", 3 },
82+
{ DuckDbMetaDataCollectionNames.ForeignKeys, "Constraint", "constraint_name", 4 },
83+
84+
{ DuckDbMetaDataCollectionNames.Indexes, "Catalog", "constraint_catalog", 1 },
85+
{ DuckDbMetaDataCollectionNames.Indexes, "Schema", "constraint_schema", 2 },
86+
{ DuckDbMetaDataCollectionNames.Indexes, "Table", "table_name", 3 },
87+
{ DuckDbMetaDataCollectionNames.Indexes, "Constraint", "constraint_name", 4 },
88+
},
89+
};
90+
91+
private static DataTable GetReservedWords(DuckDBConnection connection)
92+
{
93+
using var command = connection.CreateCommand();
94+
command.CommandText = "SELECT keyword_name as ReservedWord FROM duckdb_keywords() WHERE keyword_category = 'reserved'";
95+
command.CommandType = CommandType.Text;
96+
return GetDataTable(DbMetaDataCollectionNames.ReservedWords, command);
97+
}
98+
99+
private static DataTable GetTables(DuckDBConnection connection, string?[]? restrictionValues)
100+
{
101+
if (restrictionValues?.Length > TableRestrictions.Length)
102+
throw new ArgumentException("Too many restrictions", nameof(restrictionValues));
103+
104+
const string query = "SELECT table_catalog, table_schema, table_name, table_type FROM information_schema.tables";
105+
106+
using var command = BuildCommand(connection, query, restrictionValues, true, TableRestrictions);
107+
108+
return GetDataTable(DuckDbMetaDataCollectionNames.Tables, command);
109+
}
110+
111+
private static DataTable GetColumns(DuckDBConnection connection, string?[]? restrictionValues)
112+
{
113+
if (restrictionValues?.Length > ColumnRestrictions.Length)
114+
throw new ArgumentException("Too many restrictions", nameof(restrictionValues));
115+
116+
const string query =
117+
"""
118+
SELECT
119+
table_catalog, table_schema, table_name, column_name,
120+
ordinal_position, column_default, is_nullable, data_type,
121+
character_maximum_length, character_octet_length,
122+
numeric_precision, numeric_precision_radix,
123+
numeric_scale, datetime_precision,
124+
character_set_catalog, character_set_schema, character_set_name, collation_catalog
125+
FROM information_schema.columns
126+
""";
127+
using var command = BuildCommand(connection, query, restrictionValues, true, ColumnRestrictions);
128+
129+
return GetDataTable(DuckDbMetaDataCollectionNames.Columns, command);
130+
}
131+
132+
private static DataTable GetForeignKeys(DuckDBConnection connection, string?[]? restrictionValues)
133+
{
134+
if (restrictionValues?.Length > ForeignKeyRestrictions.Length)
135+
throw new ArgumentException("Too many restrictions", nameof(restrictionValues));
136+
137+
const string query =
138+
"""
139+
SELECT
140+
constraint_catalog, constraint_schema, constraint_name,
141+
table_catalog, table_schema, table_name, constraint_type,
142+
is_deferrable, initially_deferred
143+
FROM information_schema.table_constraints
144+
WHERE constraint_type = 'FOREIGN KEY'
145+
""";
146+
using var command = BuildCommand(connection, query, restrictionValues, false, ForeignKeyRestrictions);
147+
148+
return GetDataTable(DuckDbMetaDataCollectionNames.ForeignKeys, command);
149+
}
150+
151+
private static DataTable GetIndexes(DuckDBConnection connection, string?[]? restrictionValues)
152+
{
153+
if (restrictionValues?.Length > IndexesRestrictions.Length)
154+
throw new ArgumentException("Too many restrictions", nameof(restrictionValues));
155+
156+
const string query =
157+
"""
158+
SELECT
159+
database_name as index_catalog,
160+
schema_name as index_schema,
161+
index_name,
162+
table_name,
163+
is_unique,
164+
is_primary
165+
FROM duckdb_indexes()
166+
""";
167+
using var command = BuildCommand(connection, query, restrictionValues, true, IndexesRestrictions);
168+
169+
return GetDataTable(DuckDbMetaDataCollectionNames.Indexes, command);
170+
}
171+
172+
private static DuckDBCommand BuildCommand(DuckDBConnection connection, string query, string?[]? restrictions,
173+
bool addWhere, string[]? restrictionNames)
174+
{
175+
var command = connection.CreateCommand();
176+
if (restrictions is not { Length: > 0 } || restrictionNames == null)
177+
{
178+
command.CommandText = query;
179+
return command;
180+
}
181+
182+
var builder = new StringBuilder(query);
183+
foreach (var (name, restriction) in restrictionNames.Zip(restrictions, Tuple.Create))
184+
{
185+
if (restriction?.Length > 0)
186+
{
187+
if (addWhere)
188+
{
189+
builder.Append(" WHERE ");
190+
addWhere = false;
191+
}
192+
else
193+
{
194+
builder.Append(" AND ");
195+
}
196+
197+
builder.Append($"{name} = ${name}");
198+
command.Parameters.Add(new DuckDBParameter(name, restriction));
199+
}
200+
}
201+
202+
command.CommandText = builder.ToString();
203+
return command;
204+
}
205+
206+
private static DataTable GetDataTable(string tableName, DuckDBCommand command)
207+
{
208+
var table = new DataTable(tableName);
209+
try
210+
{
211+
using var reader = command.ExecuteReader();
212+
table.BeginLoadData();
213+
table.Load(reader);
214+
}
215+
finally
216+
{
217+
table.EndLoadData();
218+
}
219+
220+
return table;
221+
}
222+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace DuckDB.NET.Data;
2+
3+
internal static class DuckDbMetaDataCollectionNames
4+
{
5+
public const string Tables = "Tables";
6+
public const string Columns = "Columns";
7+
public const string ForeignKeys = "ForeignKeys";
8+
public const string Indexes = "Indexes";
9+
}

0 commit comments

Comments
 (0)