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