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