88
99import java .sql .SQLException ;
1010import java .util .ArrayList ;
11+ import java .util .Collection ;
1112import java .util .Comparator ;
1213import java .util .LinkedHashMap ;
1314import java .util .List ;
1415import java .util .Locale ;
1516import java .util .Map ;
1617import java .util .Objects ;
1718import java .util .Set ;
18- import java .util .function .Function ;
19- import java .util .stream .Collectors ;
2019
2120public class SqlMetadataCatalog {
2221 public static final int DEFAULT_RELATION_LIMIT = 100 ;
@@ -35,7 +34,14 @@ public SqlMetadataCatalog(ResourceBrowser resourceBrowser, ConnectionManager con
3534
3635 public RelationMetadataPage listRelations (ResourceNodeSnapshot node , String context , String prefix , Integer limit )
3736 throws SQLException {
38- var scope = this .resolveScope (node , context );
37+ var scope = connectionManager .withDatabaseContext (null , () -> this .resolveScope (node , context ));
38+ return connectionManager .withDatabaseContext (
39+ scope .catalog (),
40+ () -> this .listRelations (scope , prefix , limit )
41+ );
42+ }
43+
44+ private RelationMetadataPage listRelations (MetadataScope scope , String prefix , Integer limit ) throws SQLException {
3945 var schemas = this .resolveSchemas (scope .schema ());
4046 var relations = new ArrayList <QualifiedRelation >();
4147
@@ -80,20 +86,29 @@ public List<RelationColumnsMetadata> listColumns(
8086 throw new IllegalArgumentException ("Too many relations in one metadata request" );
8187 }
8288
83- var scope = this .resolveScope (node , context );
89+ var scope = connectionManager .withDatabaseContext (null , () -> this .resolveScope (node , context ));
90+ return connectionManager .withDatabaseContext (
91+ scope .catalog (),
92+ () -> this .listColumns (scope , requestedRelations )
93+ );
94+ }
95+
96+ private List <RelationColumnsMetadata > listColumns (
97+ MetadataScope scope ,
98+ List <QualifiedRelation > requestedRelations
99+ ) throws SQLException {
84100 var availableSchemas = this .resolveSchemas (null );
85- var canonicalSchemas = availableSchemas .stream ()
86- .collect (Collectors .toMap (Function .identity (), Function .identity (), (left , right ) -> left ));
87101 var relationsBySchema = new LinkedHashMap <String , Map <RelationKey , QualifiedRelation >>();
88102 var canonicalRequests = new LinkedHashMap <RelationKey , QualifiedRelation >();
89103
90104 for (var requested : requestedRelations ) {
91105 this .validateRequestedRelation (requested , scope .catalog ());
92106 var requestedSchema = StringUtils .defaultIfBlank (requested .schema (), scope .schema ());
93- var canonicalSchema = canonicalSchemas .get (requestedSchema );
94- if (canonicalSchema == null ) {
95- throw new IllegalArgumentException ("Unknown relation schema" );
96- }
107+ var canonicalSchema = this .resolveCanonicalIdentifier (
108+ availableSchemas ,
109+ requestedSchema ,
110+ "Unknown relation schema"
111+ );
97112
98113 var availableRelations = relationsBySchema .get (canonicalSchema );
99114 if (availableRelations == null ) {
@@ -103,9 +118,18 @@ public List<RelationColumnsMetadata> listColumns(
103118 var key = new RelationKey (canonicalSchema , requested .name (), requested .kind ());
104119 var canonical = availableRelations .get (key );
105120 if (canonical == null ) {
106- throw new IllegalArgumentException ("Unknown relation" );
121+ var canonicalName = this .resolveCanonicalIdentifier (
122+ availableRelations .values ().stream ()
123+ .filter (relation -> relation .kind ().equals (requested .kind ()))
124+ .map (QualifiedRelation ::name )
125+ .toList (),
126+ requested .name (),
127+ "Unknown relation"
128+ );
129+ canonical = availableRelations .get (new RelationKey (canonicalSchema , canonicalName , requested .kind ()));
107130 }
108- canonicalRequests .putIfAbsent (key , canonical );
131+ var canonicalKey = new RelationKey (canonical .schema (), canonical .name (), canonical .kind ());
132+ canonicalRequests .putIfAbsent (canonicalKey , canonical );
109133 }
110134
111135 var result = new ArrayList <RelationColumnsMetadata >();
@@ -136,10 +160,7 @@ private MetadataScope resolveScope(ResourceNodeSnapshot node, String context) th
136160 }
137161 catalog = currentContext ;
138162 }
139- if (StringUtils .isNotBlank (catalog )) {
140- SqlIdentifierUtils .validateDatabaseName (catalog );
141- connectionManager .setDatabaseContext (catalog );
142- }
163+ SqlIdentifierUtils .validateDatabaseName (catalog );
143164
144165 String schema = null ;
145166 if (StringUtils .equals (contextKey , "schema" )) {
@@ -158,11 +179,58 @@ private List<String> resolveSchemas(String requestedSchema) throws SQLException
158179 if (StringUtils .isBlank (requestedSchema )) {
159180 return schemas ;
160181 }
161- return schemas .stream ()
162- .filter (schema -> schema .equals (requestedSchema ))
163- .findFirst ()
164- .map (List ::of )
165- .orElseThrow (() -> new IllegalArgumentException ("Unknown metadata schema" ));
182+ return List .of (this .resolveCanonicalIdentifier (schemas , requestedSchema , "Unknown metadata schema" ));
183+ }
184+
185+ private String resolveCanonicalIdentifier (
186+ Collection <String > candidates ,
187+ String requested ,
188+ String unknownMessage
189+ ) {
190+ if (StringUtils .isBlank (requested )) {
191+ throw new IllegalArgumentException (unknownMessage );
192+ }
193+
194+ var exactMatches = candidates .stream ().filter (requested ::equals ).distinct ().toList ();
195+ if (exactMatches .size () == 1 ) {
196+ return exactMatches .get (0 );
197+ }
198+
199+ var caseRule = this .identifierCaseRule ();
200+ if (caseRule == IdentifierCaseRule .LOWER || caseRule == IdentifierCaseRule .UPPER ) {
201+ var normalizedRequested = caseRule .normalize (requested );
202+ var normalizedMatches = candidates .stream ()
203+ .filter (candidate -> candidate .equals (caseRule .normalize (candidate )))
204+ .filter (candidate -> candidate .equals (normalizedRequested ))
205+ .distinct ()
206+ .toList ();
207+ if (normalizedMatches .size () == 1 ) {
208+ return normalizedMatches .get (0 );
209+ }
210+ throw new IllegalArgumentException (unknownMessage );
211+ }
212+
213+ if (caseRule == IdentifierCaseRule .INSENSITIVE ) {
214+ var insensitiveMatches = candidates .stream ()
215+ .filter (candidate -> candidate .equalsIgnoreCase (requested ))
216+ .distinct ()
217+ .toList ();
218+ if (insensitiveMatches .size () == 1 ) {
219+ return insensitiveMatches .get (0 );
220+ }
221+ }
222+ throw new IllegalArgumentException (unknownMessage );
223+ }
224+
225+ private IdentifierCaseRule identifierCaseRule () {
226+ var connectInfo = connectionManager .getConnectInfo ();
227+ var dbType = connectInfo == null ? "" : StringUtils .defaultString (connectInfo .getDbType ());
228+ return switch (dbType .toLowerCase (Locale .ROOT )) {
229+ case "postgresql" -> IdentifierCaseRule .LOWER ;
230+ case "oracle" , "db2" , "dm" , "dameng" -> IdentifierCaseRule .UPPER ;
231+ case "mysql" , "mariadb" , "sqlserver" -> IdentifierCaseRule .INSENSITIVE ;
232+ default -> IdentifierCaseRule .EXACT ;
233+ };
166234 }
167235
168236 private Map <RelationKey , QualifiedRelation > loadRelationsByKey (String catalog , String schema ) throws SQLException {
@@ -193,4 +261,15 @@ private record MetadataScope(String catalog, String schema) {
193261
194262 private record RelationKey (String schema , String name , String kind ) {
195263 }
264+
265+ private enum IdentifierCaseRule {
266+ LOWER ,
267+ UPPER ,
268+ INSENSITIVE ,
269+ EXACT ;
270+
271+ private String normalize (String identifier ) {
272+ return this == UPPER ? identifier .toUpperCase (Locale .ROOT ) : identifier .toLowerCase (Locale .ROOT );
273+ }
274+ }
196275}
0 commit comments