25
25
import static java .util .stream .Collectors .toList ;
26
26
import static org .oser .tools .jdbc .JdbcHelpers .adaptCaseForDb ;
27
27
28
- /** Represents one foreign key constraint in JDBC. In JDBC <em>one</em> db constraint between table 1 and table 2 has <em>two</em> representations: the link from
29
- * table 1 to table 2 and vice versa. One of the 2 constraints is <em>reverted</em>. */
28
+ /**
29
+ * Represents one foreign key constraint in JDBC. <a>
30
+ * <p>
31
+ * In JDBC <em>one</em> fk constraint between table 1 and table 2 has <em>two</em> representations: the link from
32
+ * table 1 to table 2 and vice versa. One of the 2 constraints is <em>inverted</em>, refer to the field <code>inverted</code>.
33
+ * <a>
34
+ * Supports virtual foreign keys (that do not exist in the db).
35
+ */
30
36
@ Getter
31
37
public class Fk {
32
38
private String pktable ;
@@ -39,41 +45,53 @@ public class Fk {
39
45
40
46
private final String keySeq ;
41
47
private final String fkName ;
42
- /** excluded in equals! */
48
+
49
+ /**
50
+ * The same as JDBC calls exported or imported. <br/>
51
+ * inverted = false: the foreign key columns that reference the given table's primary key columns (the foreign keys exported by a table) <br/>
52
+ * inverted = true: the primary key columns that are referenced by the given table's foreign key columns (the primary keys imported by a table). <br/>
53
+ * <p>
54
+ * Is excluded in equals.
55
+ */
43
56
private boolean inverted ;
44
57
45
- public Fk (){ // todo rm again
58
+ public Fk () { // todo rm again
46
59
keySeq = "" ;
47
60
fkName = "" ;
48
61
}
49
62
50
63
51
- /** Constructor taking single values only */
64
+ /**
65
+ * Constructor taking single values only
66
+ */
52
67
public Fk (String pktable , String pkcolumn , String fktable , String fkcolumn , String keySeq , String fkName , boolean inverted ) {
53
68
this .pktable = pktable ;
54
- this .pkcolumn = new String []{ pkcolumn };
69
+ this .pkcolumn = new String []{pkcolumn };
55
70
this .fktable = fktable ;
56
- this .fkcolumn = new String []{ fkcolumn };
71
+ this .fkcolumn = new String []{fkcolumn };
57
72
this .keySeq = keySeq ;
58
73
this .fkName = fkName ;
59
74
this .inverted = inverted ;
60
75
}
61
76
62
77
public Fk (String pktable , String [] pkcolumnArray , String fktable , String [] fkcolumnArray , String keySeq , String fkName , boolean inverted ) {
63
78
this .pktable = pktable ;
64
- this .pkcolumn = pkcolumnArray ;
79
+ this .pkcolumn = pkcolumnArray ;
65
80
this .fktable = fktable ;
66
- this .fkcolumn = fkcolumnArray ;
81
+ this .fkcolumn = fkcolumnArray ;
67
82
this .keySeq = keySeq ;
68
83
this .fkName = fkName ;
69
84
this .inverted = inverted ;
70
85
}
71
86
72
87
73
-
88
+ /**
89
+ * Refer to {@link #getFksOfTable(Connection, String)}
90
+ * Uses a cache.
91
+ */
74
92
public static List <Fk > getFksOfTable (Connection connection , String table , Cache <String , List <Fk >> cache ) throws SQLException {
75
93
List <Fk > result = cache .getIfPresent (table );
76
- if (result == null ){
94
+ if (result == null ) {
77
95
result = getFksOfTable (connection , table );
78
96
}
79
97
cache .put (table , result );
@@ -85,10 +103,10 @@ public static boolean hasSelfLink(List<Fk> fks) {
85
103
}
86
104
87
105
/**
88
- * get FK metadata of one table (both direction of metadata, exported and imported FKs)
89
- *
106
+ * Get FK metadata of one table (both direction of metadata, exported and imported FKs)
107
+ * <p>
90
108
* If the tableName has a schema prefix (e.g. mySchema.) it adds it to the table names
91
- * of the returned FKs.
109
+ * of the returned FKs.
92
110
*/
93
111
public static List <Fk > getFksOfTable (Connection connection , String tableName ) throws SQLException {
94
112
List <Fk > fks = new CopyOnWriteArrayList <>();
@@ -111,14 +129,16 @@ public static List<Fk> getFksOfTable(Connection connection, String tableName) th
111
129
return unifyFks (fks );
112
130
}
113
131
114
- /** One Fk can contain multiple columns, we need to merge those that form the same fk */
132
+ /**
133
+ * One Fk can contain multiple columns, we need to merge those that form the same Fk
134
+ */
115
135
public static List <Fk > unifyFks (List <Fk > input ) {
116
136
Map <String , List <Fk >> fkNameToFk = new HashMap <>();
117
137
input .forEach (f -> fkNameToFk .computeIfAbsent (f .getFkName (), l -> new ArrayList <>()).add (f ));
118
138
119
139
List <Map .Entry <String , List <Fk >>> toMerge =
120
140
fkNameToFk .entrySet ().stream ().filter (e -> e .getValue ().size () > 1 && !hasSelfLink (e .getValue ().get (0 ))).collect (toList ());
121
- for (Map .Entry <String , List <Fk >> e : toMerge ){
141
+ for (Map .Entry <String , List <Fk >> e : toMerge ) {
122
142
e .getValue ().sort (Comparator .comparing (Fk ::getKeySeq ));
123
143
124
144
e .getValue ().get (0 ).setFkcolumn (e .getValue ().stream ().map (fk -> fk .getFkcolumn ()[0 ]).collect (toList ()).toArray (new String []{}));
@@ -130,18 +150,20 @@ public static List<Fk> unifyFks(List<Fk> input) {
130
150
return input ;
131
151
}
132
152
133
- /** check for link to self (e.g. table N having a FK to itself */
153
+ /**
154
+ * check for link to self (e.g. table N having a FK to itself
155
+ */
134
156
public static boolean hasSelfLink (Fk e ) {
135
157
return e .getFktable ().equals (e .getPktable ());
136
158
}
137
159
138
160
private static void addFks (List <Fk > fks , ResultSet rs , boolean inverted , JdbcHelpers .Table table ) throws SQLException {
139
161
String optionalPrefix = table .isHasSchemaPrefix () ? table .getSchema () + "." : "" ;
140
162
while (rs .next ()) {
141
- Fk fk = new Fk (optionalPrefix + getStringFromResultSet (rs ,"pktable_name" ),
142
- getStringFromResultSet (rs ,"pkcolumn_name" ),
143
- optionalPrefix + getStringFromResultSet (rs ,"fktable_name" ),
144
- getStringFromResultSet (rs ,"fkcolumn_name" ),
163
+ Fk fk = new Fk (optionalPrefix + getStringFromResultSet (rs , "pktable_name" ),
164
+ getStringFromResultSet (rs , "pkcolumn_name" ),
165
+ optionalPrefix + getStringFromResultSet (rs , "fktable_name" ),
166
+ getStringFromResultSet (rs , "fkcolumn_name" ),
145
167
getStringFromResultSet (rs , "KEY_SEQ" ),
146
168
getStringFromResultSet (rs , "fk_name" ), inverted );
147
169
@@ -161,7 +183,7 @@ private static String getStringFromResultSet(ResultSet rs, String columnName) th
161
183
162
184
// to remove " that mysql puts
163
185
static String removeOptionalQuotes (String string ) {
164
- if (string != null && string .startsWith ("\" " ) && string .endsWith ("\" " )){
186
+ if (string != null && string .startsWith ("\" " ) && string .endsWith ("\" " )) {
165
187
string = string .substring (1 , string .length () - 1 );
166
188
}
167
189
return string ;
@@ -204,10 +226,10 @@ public int hashCode() {
204
226
205
227
/**
206
228
* mysql does not return the indirekt FKs with {@link DatabaseMetaData#getExportedKeys(String, String, String)}
207
- * (it only shows the direct links via {@link DatabaseMetaData#getImportedKeys(String, String, String)})
208
- * - so we fake it here and add these indirect keys to the cache
229
+ * (it only shows the direct links via {@link DatabaseMetaData#getImportedKeys(String, String, String)})
230
+ * - so we fake it here and add these indirect keys to the cache
209
231
* (goes through all database tables for this)
210
- *
232
+ * <p>
211
233
* does only take the current schema into account
212
234
*/
213
235
public static void initFkCacheForMysql (Cache <String , List <Fk >> fkCache , Connection connection ) throws SQLException {
@@ -222,13 +244,15 @@ public static void initFkCacheForMysql(Cache<String, List<Fk>> fkCache, Connecti
222
244
List <Fk > current = fkCache .getIfPresent (fk .pktable );
223
245
current = (current == null ) ? new CopyOnWriteArrayList <>() : current ;
224
246
// todo: is keyseq ok?
225
- current .add (new Fk (fk .pktable , fk .pkcolumn , fk .fktable , fk .fkcolumn , fk .keySeq , fk .fkName +"inv" , false ));
247
+ current .add (new Fk (fk .pktable , fk .pkcolumn , fk .fktable , fk .fkcolumn , fk .keySeq , fk .fkName + "inv" , false ));
226
248
fkCache .put (fk .pktable , current );
227
249
}
228
250
}
229
251
}
230
252
231
- /** Like {@link #initFkCacheForMysql(Cache, Connection)} but logs exceptions to stdout */
253
+ /**
254
+ * Like {@link #initFkCacheForMysql(Cache, Connection)} but logs exceptions to stdout
255
+ */
232
256
public static void initFkCacheForMysql_LogException (Connection demo , Cache <String , List <Fk >> fkCache ) {
233
257
try {
234
258
Fk .initFkCacheForMysql (fkCache , demo );
@@ -237,9 +261,11 @@ public static void initFkCacheForMysql_LogException(Connection demo, Cache<Strin
237
261
}
238
262
}
239
263
240
- /** Add a virtualForeignKey to the foreign key cache "importerOrExporter".
241
- * (needs to be done once for the DbImporter AND the DbExporter).
242
- * This requires 2 internal foreign keys, one of which is reverted */
264
+ /**
265
+ * Add a virtualForeignKey to the foreign key cache contained in "importerOrExporter".
266
+ * (needs to be done once for the DbImporter AND the DbExporter).
267
+ * This requires 2 internal foreign keys, one of which is inverted
268
+ */
243
269
public static void addVirtualForeignKey (Connection dbConnection ,
244
270
FkCacheAccessor importerOrExporter ,
245
271
String tableOne ,
@@ -258,34 +284,38 @@ public static void addVirtualForeignKey(Connection dbConnection,
258
284
importerOrExporter .getFkCache ().put (tableTwo , fks2 );
259
285
}
260
286
261
- /** Add a virtualForeignKey to the foreign key cache "importerOrExporter".
262
- * (needs to be done once for the DbImporter AND the DbExporter).
263
- * This requires 2 internal foreign keys, one of which is reverted */
287
+ /**
288
+ * Add a virtualForeignKey to the foreign key cache "importerOrExporter".
289
+ * (needs to be done once for the DbImporter AND the DbExporter).
290
+ * This requires 2 internal foreign keys, one of which is reverted
291
+ */
264
292
public static void addVirtualForeignKey (Connection dbConnection ,
265
293
FkCacheAccessor importerOrExporter ,
266
294
String tableOne ,
267
295
String tableOneColumn ,
268
296
String tableTwo ,
269
297
String tableTwoColumn ) throws SQLException {
270
- addVirtualForeignKey (dbConnection , importerOrExporter , tableOne , new String [] {tableOneColumn },
271
- tableTwo , new String [] {tableTwoColumn });
298
+ addVirtualForeignKey (dbConnection , importerOrExporter , tableOne , new String []{tableOneColumn },
299
+ tableTwo , new String []{tableTwoColumn });
272
300
}
273
301
274
- /// Helper
302
+ /// Helper
275
303
276
304
public static Map <String , List <Fk >> fksByColumnName (List <Fk > fksOfTable ) {
277
305
Map <String , List <Fk >> fksByColumnName = new HashMap <>();
278
306
279
- for (Fk fk : fksOfTable ){
307
+ for (Fk fk : fksOfTable ) {
280
308
String [] names = fk .inverted ? fk .getFkcolumn () : fk .getPkcolumn ();
281
309
Stream .of (names ).forEach (name -> fksByColumnName .computeIfAbsent (name .toLowerCase (), l -> new ArrayList <>()).add (fk ));
282
310
}
283
311
return fksByColumnName ;
284
312
}
285
313
286
- /** register virtualFK via String
287
- * experimental string config of a virtual foreing key
288
- * table1(field1,field2)-table2(field3,field4) */
314
+ /**
315
+ * register virtualFK via String
316
+ * experimental string config of a virtual foreing key
317
+ * table1(field1,field2)-table2(field3,field4)
318
+ */
289
319
public static void addOneVirtualForeignKeyAsString (Connection dbConnection , FkCacheAccessor importerOrExporter , String asString ) throws SQLException {
290
320
FkMatchedFields fkMatchedFields = new FkMatchedFields (asString ).parse ();
291
321
String table1 = fkMatchedFields .getTable1 ();
@@ -296,8 +326,11 @@ public static void addOneVirtualForeignKeyAsString(Connection dbConnection, FkCa
296
326
addVirtualForeignKey (dbConnection , importerOrExporter , table1 , fields1AsString .split ("," ), table2 , fields2AsString .split ("," ));
297
327
}
298
328
329
+ /**
330
+ * Same as {@link #addOneVirtualForeignKeyAsString(Connection, FkCacheAccessor, String) } but one can add multiples, separated by ; }
331
+ */
299
332
public static void addVirtualForeignKeyAsString (Connection dbConnection , FkCacheAccessor importerOrExporter , String asString ) throws SQLException {
300
- if (asString .contains (";" )){
333
+ if (asString .contains (";" )) {
301
334
String [] split = asString .split (";" );
302
335
for (String one : split ) {
303
336
addOneVirtualForeignKeyAsString (dbConnection , importerOrExporter , one );
@@ -307,6 +340,7 @@ public static void addVirtualForeignKeyAsString(Connection dbConnection, FkCache
307
340
}
308
341
}
309
342
343
+ /** To parse String FKs */
310
344
@ Getter
311
345
static class FkMatchedFields {
312
346
private String asString ;
@@ -323,8 +357,8 @@ public FkMatchedFields parse() {
323
357
String regex = "([A-Za-z0-9_.]*)\\ (([A-Za-z0-9,_]*)\\ )-([A-Za-z0-9_.]*)\\ (([A-Za-z0-9,_]*)\\ )" ;
324
358
Pattern pattern = Pattern .compile (regex );
325
359
Matcher matcher = pattern .matcher (asString );
326
- if ( !matcher .find ()){
327
- throw new IllegalArgumentException ("Wrong pattern '" + asString + "'. Not matched." );
360
+ if (!matcher .find ()) {
361
+ throw new IllegalArgumentException ("Wrong pattern '" + asString + "'. Not matched." );
328
362
}
329
363
330
364
table1 = matcher .group (1 );
@@ -345,20 +379,24 @@ public static String getSubtableName(Fk fk, String databaseProductName) {
345
379
return subTableName ;
346
380
}
347
381
348
- /** Do the table1 and table2 have a FK relationship? */
382
+ /**
383
+ * Do the table1 and table2 have a FK relationship?
384
+ */
349
385
public static Optional <Fk > getFkOfTwoTables (Connection connection , String table1 , String table2 , Cache <String , List <Fk >> cache ) throws SQLException {
350
386
List <Fk > fksOfTable = Fk .getFksOfTable (connection , table1 , cache );
351
387
352
388
for (Fk fk : fksOfTable ) {
353
389
if ((fk .getPktable ().equalsIgnoreCase (table1 ) && fk .getFktable ().equalsIgnoreCase (table2 )) ||
354
- (fk .getPktable ().equalsIgnoreCase (table2 ) && fk .getFktable ().equalsIgnoreCase (table1 ))){
390
+ (fk .getPktable ().equalsIgnoreCase (table2 ) && fk .getFktable ().equalsIgnoreCase (table1 ))) {
355
391
return Optional .of (fk );
356
392
}
357
393
}
358
394
return Optional .empty ();
359
395
}
360
396
361
- /** Do the table1 and table2 have a FK relationship? */
397
+ /**
398
+ * Do the table1 and table2 have a FK relationship?
399
+ */
362
400
public static Optional <Fk > getFkOfTwoTables (Connection connection , String table1 , String table2 ) throws SQLException {
363
401
Cache <String , List <Fk >> cache = Caffeine .newBuilder ().maximumSize (10_000 ).build ();
364
402
return getFkOfTwoTables (connection , table1 , table2 , cache );
0 commit comments