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+ }
0 commit comments