1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package com.rapiddweller.platform.db;
28
29 import com.rapiddweller.benerator.Consumer;
30 import com.rapiddweller.benerator.storage.AbstractStorageSystem;
31 import com.rapiddweller.benerator.storage.StorageSystemInserter;
32 import com.rapiddweller.common.CollectionUtil;
33 import com.rapiddweller.common.ConfigurationError;
34 import com.rapiddweller.common.ConnectFailedException;
35 import com.rapiddweller.common.Context;
36 import com.rapiddweller.common.IOUtil;
37 import com.rapiddweller.common.ImportFailedException;
38 import com.rapiddweller.common.ObjectNotFoundException;
39 import com.rapiddweller.common.StringUtil;
40 import com.rapiddweller.common.collection.OrderedNameMap;
41 import com.rapiddweller.common.converter.AnyConverter;
42 import com.rapiddweller.common.version.VersionNumber;
43 import com.rapiddweller.format.DataSource;
44 import com.rapiddweller.format.util.ConvertingDataSource;
45 import com.rapiddweller.jdbacl.ColumnInfo;
46 import com.rapiddweller.jdbacl.DBUtil;
47 import com.rapiddweller.jdbacl.DatabaseDialect;
48 import com.rapiddweller.jdbacl.DatabaseDialectManager;
49 import com.rapiddweller.jdbacl.JDBCConnectData;
50 import com.rapiddweller.jdbacl.ResultSetConverter;
51 import com.rapiddweller.jdbacl.dialect.OracleDialect;
52 import com.rapiddweller.jdbacl.model.DBCatalog;
53 import com.rapiddweller.jdbacl.model.DBColumn;
54 import com.rapiddweller.jdbacl.model.DBDataType;
55 import com.rapiddweller.jdbacl.model.DBForeignKeyConstraint;
56 import com.rapiddweller.jdbacl.model.DBMetaDataImporter;
57 import com.rapiddweller.jdbacl.model.DBPrimaryKeyConstraint;
58 import com.rapiddweller.jdbacl.model.DBSchema;
59 import com.rapiddweller.jdbacl.model.DBTable;
60 import com.rapiddweller.jdbacl.model.DBUniqueConstraint;
61 import com.rapiddweller.jdbacl.model.Database;
62 import com.rapiddweller.jdbacl.model.cache.CachingDBImporter;
63 import com.rapiddweller.jdbacl.model.jdbc.JDBCDBImporter;
64 import com.rapiddweller.jdbacl.model.jdbc.JDBCMetaDataUtil;
65 import com.rapiddweller.model.data.ComplexTypeDescriptor;
66 import com.rapiddweller.model.data.ComponentDescriptor;
67 import com.rapiddweller.model.data.DataModel;
68 import com.rapiddweller.model.data.Entity;
69 import com.rapiddweller.model.data.IdDescriptor;
70 import com.rapiddweller.model.data.Mode;
71 import com.rapiddweller.model.data.PartDescriptor;
72 import com.rapiddweller.model.data.ReferenceDescriptor;
73 import com.rapiddweller.model.data.SimpleTypeDescriptor;
74 import com.rapiddweller.model.data.TypeDescriptor;
75 import com.rapiddweller.model.data.TypeMapper;
76 import com.rapiddweller.script.PrimitiveType;
77 import com.rapiddweller.script.expression.ConstantExpression;
78 import org.apache.logging.log4j.LogManager;
79 import org.apache.logging.log4j.Logger;
80
81 import java.io.File;
82 import java.math.BigDecimal;
83 import java.sql.Blob;
84 import java.sql.Connection;
85 import java.sql.DatabaseMetaData;
86 import java.sql.PreparedStatement;
87 import java.sql.ResultSet;
88 import java.sql.SQLException;
89 import java.sql.Types;
90 import java.util.ArrayList;
91 import java.util.Collection;
92 import java.util.HashMap;
93 import java.util.List;
94 import java.util.Map;
95 import java.util.concurrent.atomic.AtomicInteger;
96
97 import static com.rapiddweller.jdbacl.SQLUtil.createCatSchTabString;
98 import static com.rapiddweller.jdbacl.SQLUtil.renderQuery;
99
100
101
102
103
104
105
106
107 public abstract class DBSystem extends AbstractStorageSystem {
108
109 private static final int DEFAULT_FETCH_SIZE = 100;
110 private static final VersionNumber MIN_ORACLE_VERSION =
111 VersionNumber.valueOf("10.2.0.4");
112 private static final TypeDescriptor[] EMPTY_TYPE_DESCRIPTOR_ARRAY =
113 new TypeDescriptor[0];
114
115
116
117
118
119 protected final Logger logger = LogManager.getLogger(getClass());
120 private final TypeMapper driverTypeMapper;
121 private final AtomicInteger invalidationCount;
122
123
124
125 protected boolean batch;
126
127
128
129 protected boolean readOnly;
130
131
132
133 protected Database database;
134
135
136
137 protected DBMetaDataImporter importer;
138
139
140
141 protected Map<String, DBTable> tables;
142
143
144
145 protected DatabaseDialect dialect;
146 private String id;
147 private String environment;
148 private String url;
149 private String user;
150 private String password;
151 private String driver;
152 private String catalogName;
153 private String schemaName;
154 private String includeTables;
155 private String excludeTables;
156 private boolean metaDataCache;
157 private boolean lazy;
158 private boolean acceptUnknownColumnTypes;
159 private int fetchSize;
160 private OrderedNameMap<TypeDescriptor> typeDescriptors;
161 private boolean dynamicQuerySupported;
162 private boolean connectedBefore;
163
164
165
166
167
168
169
170
171
172
173
174 public DBSystem(String id, String url, String driver, String user,
175 String password, DataModel dataModel) {
176 this(id, dataModel);
177 setUrl(url);
178 setUser(user);
179 setPassword(password);
180 setDriver(driver);
181 checkOracleDriverVersion(driver);
182 }
183
184
185
186
187
188
189
190
191 public DBSystem(String id, String environment, DataModel dataModel) {
192 this(id, dataModel);
193 setEnvironment(environment);
194 }
195
196 private DBSystem(String id, DataModel dataModel) {
197 setId(id);
198 setDataModel(dataModel);
199 setSchema(null);
200 setIncludeTables(".*");
201 setExcludeTables(null);
202 setFetchSize(DEFAULT_FETCH_SIZE);
203 setMetaDataCache(false);
204 setBatch(false);
205 setReadOnly(false);
206 setLazy(true);
207 setDynamicQuerySupported(true);
208 this.typeDescriptors = null;
209 this.driverTypeMapper = driverTypeMapper();
210 this.connectedBefore = false;
211 this.invalidationCount = new AtomicInteger();
212 }
213
214
215 private static String decimalGranularity(int scale) {
216 if (scale == 0) {
217 return "1";
218 }
219 return "0." + "0".repeat(Math.max(0, scale - 1)) +
220 1;
221 }
222
223 private static TypeMapper driverTypeMapper() {
224 return new TypeMapper(
225 "byte", Byte.class,
226 "short", Short.class,
227 "int", Integer.class,
228 "big_integer", Long.class,
229 "float", Float.class,
230 "double", Double.class,
231 "big_decimal", BigDecimal.class,
232
233 "boolean", Boolean.class,
234 "char", Character.class,
235 "date", java.sql.Date.class,
236 "timestamp", java.sql.Timestamp.class,
237
238 "string", java.sql.Clob.class,
239 "string", String.class,
240
241 "binary", Blob.class,
242 "binary", byte[].class
243
244
245 );
246 }
247
248 @Override
249 public String getId() {
250 return id;
251 }
252
253
254
255
256
257
258 public void setId(String id) {
259 this.id = id;
260 }
261
262
263
264
265
266
267 public String getEnvironment() {
268 return (environment != null ? environment : user);
269 }
270
271 private void setEnvironment(String environment) {
272 if (StringUtil.isEmpty(environment)) {
273 this.environment = null;
274 return;
275 }
276 logger.debug("setting environment '{}'", environment);
277 JDBCConnectData connectData = DBUtil.getConnectData(environment);
278 this.environment = environment;
279 this.url = connectData.url;
280 this.driver = connectData.driver;
281 this.catalogName = connectData.catalog;
282 this.schemaName = connectData.schema;
283 this.user = connectData.user;
284 this.password = connectData.password;
285 }
286
287
288
289
290
291
292 public String getDriver() {
293 return driver;
294 }
295
296
297
298
299
300
301 public void setDriver(String driver) {
302 this.driver = driver;
303 }
304
305
306
307
308
309
310 public String getUrl() {
311 return url;
312 }
313
314
315
316
317
318
319 public void setUrl(String url) {
320 this.url = url;
321 }
322
323
324
325
326
327
328 public String getUser() {
329 return user;
330 }
331
332
333
334
335
336
337 public void setUser(String user) {
338 this.user = user;
339 }
340
341
342
343
344
345
346 public String getPassword() {
347 return password;
348 }
349
350
351
352
353
354
355 public void setPassword(String password) {
356 this.password = StringUtil.emptyToNull(password);
357 }
358
359
360
361
362
363
364 public String getCatalog() {
365 return catalogName;
366 }
367
368
369
370
371
372
373 public void setCatalog(String catalog) {
374 this.catalogName = catalog;
375 }
376
377
378
379
380
381
382 public String getSchema() {
383 return schemaName;
384 }
385
386
387
388
389
390
391 public void setSchema(String schema) {
392 this.schemaName = StringUtil.emptyToNull(StringUtil.trim(schema));
393 }
394
395
396
397
398
399
400 @Deprecated
401 public void setTableFilter(String tableFilter) {
402 setIncludeTables(tableFilter);
403 }
404
405
406
407
408
409
410 public String getIncludeTables() {
411 return includeTables;
412 }
413
414
415
416
417
418
419 public void setIncludeTables(String includeTables) {
420 this.includeTables = includeTables;
421 }
422
423
424
425
426
427
428 public String getExcludeTables() {
429 return excludeTables;
430 }
431
432
433
434
435
436
437 public void setExcludeTables(String excludeTables) {
438 this.excludeTables = excludeTables;
439 }
440
441
442
443
444
445
446 public boolean isMetaDataCache() {
447 return metaDataCache;
448 }
449
450
451
452
453
454
455 public void setMetaDataCache(boolean metaDataCache) {
456 this.metaDataCache = metaDataCache;
457 }
458
459
460
461
462
463
464 public boolean isBatch() {
465 return batch;
466 }
467
468
469
470
471
472
473 public void setBatch(boolean batch) {
474 this.batch = batch;
475 }
476
477
478
479
480
481
482 public int getFetchSize() {
483 return fetchSize;
484 }
485
486
487
488
489
490
491 public void setFetchSize(int fetchSize) {
492 this.fetchSize = fetchSize;
493 }
494
495
496
497
498
499
500 public boolean isReadOnly() {
501 return readOnly;
502 }
503
504
505
506
507
508
509 public void setReadOnly(boolean readOnly) {
510 this.readOnly = readOnly;
511 }
512
513
514
515
516
517
518 public boolean isLazy() {
519 return lazy;
520 }
521
522
523
524
525
526
527 public void setLazy(boolean lazy) {
528 this.lazy = lazy;
529 }
530
531
532
533
534
535
536
537
538
539 public void setDynamicQuerySupported(boolean dynamicQuerySupported) {
540 this.dynamicQuerySupported = dynamicQuerySupported;
541 }
542
543
544
545
546
547
548 public void setAcceptUnknownColumnTypes(boolean acceptUnknownColumnTypes) {
549 this.acceptUnknownColumnTypes = acceptUnknownColumnTypes;
550 }
551
552
553
554 @Override
555 public TypeDescriptor[] getTypeDescriptors() {
556 logger.debug("getTypeDescriptors()");
557 parseMetadataIfNecessary();
558 if (typeDescriptors == null) {
559 return EMPTY_TYPE_DESCRIPTOR_ARRAY;
560 } else {
561 return CollectionUtil
562 .toArray(typeDescriptors.values(), TypeDescriptor.class);
563 }
564 }
565
566 @Override
567 public TypeDescriptor getTypeDescriptor(String tableName) {
568 logger.debug("getTypeDescriptor({})", tableName);
569 parseMetadataIfNecessary();
570 return typeDescriptors.get(tableName);
571 }
572
573 @Override
574 public void store(Entity entity) {
575 if (readOnly) {
576 throw new IllegalStateException(
577 "Tried to insert rows into table '" + entity.type() + "' " +
578 "though database '" + id + "' is read-only");
579 }
580 logger.debug("Storing {}", entity);
581 persistOrUpdate(entity, true);
582 }
583
584 @Override
585 public void update(Entity entity) {
586 if (readOnly) {
587 throw new IllegalStateException(
588 "Tried to update table '" + entity.type() + "' " +
589 "though database '" + id + "' is read-only");
590 }
591 logger.debug("Updating {}", entity);
592 persistOrUpdate(entity, false);
593 }
594
595 @Override
596 public void close() {
597 if (database != null) {
598 CachingDBImporter.updateCacheFile(database);
599 }
600 IOUtil.close(importer);
601 }
602
603
604
605
606
607
608
609
610 public Entity queryEntityById(String tableName, Object id) {
611 try {
612 logger.debug("queryEntityById({}, {})", tableName, id);
613 ComplexTypeDescriptor descriptor =
614 (ComplexTypeDescriptor) getTypeDescriptor(tableName);
615 PreparedStatement query = getSelectByPKStatement(descriptor);
616 query.setObject(1, id);
617 ResultSet resultSet = query.executeQuery();
618 if (resultSet.next()) {
619 return ResultSet2EntityConverter.convert(resultSet, descriptor);
620 } else {
621 return null;
622 }
623 } catch (SQLException e) {
624 throw new RuntimeException("Error querying " + tableName, e);
625 }
626 }
627
628 @Override
629 @SuppressWarnings({"null", "checkstyle:VariableDeclarationUsageDistance"})
630 public DataSource<Entity> queryEntities(String type, String selector,
631 Context context) {
632 logger.debug("queryEntities({})", type);
633 Connection connection = getConnection();
634 boolean script = false;
635 if (selector != null && selector.startsWith("{") &&
636 selector.endsWith("}")) {
637 selector = selector.substring(1, selector.length() - 1);
638 script = true;
639 }
640 String sql;
641 if (StringUtil.isEmpty(selector)) {
642 sql = "select * from " +
643 createCatSchTabString(catalogName, schemaName, type,
644 dialect);
645 } else if (StringUtil.startsWithIgnoreCase(selector, "select") ||
646 StringUtil.startsWithIgnoreCase(selector, "'select")) {
647 sql = selector;
648 } else if (selector.startsWith("ftl:") || !script) {
649 sql = "select * from " +
650 createCatSchTabString(catalogName, schemaName, type,
651 dialect) + " WHERE " + selector;
652 } else {
653 sql = "'select * from " +
654 createCatSchTabString(catalogName, schemaName, type,
655 dialect) + " WHERE ' + " + selector;
656 }
657 if (script) {
658 sql = '{' + sql + '}';
659 }
660 DataSource<ResultSet> source = createQuery(sql, context, connection);
661 return new EntityResultSetDataSource(source,
662 (ComplexTypeDescriptor) getTypeDescriptor(type));
663 }
664
665
666
667
668
669
670
671 public long countEntities(String tableName) {
672 logger.debug("countEntities({})", tableName);
673 String query = "select count(*) from " +
674 createCatSchTabString(catalogName, schemaName, tableName,
675 dialect);
676 return DBUtil.queryLong(query, getConnection());
677 }
678
679 @Override
680 public DataSource<?> queryEntityIds(String tableName, String selector,
681 Context context) {
682 logger.debug("queryEntityIds({}, {})", tableName, selector);
683
684
685 boolean script = false;
686 if (selector != null && selector.startsWith("{") &&
687 selector.endsWith("}")) {
688 selector = selector.substring(1, selector.length() - 1);
689 script = true;
690 }
691
692
693 DBTable table = getTable(tableName);
694 String[] pkColumnNames = table.getPKColumnNames();
695 if (pkColumnNames.length == 0) {
696 throw new ConfigurationError(
697 "Cannot create reference to table " + tableName +
698 " since it does not define a primary key");
699 }
700
701
702 String query =
703 renderQuery(catalogName, schemaName, tableName, pkColumnNames,
704 dialect);
705 if (selector != null) {
706 if (script) {
707 query = "{'" + query + " where ' + " + selector + "}";
708 } else {
709 query += " where " + selector;
710 }
711 }
712 return query(query, true, context);
713 }
714
715 @Override
716 @SuppressWarnings({"unchecked", "rawtypes"})
717 public DataSource<?> query(String query, boolean simplify,
718 Context context) {
719 logger.debug("query({})", query);
720 Connection connection = getConnection();
721 QueryDataSource resultSetIterable =
722 createQuery(query, context, connection);
723 ResultSetConverter converter =
724 new ResultSetConverter(Object.class, simplify);
725 return new ConvertingDataSource<ResultSet, Object>(resultSetIterable,
726 converter);
727 }
728
729
730
731
732
733
734
735
736 public Consumer inserter() {
737 return new StorageSystemInserter(this);
738 }
739
740
741
742
743
744
745
746 public Consumer inserter(String tableName) {
747 return new StorageSystemInserter(this,
748 (ComplexTypeDescriptor) getTypeDescriptor(tableName));
749 }
750
751
752
753
754
755
756 public abstract Connection getConnection();
757
758
759
760
761
762
763
764 protected abstract PreparedStatement getSelectByPKStatement(
765 ComplexTypeDescriptor descriptor);
766
767
768
769
770
771
772
773 public boolean tableExists(String tableName) {
774 logger.debug("tableExists({})", tableName);
775 return (getTypeDescriptor(tableName) != null);
776 }
777
778
779
780
781
782
783
784 public void createSequence(String name) throws SQLException {
785 getDialect().createSequence(name, 1, getConnection());
786 }
787
788
789
790
791
792
793 public void dropSequence(String name) {
794 execute(getDialect().renderDropSequence(name));
795 }
796
797 @Override
798 public Object execute(String sql) {
799 try {
800 DBUtil.executeUpdate(sql, getConnection());
801 return null;
802 } catch (SQLException e) {
803 throw new RuntimeException(e);
804 }
805 }
806
807
808
809
810
811
812
813 public long nextSequenceValue(String sequenceName) {
814 return DBUtil
815 .queryLong(getDialect().renderFetchSequenceValue(sequenceName),
816 getConnection());
817 }
818
819
820
821
822
823
824
825
826 public void setSequenceValue(String sequenceName, long value)
827 throws SQLException {
828 getDialect().setNextSequenceValue(sequenceName, value, getConnection());
829 }
830
831
832
833
834
835
836 protected Connection createConnection() {
837 try {
838 Connection connection =
839 DBUtil.connect(url, driver, user, password, readOnly);
840 if (!connectedBefore) {
841 DBUtil.logMetaData(connection);
842 connectedBefore = true;
843 }
844 connection.setAutoCommit(false);
845 return connection;
846 } catch (ConnectFailedException e) {
847 throw new RuntimeException(
848 "Connecting the database failed. URL: " + url, e);
849 } catch (SQLException e) {
850 throw new ConfigurationError("Turning off auto-commit failed", e);
851 }
852 }
853
854
855
856
857 public void invalidate() {
858 typeDescriptors = null;
859 tables = null;
860 invalidationCount.incrementAndGet();
861 if (environment != null) {
862 File bufferFile = CachingDBImporter.getCacheFile(environment);
863 if (bufferFile.exists()) {
864 if (!bufferFile.delete() && metaDataCache) {
865 logger.error("Deleting " + bufferFile + " failed");
866 metaDataCache = false;
867 } else {
868 logger.info("Deleted meta data cache file: " + bufferFile);
869 }
870
871 }
872 }
873 }
874
875
876
877
878
879
880 public int invalidationCount() {
881 return invalidationCount.get();
882 }
883
884
885
886
887 public void parseMetaData() {
888 this.tables = new HashMap<>();
889 this.typeDescriptors = OrderedNameMap.createCaseIgnorantMap();
890
891 getDialect();
892 database = getDbMetaData();
893 if (lazy) {
894 logger.info(
895 "Fetching table details and ordering tables by dependency");
896 } else {
897 logger.info("Ordering tables by dependency");
898 }
899 List<DBTable> tables = DBUtil.dependencyOrderedTables(database);
900 for (DBTable table : tables) {
901 parseTable(table);
902 }
903 }
904
905
906
907
908
909
910 public DatabaseDialect getDialect() {
911 if (dialect == null) {
912 try {
913 DatabaseMetaData metaData = getConnection().getMetaData();
914 String productName = metaData.getDatabaseProductName();
915 VersionNumber productVersion = VersionNumber
916 .valueOf(metaData.getDatabaseMajorVersion() + "." +
917 metaData.getDatabaseMinorVersion());
918 dialect = DatabaseDialectManager
919 .getDialectForProduct(productName, productVersion);
920 } catch (SQLException e) {
921 throw new ConfigurationError("Database meta data access failed",
922 e);
923 }
924 }
925 return dialect;
926 }
927
928
929
930
931
932
933
934
935 public String getSystem() {
936 return getDialect().getSystem();
937 }
938
939
940
941
942
943
944
945
946 public Database getDbMetaData() {
947 if (database == null) {
948 fetchDbMetaData();
949 }
950 return database;
951 }
952
953 @Override
954 public String toString() {
955 return getClass().getSimpleName() + '[' + user + '@' + url + ']';
956 }
957
958 private void checkOracleDriverVersion(String driver) {
959 if (driver != null && driver.contains("oracle")) {
960 Connection connection;
961 try {
962 connection = getConnection();
963 DatabaseMetaData metaData = connection.getMetaData();
964 VersionNumber driverVersion =
965 VersionNumber.valueOf(metaData.getDriverVersion());
966 if (driverVersion.compareTo(MIN_ORACLE_VERSION) < 0) {
967 logger.warn(
968 "Your Oracle driver has a bug in metadata support. Please update to 10.2.0.4 or newer. " +
969 "You can use that driver for accessing an Oracle 9 server as well.");
970 }
971 } catch (SQLException e) {
972 throw new ConfigurationError(e);
973 } finally {
974 close();
975 }
976 }
977 }
978
979 private void fetchDbMetaData() {
980 try {
981 importer = createJDBCImporter();
982 if (metaDataCache) {
983 importer = new CachingDBImporter((JDBCDBImporter) importer,
984 getEnvironment());
985 }
986 database = importer.importDatabase();
987 } catch (ConnectFailedException e) {
988 throw new ConfigurationError("Database not available. ", e);
989 } catch (ImportFailedException e) {
990 throw new ConfigurationError(
991 "Unexpected failure of database meta data import. ", e);
992 }
993 }
994
995 private JDBCDBImporter createJDBCImporter() {
996 return JDBCMetaDataUtil
997 .getJDBCDBImporter(getConnection(), user, catalogName, schemaName,
998 true, false, false, false, includeTables,
999 excludeTables);
1000 }
1001
1002 private QueryDataSource createQuery(String query, Context context,
1003 Connection connection) {
1004 return new QueryDataSource(connection, query, fetchSize,
1005 (dynamicQuerySupported ? context : null));
1006 }
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016 protected abstract PreparedStatement getStatement(
1017 ComplexTypeDescriptor descriptor, boolean insert,
1018 List<ColumnInfo> columnInfos);
1019
1020 private void parseTable(DBTable table) {
1021 logger.debug("Parsing table {}", table);
1022 String tableName = table.getName();
1023 tables.put(tableName.toUpperCase(), table);
1024 ComplexTypeDescriptor complexType;
1025 if (lazy) {
1026 complexType = new LazyTableComplexTypeDescriptor(table, this);
1027 } else {
1028 complexType = mapTableToComplexTypeDescriptor(table,
1029 new ComplexTypeDescriptor(tableName, this));
1030 }
1031 typeDescriptors.put(tableName, complexType);
1032 }
1033
1034
1035
1036
1037
1038
1039
1040
1041 public ComplexTypeDescriptor mapTableToComplexTypeDescriptor(DBTable table,
1042 ComplexTypeDescriptor complexType) {
1043
1044 DBPrimaryKeyConstraint pkConstraint = table.getPrimaryKeyConstraint();
1045 if (pkConstraint != null) {
1046 String[] pkColumnNames = pkConstraint.getColumnNames();
1047 if (pkColumnNames.length ==
1048 1) {
1049 String columnName = pkColumnNames[0];
1050 DBColumn column = table.getColumn(columnName);
1051 table.getColumn(columnName);
1052 String abstractType = JdbcMetaTypeMapper
1053 .abstractType(column.getType(),
1054 acceptUnknownColumnTypes);
1055 IdDescriptor idDescriptor =
1056 new IdDescriptor(columnName, this, abstractType);
1057 complexType.setComponent(idDescriptor);
1058 }
1059 }
1060
1061
1062 for (DBForeignKeyConstraint constraint : table
1063 .getForeignKeyConstraints()) {
1064 String[] foreignKeyColumnNames =
1065 constraint.getForeignKeyColumnNames();
1066 if (foreignKeyColumnNames.length == 1) {
1067 String fkColumnName = foreignKeyColumnNames[0];
1068 DBTable targetTable = constraint.getRefereeTable();
1069 DBColumn fkColumn =
1070 constraint.getTable().getColumn(fkColumnName);
1071 DBDataType concreteType = fkColumn.getType();
1072 String abstractType = JdbcMetaTypeMapper
1073 .abstractType(concreteType, acceptUnknownColumnTypes);
1074 ReferenceDescriptoror.html#ReferenceDescriptor">ReferenceDescriptor descriptor = new ReferenceDescriptor(
1075 fkColumnName,
1076 this,
1077 abstractType,
1078 targetTable.getName(),
1079 constraint.getRefereeColumnNames()[0]);
1080 descriptor.getLocalType(false).setSource(id);
1081 descriptor.setMinCount(new ConstantExpression<>(1L));
1082 descriptor.setMaxCount(new ConstantExpression<>(1L));
1083 boolean nullable = fkColumn.isNullable();
1084 descriptor.setNullable(nullable);
1085 complexType.setComponent(
1086 descriptor);
1087 if (logger.isDebugEnabled()) {
1088 logger.debug("Parsed reference " + table.getName() + '.' +
1089 descriptor);
1090 }
1091 } else {
1092
1093 }
1094 }
1095
1096 for (DBColumn column : table.getColumns()) {
1097 try {
1098 logger.debug("parsing column: {}", column);
1099 String columnName = column.getName();
1100 if (complexType.getComponent(columnName) != null) {
1101 continue;
1102 }
1103 String columnId = table.getName() + '.' + columnName;
1104 if (column.isVersionColumn()) {
1105 logger.debug("Leaving out version column {}", columnId);
1106 continue;
1107 }
1108 DBDataType columnType = column.getType();
1109 String type = JdbcMetaTypeMapper
1110 .abstractType(columnType, acceptUnknownColumnTypes);
1111 String defaultValue = column.getDefaultValue();
1112 SimpleTypeDescriptor typeDescriptor =
1113 new SimpleTypeDescriptor(columnId, this, type);
1114 if (defaultValue != null) {
1115 typeDescriptor.setDetailValue("constant", defaultValue);
1116 }
1117 if (column.getSize() != null) {
1118 typeDescriptor.setMaxLength(column.getSize());
1119 }
1120 if (column.getFractionDigits() != null) {
1121 if ("timestamp".equals(type)) {
1122 typeDescriptor.setGranularity("1970-01-02");
1123 } else {
1124 typeDescriptor.setGranularity(
1125 decimalGranularity(column.getFractionDigits()));
1126 }
1127 }
1128
1129 PartDescriptor descriptor =
1130 new PartDescriptor(columnName, this);
1131 descriptor.setLocalType(typeDescriptor);
1132 descriptor.setMinCount(new ConstantExpression<>(1L));
1133 descriptor.setMaxCount(new ConstantExpression<>(1L));
1134 descriptor.setNullable(column.getNotNullConstraint() == null);
1135 List<DBUniqueConstraint> ukConstraints =
1136 column.getUkConstraints();
1137 for (DBUniqueConstraint constraint : ukConstraints) {
1138 if (constraint.getColumnNames().length == 1) {
1139 descriptor.setUnique(true);
1140 } else {
1141 logger.debug(
1142 "Automated uniqueness assurance on multiple columns is not provided yet: " +
1143 constraint);
1144
1145 }
1146 }
1147 logger.debug(
1148 "parsed attribute " + columnId + ": " + descriptor);
1149 complexType.addComponent(descriptor);
1150 } catch (Exception e) {
1151 throw new ConfigurationError(
1152 "Error processing column " + column.getName() +
1153 " of table " + table.getName(), e);
1154 }
1155 }
1156 return complexType;
1157 }
1158
1159
1160
1161
1162
1163
1164
1165
1166 public List<ColumnInfo> getWriteColumnInfos(Entity entity, boolean insert) {
1167 String tableName = entity.type();
1168 DBTable table;
1169 if (entity.descriptor instanceof LazyTableComplexTypeDescriptor) {
1170 table = getTable(this.schemaName, tableName);
1171 } else {
1172 table = getTable(tableName);
1173 }
1174 List<String> pkColumnNames =
1175 CollectionUtil.toList(table.getPKColumnNames());
1176 ComplexTypeDescriptor typeDescriptor =
1177 (ComplexTypeDescriptor) getTypeDescriptor(tableName);
1178 Collection<ComponentDescriptor> componentDescriptors =
1179 typeDescriptor.getComponents();
1180 List<ColumnInfo> pkInfos = new ArrayList<>(componentDescriptors.size());
1181 List<ColumnInfo> normalInfos =
1182 new ArrayList<>(componentDescriptors.size());
1183 ComplexTypeDescriptor entityDescriptor = entity.descriptor();
1184 for (ComponentDescriptor dbCompDescriptor : componentDescriptors) {
1185 ComponentDescriptor enCompDescriptor =
1186 entityDescriptor.getComponent(dbCompDescriptor.getName());
1187 if (enCompDescriptor != null &&
1188 enCompDescriptor.getMode() == Mode.ignored) {
1189 continue;
1190 }
1191 if (dbCompDescriptor.getMode() != Mode.ignored) {
1192 String name = dbCompDescriptor.getName();
1193 SimpleTypeDescriptor type =
1194 (SimpleTypeDescriptor) dbCompDescriptor
1195 .getTypeDescriptor();
1196 PrimitiveType primitiveType = type.getPrimitiveType();
1197 if (primitiveType == null) {
1198 if (!acceptUnknownColumnTypes) {
1199 throw new ConfigurationError(
1200 "Column type of " + entityDescriptor.getName() +
1201 "." +
1202 dbCompDescriptor.getName() +
1203 " unknown: " + type.getName());
1204 } else if (entity.get(type.getName()) instanceof String) {
1205 primitiveType = PrimitiveType.STRING;
1206 } else {
1207 primitiveType = PrimitiveType.OBJECT;
1208 }
1209 }
1210 String primitiveTypeName = primitiveType.getName();
1211
1212 DBColumn column = table.getColumn(name);
1213 DBDataType columnType = column.getType();
1214 int sqlType = columnType.getJdbcType();
1215 Class<?> javaType =
1216 driverTypeMapper.concreteType(primitiveTypeName);
1217 ColumnInfo info = new ColumnInfo(name, sqlType, javaType);
1218 if (pkColumnNames.contains(name)) {
1219 pkInfos.add(info);
1220 } else {
1221 normalInfos.add(info);
1222 }
1223 }
1224 }
1225 if (insert) {
1226 pkInfos.addAll(normalInfos);
1227 return pkInfos;
1228 } else {
1229 normalInfos.addAll(pkInfos);
1230 return normalInfos;
1231 }
1232 }
1233
1234
1235
1236
1237
1238
1239
1240 public DBTable getTable(String tableName) {
1241 parseMetadataIfNecessary();
1242 DBTable table = findTableInConfiguredCatalogAndSchema(schemaName, tableName);
1243 if (table != null) {
1244 return table;
1245 } else {
1246 table = findAnyTableOfName(tableName);
1247 if (table != null) {
1248 logger.warn("Table '" + tableName + "' not found " +
1249 "in the expected catalog or schema." +
1250 "I have taken it from catalog '" + table.getCatalog() +
1251 "' and schema '" + table.getSchema() + "' instead. " +
1252 "You better make sure this is right and fix the configuration");
1253 return table;
1254 }
1255 }
1256 throw new ObjectNotFoundException("Table " + tableName);
1257 }
1258
1259
1260
1261
1262
1263
1264
1265
1266 public DBTable getTable(String schemaName, String tableName) {
1267 parseMetadataIfNecessary();
1268 DBTable table = findTableInConfiguredCatalogAndSchema(schemaName, tableName);
1269 if (table != null) {
1270 return table;
1271 } else {
1272 table = findAnyTableOfName(tableName);
1273 if (table != null) {
1274 logger.info("Table '" + tableName + "' not found " +
1275 "in the expected catalog or schema." +
1276 "I have taken it from catalog '" + table.getCatalog() +
1277 "' and schema '" + table.getSchema() + "' instead. " +
1278 "You better make sure this is right and fix the configuration");
1279 return table;
1280 }
1281 }
1282 throw new ObjectNotFoundException("Table " + tableName);
1283 }
1284
1285 private DBTable findAnyTableOfName(String tableName) {
1286 for (DBCatalog catalog : database.getCatalogs()) {
1287 for (DBSchema schema : catalog.getSchemas()) {
1288 DBTable table = schema.getTable(tableName);
1289 if (table != null) {
1290 return table;
1291 }
1292 }
1293 }
1294 return null;
1295 }
1296
1297 private DBTable findTableInConfiguredCatalogAndSchema(String tableName) {
1298 DBCatalog catalog = database.getCatalog(catalogName);
1299 DBSchema dbSchema;
1300 if (catalog == null) {
1301
1302 return database.getCatalog("benerator").getSchema(schemaName).getTable(tableName);
1303 } else {
1304 dbSchema = catalog.getSchema(schemaName);
1305 }
1306 if (dbSchema != null) {
1307 return dbSchema.getTable(tableName);
1308 }
1309 return null;
1310 }
1311
1312 private DBTable findTableInConfiguredCatalogAndSchema(String schemaName, String tableName) {
1313 DBSchema dbSchema;
1314 DBCatalog catalog = database.getCatalog(catalogName);
1315 if (catalog == null) {
1316
1317 dbSchema = database.getSchema(schemaName);
1318 } else {
1319 dbSchema = catalog.getSchema(schemaName);
1320 }
1321 if (dbSchema != null) {
1322 return dbSchema.getTable(tableName);
1323 }
1324 return null;
1325 }
1326
1327 private void persistOrUpdate(Entity entity, boolean insert) {
1328 parseMetadataIfNecessary();
1329 List<ColumnInfo> writeColumnInfos = getWriteColumnInfos(entity, insert);
1330 try {
1331 String tableName = entity.type();
1332 PreparedStatement statement =
1333 getStatement(entity.descriptor(), insert, writeColumnInfos);
1334 for (int i = 0; i < writeColumnInfos.size(); i++) {
1335 ColumnInfo info = writeColumnInfos.get(i);
1336 Object jdbcValue = entity.getComponent(info.name);
1337 if (info.type != null) {
1338 jdbcValue = AnyConverter.convert(jdbcValue, info.type);
1339 }
1340 try {
1341 boolean criticalOracleType =
1342 (dialect instanceof OracleDialect &&
1343 (info.sqlType == Types.NCLOB ||
1344 info.sqlType == Types.OTHER));
1345 if (jdbcValue != null ||
1346 criticalOracleType) {
1347
1348 statement.setObject(i + 1, jdbcValue);
1349 } else {
1350 statement.setNull(i + 1, info.sqlType);
1351 }
1352 } catch (SQLException e) {
1353 throw new RuntimeException(
1354 "error setting column " + tableName + '.' +
1355 info.name, e);
1356 }
1357 }
1358 if (batch) {
1359 statement.addBatch();
1360 } else {
1361 int rowCount = statement.executeUpdate();
1362 if (rowCount == 0) {
1363 throw new RuntimeException(
1364 "Update failed because, since there is no database entry with the PK of " +
1365 entity);
1366 }
1367 }
1368 } catch (Exception e) {
1369 throw new RuntimeException("Error in persisting " + entity, e);
1370 }
1371 }
1372
1373 private void parseMetadataIfNecessary() {
1374 if (typeDescriptors == null) {
1375 parseMetaData();
1376 }
1377 }
1378
1379 }