Refactoring PKs
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/929b6cb4 Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/929b6cb4 Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/929b6cb4 Branch: refs/heads/master Commit: 929b6cb45285758c042ebac5ac818b858f8e2448 Parents: 2733a9f Author: Aleksey Pleshkanev <priest...@hotmail.com> Authored: Sun Apr 1 00:19:04 2018 +0300 Committer: Aleksey Pleshkanev <priest...@hotmail.com> Committed: Sun Apr 1 00:19:04 2018 +0300 ---------------------------------------------------------------------- .../configuration/server/ServerModule.java | 58 +- .../org/apache/cayenne/dba/JdbcAdapter.java | 1121 +++++++++--------- .../apache/cayenne/dba/db2/DB2PkGenerator.java | 62 +- .../org/apache/cayenne/dba/db2/DB2Sniffer.java | 36 +- .../cayenne/dba/derby/DerbyPkGenerator.java | 64 +- .../apache/cayenne/dba/derby/DerbySniffer.java | 35 +- .../dba/frontbase/FrontBasePkGenerator.java | 196 +-- .../cayenne/dba/frontbase/FrontBaseSniffer.java | 33 +- .../apache/cayenne/dba/h2/H2PkGenerator.java | 44 +- .../org/apache/cayenne/dba/h2/H2Sniffer.java | 32 +- .../cayenne/dba/ingres/IngresPkGenerator.java | 28 +- .../cayenne/dba/ingres/IngresSniffer.java | 33 +- .../cayenne/dba/mysql/MySQLPkGenerator.java | 276 ++--- .../apache/cayenne/dba/mysql/MySQLSniffer.java | 19 +- .../dba/openbase/OpenBasePkGenerator.java | 464 ++++---- .../cayenne/dba/openbase/OpenBaseSniffer.java | 33 +- .../cayenne/dba/oracle/OraclePkGenerator.java | 386 +++--- .../cayenne/dba/oracle/OracleSniffer.java | 30 +- .../dba/postgres/PostgresPkGenerator.java | 46 +- .../cayenne/dba/postgres/PostgresSniffer.java | 33 +- .../cayenne/dba/sqlite/SQLiteSniffer.java | 28 +- .../dba/sqlserver/SQLServerPkGenerator.java | 2 +- .../cayenne/dba/sqlserver/SQLServerSniffer.java | 2 +- .../cayenne/dba/sybase/SybaseSniffer.java | 33 +- .../server/DataDomainProviderTest.java | 33 +- .../dba/sqlserver/SQLServerSnifferIT.java | 2 +- .../unit/di/server/ServerCaseModule.java | 23 +- 27 files changed, 1740 insertions(+), 1412 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cayenne/blob/929b6cb4/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java index ac7753f..aa6450c 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java @@ -85,20 +85,40 @@ import org.apache.cayenne.configuration.xml.XMLDataMapLoader; import org.apache.cayenne.configuration.xml.XMLReaderProvider; import org.apache.cayenne.dba.JdbcPkGenerator; import org.apache.cayenne.dba.PkGenerator; +import org.apache.cayenne.dba.db2.DB2Adapter; +import org.apache.cayenne.dba.db2.DB2PkGenerator; import org.apache.cayenne.dba.db2.DB2Sniffer; +import org.apache.cayenne.dba.derby.DerbyAdapter; +import org.apache.cayenne.dba.derby.DerbyPkGenerator; import org.apache.cayenne.dba.derby.DerbySniffer; import org.apache.cayenne.dba.firebird.FirebirdSniffer; +import org.apache.cayenne.dba.frontbase.FrontBaseAdapter; +import org.apache.cayenne.dba.frontbase.FrontBasePkGenerator; import org.apache.cayenne.dba.frontbase.FrontBaseSniffer; +import org.apache.cayenne.dba.h2.H2Adapter; +import org.apache.cayenne.dba.h2.H2PkGenerator; import org.apache.cayenne.dba.h2.H2Sniffer; import org.apache.cayenne.dba.hsqldb.HSQLDBSniffer; +import org.apache.cayenne.dba.ingres.IngresAdapter; +import org.apache.cayenne.dba.ingres.IngresPkGenerator; import org.apache.cayenne.dba.ingres.IngresSniffer; +import org.apache.cayenne.dba.mysql.MySQLAdapter; +import org.apache.cayenne.dba.mysql.MySQLPkGenerator; import org.apache.cayenne.dba.mysql.MySQLSniffer; +import org.apache.cayenne.dba.openbase.OpenBaseAdapter; +import org.apache.cayenne.dba.openbase.OpenBasePkGenerator; import org.apache.cayenne.dba.openbase.OpenBaseSniffer; +import org.apache.cayenne.dba.oracle.Oracle8Adapter; +import org.apache.cayenne.dba.oracle.OracleAdapter; +import org.apache.cayenne.dba.oracle.OraclePkGenerator; import org.apache.cayenne.dba.oracle.OracleSniffer; +import org.apache.cayenne.dba.postgres.PostgresAdapter; +import org.apache.cayenne.dba.postgres.PostgresPkGenerator; import org.apache.cayenne.dba.postgres.PostgresSniffer; import org.apache.cayenne.dba.sqlite.SQLiteSniffer; import org.apache.cayenne.dba.sqlserver.SQLServerAdapter; import org.apache.cayenne.dba.sqlserver.SQLServerSniffer; +import org.apache.cayenne.dba.sybase.SybaseAdapter; import org.apache.cayenne.dba.sybase.SybasePkGenerator; import org.apache.cayenne.dba.sybase.SybaseSniffer; import org.apache.cayenne.di.AdhocObjectFactory; @@ -301,21 +321,30 @@ public class ServerModule implements Module { // configure known DbAdapter detectors in reverse order of popularity. // Users can add their own to install custom adapters automatically - contributeAdapterDetectors(binder).add(FirebirdSniffer.class).add(OpenBaseSniffer.class) - .add(FrontBaseSniffer.class).add(IngresSniffer.class).add(SQLiteSniffer.class).add(DB2Sniffer.class) - .add(H2Sniffer.class).add(HSQLDBSniffer.class).add(SybaseSniffer.class).add(DerbySniffer.class) - .add(SQLServerSniffer.class).add(OracleSniffer.class).add(PostgresSniffer.class) + contributeAdapterDetectors(binder) + .add(FirebirdSniffer.class) + .add(OpenBaseSniffer.class) + .add(FrontBaseSniffer.class) + .add(IngresSniffer.class) + .add(SQLiteSniffer.class) + .add(DB2Sniffer.class) + .add(H2Sniffer.class) + .add(HSQLDBSniffer.class) + .add(SybaseSniffer.class) + .add(DerbySniffer.class) + .add(SQLServerSniffer.class) + .add(OracleSniffer.class) + .add(PostgresSniffer.class) .add(MySQLSniffer.class); //installing Pk for adapters binder.bind(PkGeneratorFactoryProvider.class).to(PkGeneratorFactoryProvider.class); binder.bind(PkGenerator.class).to(JdbcPkGenerator.class); - contributePkGenerators(binder).put(SQLServerAdapter.class.getName(), SybasePkGenerator.class); - /*contributePkGenerators(binder) + contributePkGenerators(binder) .put(DB2Adapter.class.getName(), DB2PkGenerator.class) .put(DerbyAdapter.class.getName(), DerbyPkGenerator.class) - .put(FrontBaseAdapter.class.getName(), FrontBaseAdapter.class) + .put(FrontBaseAdapter.class.getName(), FrontBasePkGenerator.class) .put(H2Adapter.class.getName(), H2PkGenerator.class) .put(IngresAdapter.class.getName(), IngresPkGenerator.class) .put(MySQLAdapter.class.getName(), MySQLPkGenerator.class) @@ -325,7 +354,7 @@ public class ServerModule implements Module { .put(PostgresAdapter.class.getName(), PostgresPkGenerator.class) .put(SQLServerAdapter.class.getName(), SybasePkGenerator.class) .put(SybaseAdapter.class.getName(), SybasePkGenerator.class); -*/ + // configure a filter chain with only one TransactionFilter as default contributeDomainFilters(binder).add(TransactionFilter.class); @@ -336,10 +365,17 @@ public class ServerModule implements Module { contributeDefaultTypes(binder) .add(new VoidType()) .add(new BigDecimalType()) - .add(new BooleanType()).add(new ByteType(false)).add(new CharType(false, true)) - .add(new DoubleType()).add(new FloatType()).add(new IntegerType()).add(new LongType()).add(new ShortType(false)) + .add(new BooleanType()) + .add(new ByteType(false)) + .add(new CharType(false, true)) + .add(new DoubleType()) + .add(new FloatType()) + .add(new IntegerType()) + .add(new LongType()) + .add(new ShortType(false)) .add(new ByteArrayType(false, true)) - .add(new DateType()).add(new TimeType()).add(new TimestampType()) + .add(new DateType()).add(new TimeType()) + .add(new TimestampType()) // should be converted from ExtendedType to ValueType .add(new UtilDateType()).add(new CalendarType<>(GregorianCalendar.class)).add(new CalendarType<>(Calendar.class)); contributeUserTypes(binder); http://git-wip-us.apache.org/repos/asf/cayenne/blob/929b6cb4/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java index f4b0a1c..72b84a9 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java @@ -65,565 +65,566 @@ import java.util.List; */ public class JdbcAdapter implements DbAdapter { - private PkGenerator pkGenerator; - protected QuotingStrategy quotingStrategy; - - protected TypesHandler typesHandler; - protected ExtendedTypeMap extendedTypes; - protected boolean supportsBatchUpdates; - protected boolean supportsUniqueConstraints; - protected boolean supportsGeneratedKeys; - protected EJBQLTranslatorFactory ejbqlTranslatorFactory; - - protected ResourceLocator resourceLocator; - protected boolean caseInsensitiveCollations; - - /** - * @since 3.1 - * @deprecated since 4.0 BatchQueryBuilderfactory is attached to the DataNode. - */ - @Inject - protected BatchTranslatorFactory batchQueryBuilderFactory; - - @Inject - protected JdbcEventLogger logger; - - /** - * Creates new JdbcAdapter with a set of default parameters. - */ - public JdbcAdapter(@Inject RuntimeProperties runtimeProperties, - @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes, - @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes, - @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories, - @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator, - @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) { - - // init defaults - this.setSupportsBatchUpdates(false); - this.setSupportsUniqueConstraints(true); - this.caseInsensitiveCollations = runtimeProperties.getBoolean(Constants.CI_PROPERTY, false); - this.resourceLocator = resourceLocator; - - this.pkGenerator = createPkGenerator(); - this.quotingStrategy = createQuotingStrategy(); - - this.ejbqlTranslatorFactory = createEJBQLTranslatorFactory(); - this.typesHandler = TypesHandler.getHandler(findResource("/types.xml")); - this.extendedTypes = new ExtendedTypeMap(); - initExtendedTypes(defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, valueObjectTypeRegistry); - } - - /** - * Returns default separator - a semicolon. - * - * @since 1.0.4 - */ - @Override - public String getBatchTerminator() { - return ";"; - } - - /** - * @since 3.1 - */ - public JdbcEventLogger getJdbcEventLogger() { - return this.logger; - } - - /** - * Locates and returns a named adapter resource. A resource can be an XML - * file, etc. - * <p> - * This implementation is based on the premise that each adapter is located - * in its own Java package and all resources are in the same package as - * well. Resource lookup is recursive, so that if DbAdapter is a subclass of - * another adapter, parent adapter package is searched as a failover. - * </p> - * - * @since 3.0 - */ - protected URL findResource(String name) { - Class<?> adapterClass = getClass(); - - while (adapterClass != null && JdbcAdapter.class.isAssignableFrom(adapterClass)) { - - String path = Util.getPackagePath(adapterClass.getName()) + name; - Collection<Resource> resources = resourceLocator.findResources(path); - - if (!resources.isEmpty()) { - return resources.iterator().next().getURL(); - } - - adapterClass = adapterClass.getSuperclass(); - } - - return null; - } - - /** - * Called from {@link #initExtendedTypes(List, List, List, ValueObjectTypeRegistry)} to load - * adapter-specific types into the ExtendedTypeMap right after the default - * types are loaded, but before the DI overrides are. This method has - * specific implementations in JdbcAdapter subclasses. - */ - protected void configureExtendedTypes(ExtendedTypeMap map) { - // noop... subclasses may override to install custom types - } - - /** - * @since 3.1 - */ - protected void initExtendedTypes(List<ExtendedType> defaultExtendedTypes, List<ExtendedType> userExtendedTypes, - List<ExtendedTypeFactory> extendedTypeFactories, - ValueObjectTypeRegistry valueObjectTypeRegistry) { - for (ExtendedType type : defaultExtendedTypes) { - extendedTypes.registerType(type); - } - - // loading adapter specific extended types - configureExtendedTypes(extendedTypes); - - for (ExtendedType type : userExtendedTypes) { - extendedTypes.registerType(type); - } - for (ExtendedTypeFactory typeFactory : extendedTypeFactories) { - extendedTypes.addFactory(typeFactory); - } - extendedTypes.addFactory(new ValueObjectTypeFactory(extendedTypes, valueObjectTypeRegistry)); - } - - /** - * Creates and returns a primary key generator. This factory method should - * be overriden by JdbcAdapter subclasses to provide custom implementations - * of PKGenerator. - */ - protected PkGenerator createPkGenerator() { - return new JdbcPkGenerator(this); - } - - /** - * Creates and returns an {@link EJBQLTranslatorFactory} used to generate - * visitors for EJBQL to SQL translations. This method should be overriden - * by subclasses that need to customize EJBQL generation. - * - * @since 3.0 - */ - protected EJBQLTranslatorFactory createEJBQLTranslatorFactory() { - JdbcEJBQLTranslatorFactory translatorFactory = new JdbcEJBQLTranslatorFactory(); - translatorFactory.setCaseInsensitive(caseInsensitiveCollations); - return translatorFactory; - } - - /** - * Returns primary key generator associated with this DbAdapter. - */ - @Override - public PkGenerator getPkGenerator() { - return pkGenerator; - } - - /** - * Sets new primary key generator. - * - * @since 1.1 - */ - public void setPkGenerator(PkGenerator pkGenerator) { - this.pkGenerator = pkGenerator; - } - - /** - * Returns true. - * - * @since 1.1 - */ - @Override - public boolean supportsUniqueConstraints() { - return supportsUniqueConstraints; - } - - /** - * Returns true. - * - * @since 4.0 - */ - @Override - public boolean supportsCatalogsOnReverseEngineering() { - return true; - } - - /** - * @since 1.1 - */ - public void setSupportsUniqueConstraints(boolean flag) { - this.supportsUniqueConstraints = flag; - } - - /** - * Returns true if supplied type can have a length attribute as a part of - * column definition - * - * @since 4.0 - */ - public boolean typeSupportsLength(int type) { - return JdbcAdapter.supportsLength(type); - } - - /** - * Returns true if supplied type can have a length attribute as a part of - * column definition - * <p/> - * TODO: this is a static method only to support the deprecated method - * {@link TypesMapping#supportsLength(int)} When the deprecated method is - * removed this body should be moved in to {@link #typeSupportsLength(int)} - * - * @deprecated - */ - static boolean supportsLength(int type) { - return type == Types.BINARY || type == Types.CHAR || type == Types.NCHAR || type == Types.NVARCHAR - || type == Types.LONGNVARCHAR || type == Types.DECIMAL || type == Types.DOUBLE || type == Types.FLOAT - || type == Types.NUMERIC || type == Types.REAL || type == Types.VARBINARY || type == Types.VARCHAR; - } - - /** - * @since 3.0 - */ - @Override - public Collection<String> dropTableStatements(DbEntity table) { - return Collections.singleton("DROP TABLE " + quotingStrategy.quotedFullyQualifiedName(table)); - } - - /** - * Returns a SQL string that can be used to create database table - * corresponding to <code>ent</code> parameter. - */ - @Override - public String createTable(DbEntity entity) { - - StringBuffer sqlBuffer = new StringBuffer(); - sqlBuffer.append("CREATE TABLE "); - sqlBuffer.append(quotingStrategy.quotedFullyQualifiedName(entity)); - - sqlBuffer.append(" ("); - // columns - Iterator<DbAttribute> it = entity.getAttributes().iterator(); - if (it.hasNext()) { - boolean first = true; - while (it.hasNext()) { - if (first) { - first = false; - } else { - sqlBuffer.append(", "); - } - - DbAttribute column = it.next(); - - // attribute may not be fully valid, do a simple check - if (column.getType() == TypesMapping.NOT_DEFINED) { - throw new CayenneRuntimeException("Undefined type for attribute '%s.%s'." - , entity.getFullyQualifiedName(), column.getName()); - } - - createTableAppendColumn(sqlBuffer, column); - } - - createTableAppendPKClause(sqlBuffer, entity); - } - - sqlBuffer.append(')'); - return sqlBuffer.toString(); - } - - /** - * @since 1.2 - */ - protected void createTableAppendPKClause(StringBuffer sqlBuffer, DbEntity entity) { - - Iterator<DbAttribute> pkit = entity.getPrimaryKeys().iterator(); - if (pkit.hasNext()) { - sqlBuffer.append(", PRIMARY KEY ("); - boolean firstPk = true; - - while (pkit.hasNext()) { - if (firstPk) { - firstPk = false; - } else { - sqlBuffer.append(", "); - } - - DbAttribute at = pkit.next(); - - sqlBuffer.append(quotingStrategy.quotedName(at)); - } - sqlBuffer.append(')'); - } - } - - /** - * Appends SQL for column creation to CREATE TABLE buffer. - * - * @since 1.2 - */ - @Override - public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) { - sqlBuffer.append(quotingStrategy.quotedName(column)); - sqlBuffer.append(' ').append(getType(this, column)); - - sqlBuffer.append(sizeAndPrecision(this, column)); - sqlBuffer.append(column.isMandatory() ? " NOT NULL" : " NULL"); - } - - public static String sizeAndPrecision(DbAdapter adapter, DbAttribute column) { - if (!adapter.typeSupportsLength(column.getType())) { - return ""; - } - - int len = column.getMaxLength(); - int scale = TypesMapping.isDecimal(column.getType()) && column.getType() != Types.FLOAT ? column.getScale() - : -1; - - // sanity check - if (scale > len) { - scale = -1; - } - - if (len > 0) { - return "(" + len + (scale >= 0 ? ", " + scale : "") + ")"; - } - - return ""; - } - - public static String getType(DbAdapter adapter, DbAttribute column) { - String[] types = adapter.externalTypesForJdbcType(column.getType()); - if (types == null || types.length == 0) { - String entityName = column.getEntity() != null ? column.getEntity().getFullyQualifiedName() : "<null>"; - throw new CayenneRuntimeException("Undefined type for attribute '%s.%s': %s." - , entityName, column.getName(), column.getType()); - } - return types[0]; - } - - /** - * Returns a DDL string to create a unique constraint over a set of columns. - * - * @since 1.1 - */ - @Override - public String createUniqueConstraint(DbEntity source, Collection<DbAttribute> columns) { - - if (columns == null || columns.isEmpty()) { - throw new CayenneRuntimeException("Can't create UNIQUE constraint - no columns specified."); - } - - StringBuilder buf = new StringBuilder(); - - buf.append("ALTER TABLE "); - buf.append(quotingStrategy.quotedFullyQualifiedName(source)); - buf.append(" ADD UNIQUE ("); - - Iterator<DbAttribute> it = columns.iterator(); - DbAttribute first = it.next(); - buf.append(quotingStrategy.quotedName(first)); - - while (it.hasNext()) { - DbAttribute next = it.next(); - buf.append(", "); - buf.append(quotingStrategy.quotedName(next)); - } - - buf.append(")"); - - return buf.toString(); - } - - /** - * Returns a SQL string that can be used to create a foreign key constraint - * for the relationship. - */ - @Override - public String createFkConstraint(DbRelationship rel) { - - DbEntity source = rel.getSourceEntity(); - StringBuilder buf = new StringBuilder(); - StringBuilder refBuf = new StringBuilder(); - - buf.append("ALTER TABLE "); - - buf.append(quotingStrategy.quotedFullyQualifiedName(source)); - buf.append(" ADD FOREIGN KEY ("); - - boolean first = true; - - for (DbJoin join : rel.getJoins()) { - if (first) { - first = false; - } else { - buf.append(", "); - refBuf.append(", "); - } - - buf.append(quotingStrategy.quotedSourceName(join)); - refBuf.append(quotingStrategy.quotedTargetName(join)); - } - - buf.append(") REFERENCES "); - - buf.append(quotingStrategy.quotedFullyQualifiedName(rel.getTargetEntity())); - - buf.append(" (").append(refBuf.toString()).append(')'); - return buf.toString(); - } - - @Override - public String[] externalTypesForJdbcType(int type) { - return typesHandler.externalTypesForJdbcType(type); - } - - @Override - public ExtendedTypeMap getExtendedTypes() { - return extendedTypes; - } - - @Override - public DbAttribute buildAttribute(String name, String typeName, int type, int size, int scale, boolean allowNulls) { - - DbAttribute attr = new DbAttribute(); - attr.setName(name); - attr.setType(type); - attr.setMandatory(!allowNulls); - - if (size >= 0) { - attr.setMaxLength(size); - } - - if (scale >= 0) { - attr.setScale(scale); - } - - return attr; - } - - @Override - public String tableTypeForTable() { - return "TABLE"; - } - - @Override - public String tableTypeForView() { - return "VIEW"; - } - - /** - * Creates and returns a default implementation of a qualifier translator. - */ - @Override - public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) { - QualifierTranslator translator = new QualifierTranslator(queryAssembler); - translator.setCaseInsensitive(caseInsensitiveCollations); - return translator; - } - - /** - * Uses JdbcActionBuilder to create the right action. - * - * @since 1.2 - */ - @Override - public SQLAction getAction(Query query, DataNode node) { - return query.createSQLAction(new JdbcActionBuilder(node)); - } - - @Override - public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) { - return new DefaultSelectTranslator(query, this, entityResolver); - } - - @Override - public void bindParameter(PreparedStatement statement, ParameterBinding binding) - throws SQLException, Exception { - - if (binding.getValue() == null) { - statement.setNull(binding.getStatementPosition(), binding.getJdbcType()); - } else { - binding.getExtendedType().setJdbcObject(statement, - binding.getValue(), - binding.getStatementPosition(), - binding.getJdbcType(), - binding.getScale()); - } - } - - @Override - public boolean supportsBatchUpdates() { - return this.supportsBatchUpdates; - } - - public void setSupportsBatchUpdates(boolean flag) { - this.supportsBatchUpdates = flag; - } - - /** - * @since 1.2 - */ - @Override - public boolean supportsGeneratedKeys() { - return supportsGeneratedKeys; - } - - /** - * @since 1.2 - */ - public void setSupportsGeneratedKeys(boolean flag) { - this.supportsGeneratedKeys = flag; - } - - /** - * Returns a translator factory for EJBQL to SQL translation. The factory is - * normally initialized in constructor by calling - * {@link #createEJBQLTranslatorFactory()}, and can be changed later by - * calling {@link #setEjbqlTranslatorFactory(EJBQLTranslatorFactory)}. - * - * @since 3.0 - */ - public EJBQLTranslatorFactory getEjbqlTranslatorFactory() { - return ejbqlTranslatorFactory; - } - - /** - * Sets a translator factory for EJBQL to SQL translation. This property is - * normally initialized in constructor by calling - * {@link #createEJBQLTranslatorFactory()}, so users would only override it - * if they need to customize EJBQL translation. - * - * @since 3.0 - */ - public void setEjbqlTranslatorFactory(EJBQLTranslatorFactory ejbqlTranslatorFactory) { - this.ejbqlTranslatorFactory = ejbqlTranslatorFactory; - } - - /** - * @return - * @since 4.0 - */ - protected QuotingStrategy createQuotingStrategy() { - return new DefaultQuotingStrategy("\"", "\""); - } - - /** - * @since 4.0 - */ - public QuotingStrategy getQuotingStrategy() { - return quotingStrategy; - } - - /** - * Simply returns this, as JdbcAdapter is not a wrapper. - * - * @since 4.0 - */ - @Override - public DbAdapter unwrap() { - return this; - } + private PkGenerator pkGenerator; + protected QuotingStrategy quotingStrategy; + + protected TypesHandler typesHandler; + protected ExtendedTypeMap extendedTypes; + protected boolean supportsBatchUpdates; + protected boolean supportsUniqueConstraints; + protected boolean supportsGeneratedKeys; + protected EJBQLTranslatorFactory ejbqlTranslatorFactory; + + protected ResourceLocator resourceLocator; + protected boolean caseInsensitiveCollations; + + /** + * @since 3.1 + * @deprecated since 4.0 BatchQueryBuilderfactory is attached to the DataNode. + */ + @Inject + protected BatchTranslatorFactory batchQueryBuilderFactory; + + @Inject + protected JdbcEventLogger logger; + + /** + * Creates new JdbcAdapter with a set of default parameters. + */ + public JdbcAdapter(@Inject RuntimeProperties runtimeProperties, + @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes, + @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes, + @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories, + @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator, + @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) { + + // init defaults + this.setSupportsBatchUpdates(false); + this.setSupportsUniqueConstraints(true); + this.caseInsensitiveCollations = runtimeProperties.getBoolean(Constants.CI_PROPERTY, false); + this.resourceLocator = resourceLocator; + + this.pkGenerator = createPkGenerator(); + this.quotingStrategy = createQuotingStrategy(); + + this.ejbqlTranslatorFactory = createEJBQLTranslatorFactory(); + this.typesHandler = TypesHandler.getHandler(findResource("/types.xml")); + this.extendedTypes = new ExtendedTypeMap(); + initExtendedTypes(defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, valueObjectTypeRegistry); + } + + /** + * Returns default separator - a semicolon. + * + * @since 1.0.4 + */ + @Override + public String getBatchTerminator() { + return ";"; + } + + /** + * @since 3.1 + */ + public JdbcEventLogger getJdbcEventLogger() { + return this.logger; + } + + /** + * Locates and returns a named adapter resource. A resource can be an XML + * file, etc. + * <p> + * This implementation is based on the premise that each adapter is located + * in its own Java package and all resources are in the same package as + * well. Resource lookup is recursive, so that if DbAdapter is a subclass of + * another adapter, parent adapter package is searched as a failover. + * </p> + * + * @since 3.0 + */ + protected URL findResource(String name) { + Class<?> adapterClass = getClass(); + + while (adapterClass != null && JdbcAdapter.class.isAssignableFrom(adapterClass)) { + + String path = Util.getPackagePath(adapterClass.getName()) + name; + Collection<Resource> resources = resourceLocator.findResources(path); + + if (!resources.isEmpty()) { + return resources.iterator().next().getURL(); + } + + adapterClass = adapterClass.getSuperclass(); + } + + return null; + } + + /** + * Called from {@link #initExtendedTypes(List, List, List, ValueObjectTypeRegistry)} to load + * adapter-specific types into the ExtendedTypeMap right after the default + * types are loaded, but before the DI overrides are. This method has + * specific implementations in JdbcAdapter subclasses. + */ + protected void configureExtendedTypes(ExtendedTypeMap map) { + // noop... subclasses may override to install custom types + } + + /** + * @since 3.1 + */ + protected void initExtendedTypes(List<ExtendedType> defaultExtendedTypes, List<ExtendedType> userExtendedTypes, + List<ExtendedTypeFactory> extendedTypeFactories, + ValueObjectTypeRegistry valueObjectTypeRegistry) { + for (ExtendedType type : defaultExtendedTypes) { + extendedTypes.registerType(type); + } + + // loading adapter specific extended types + configureExtendedTypes(extendedTypes); + + for (ExtendedType type : userExtendedTypes) { + extendedTypes.registerType(type); + } + for (ExtendedTypeFactory typeFactory : extendedTypeFactories) { + extendedTypes.addFactory(typeFactory); + } + extendedTypes.addFactory(new ValueObjectTypeFactory(extendedTypes, valueObjectTypeRegistry)); + } + + /** + * Creates and returns a primary key generator. This factory method should + * be overriden by JdbcAdapter subclasses to provide custom implementations + * of PKGenerator. + */ + @Deprecated + protected PkGenerator createPkGenerator() { + return new JdbcPkGenerator(this); + } + + /** + * Creates and returns an {@link EJBQLTranslatorFactory} used to generate + * visitors for EJBQL to SQL translations. This method should be overriden + * by subclasses that need to customize EJBQL generation. + * + * @since 3.0 + */ + protected EJBQLTranslatorFactory createEJBQLTranslatorFactory() { + JdbcEJBQLTranslatorFactory translatorFactory = new JdbcEJBQLTranslatorFactory(); + translatorFactory.setCaseInsensitive(caseInsensitiveCollations); + return translatorFactory; + } + + /** + * Returns primary key generator associated with this DbAdapter. + */ + @Override + public PkGenerator getPkGenerator() { + return pkGenerator; + } + + /** + * Sets new primary key generator. + * + * @since 1.1 + */ + public void setPkGenerator(PkGenerator pkGenerator) { + this.pkGenerator = pkGenerator; + } + + /** + * Returns true. + * + * @since 1.1 + */ + @Override + public boolean supportsUniqueConstraints() { + return supportsUniqueConstraints; + } + + /** + * Returns true. + * + * @since 4.0 + */ + @Override + public boolean supportsCatalogsOnReverseEngineering() { + return true; + } + + /** + * @since 1.1 + */ + public void setSupportsUniqueConstraints(boolean flag) { + this.supportsUniqueConstraints = flag; + } + + /** + * Returns true if supplied type can have a length attribute as a part of + * column definition + * + * @since 4.0 + */ + public boolean typeSupportsLength(int type) { + return JdbcAdapter.supportsLength(type); + } + + /** + * Returns true if supplied type can have a length attribute as a part of + * column definition + * <p/> + * TODO: this is a static method only to support the deprecated method + * {@link TypesMapping#supportsLength(int)} When the deprecated method is + * removed this body should be moved in to {@link #typeSupportsLength(int)} + * + * @deprecated + */ + static boolean supportsLength(int type) { + return type == Types.BINARY || type == Types.CHAR || type == Types.NCHAR || type == Types.NVARCHAR + || type == Types.LONGNVARCHAR || type == Types.DECIMAL || type == Types.DOUBLE || type == Types.FLOAT + || type == Types.NUMERIC || type == Types.REAL || type == Types.VARBINARY || type == Types.VARCHAR; + } + + /** + * @since 3.0 + */ + @Override + public Collection<String> dropTableStatements(DbEntity table) { + return Collections.singleton("DROP TABLE " + quotingStrategy.quotedFullyQualifiedName(table)); + } + + /** + * Returns a SQL string that can be used to create database table + * corresponding to <code>ent</code> parameter. + */ + @Override + public String createTable(DbEntity entity) { + + StringBuffer sqlBuffer = new StringBuffer(); + sqlBuffer.append("CREATE TABLE "); + sqlBuffer.append(quotingStrategy.quotedFullyQualifiedName(entity)); + + sqlBuffer.append(" ("); + // columns + Iterator<DbAttribute> it = entity.getAttributes().iterator(); + if (it.hasNext()) { + boolean first = true; + while (it.hasNext()) { + if (first) { + first = false; + } else { + sqlBuffer.append(", "); + } + + DbAttribute column = it.next(); + + // attribute may not be fully valid, do a simple check + if (column.getType() == TypesMapping.NOT_DEFINED) { + throw new CayenneRuntimeException("Undefined type for attribute '%s.%s'." + , entity.getFullyQualifiedName(), column.getName()); + } + + createTableAppendColumn(sqlBuffer, column); + } + + createTableAppendPKClause(sqlBuffer, entity); + } + + sqlBuffer.append(')'); + return sqlBuffer.toString(); + } + + /** + * @since 1.2 + */ + protected void createTableAppendPKClause(StringBuffer sqlBuffer, DbEntity entity) { + + Iterator<DbAttribute> pkit = entity.getPrimaryKeys().iterator(); + if (pkit.hasNext()) { + sqlBuffer.append(", PRIMARY KEY ("); + boolean firstPk = true; + + while (pkit.hasNext()) { + if (firstPk) { + firstPk = false; + } else { + sqlBuffer.append(", "); + } + + DbAttribute at = pkit.next(); + + sqlBuffer.append(quotingStrategy.quotedName(at)); + } + sqlBuffer.append(')'); + } + } + + /** + * Appends SQL for column creation to CREATE TABLE buffer. + * + * @since 1.2 + */ + @Override + public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) { + sqlBuffer.append(quotingStrategy.quotedName(column)); + sqlBuffer.append(' ').append(getType(this, column)); + + sqlBuffer.append(sizeAndPrecision(this, column)); + sqlBuffer.append(column.isMandatory() ? " NOT NULL" : " NULL"); + } + + public static String sizeAndPrecision(DbAdapter adapter, DbAttribute column) { + if (!adapter.typeSupportsLength(column.getType())) { + return ""; + } + + int len = column.getMaxLength(); + int scale = TypesMapping.isDecimal(column.getType()) && column.getType() != Types.FLOAT ? column.getScale() + : -1; + + // sanity check + if (scale > len) { + scale = -1; + } + + if (len > 0) { + return "(" + len + (scale >= 0 ? ", " + scale : "") + ")"; + } + + return ""; + } + + public static String getType(DbAdapter adapter, DbAttribute column) { + String[] types = adapter.externalTypesForJdbcType(column.getType()); + if (types == null || types.length == 0) { + String entityName = column.getEntity() != null ? column.getEntity().getFullyQualifiedName() : "<null>"; + throw new CayenneRuntimeException("Undefined type for attribute '%s.%s': %s." + , entityName, column.getName(), column.getType()); + } + return types[0]; + } + + /** + * Returns a DDL string to create a unique constraint over a set of columns. + * + * @since 1.1 + */ + @Override + public String createUniqueConstraint(DbEntity source, Collection<DbAttribute> columns) { + + if (columns == null || columns.isEmpty()) { + throw new CayenneRuntimeException("Can't create UNIQUE constraint - no columns specified."); + } + + StringBuilder buf = new StringBuilder(); + + buf.append("ALTER TABLE "); + buf.append(quotingStrategy.quotedFullyQualifiedName(source)); + buf.append(" ADD UNIQUE ("); + + Iterator<DbAttribute> it = columns.iterator(); + DbAttribute first = it.next(); + buf.append(quotingStrategy.quotedName(first)); + + while (it.hasNext()) { + DbAttribute next = it.next(); + buf.append(", "); + buf.append(quotingStrategy.quotedName(next)); + } + + buf.append(")"); + + return buf.toString(); + } + + /** + * Returns a SQL string that can be used to create a foreign key constraint + * for the relationship. + */ + @Override + public String createFkConstraint(DbRelationship rel) { + + DbEntity source = rel.getSourceEntity(); + StringBuilder buf = new StringBuilder(); + StringBuilder refBuf = new StringBuilder(); + + buf.append("ALTER TABLE "); + + buf.append(quotingStrategy.quotedFullyQualifiedName(source)); + buf.append(" ADD FOREIGN KEY ("); + + boolean first = true; + + for (DbJoin join : rel.getJoins()) { + if (first) { + first = false; + } else { + buf.append(", "); + refBuf.append(", "); + } + + buf.append(quotingStrategy.quotedSourceName(join)); + refBuf.append(quotingStrategy.quotedTargetName(join)); + } + + buf.append(") REFERENCES "); + + buf.append(quotingStrategy.quotedFullyQualifiedName(rel.getTargetEntity())); + + buf.append(" (").append(refBuf.toString()).append(')'); + return buf.toString(); + } + + @Override + public String[] externalTypesForJdbcType(int type) { + return typesHandler.externalTypesForJdbcType(type); + } + + @Override + public ExtendedTypeMap getExtendedTypes() { + return extendedTypes; + } + + @Override + public DbAttribute buildAttribute(String name, String typeName, int type, int size, int scale, boolean allowNulls) { + + DbAttribute attr = new DbAttribute(); + attr.setName(name); + attr.setType(type); + attr.setMandatory(!allowNulls); + + if (size >= 0) { + attr.setMaxLength(size); + } + + if (scale >= 0) { + attr.setScale(scale); + } + + return attr; + } + + @Override + public String tableTypeForTable() { + return "TABLE"; + } + + @Override + public String tableTypeForView() { + return "VIEW"; + } + + /** + * Creates and returns a default implementation of a qualifier translator. + */ + @Override + public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) { + QualifierTranslator translator = new QualifierTranslator(queryAssembler); + translator.setCaseInsensitive(caseInsensitiveCollations); + return translator; + } + + /** + * Uses JdbcActionBuilder to create the right action. + * + * @since 1.2 + */ + @Override + public SQLAction getAction(Query query, DataNode node) { + return query.createSQLAction(new JdbcActionBuilder(node)); + } + + @Override + public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) { + return new DefaultSelectTranslator(query, this, entityResolver); + } + + @Override + @SuppressWarnings("unchecked") + public void bindParameter(PreparedStatement statement, ParameterBinding binding) + throws SQLException, Exception { + + if (binding.getValue() == null) { + statement.setNull(binding.getStatementPosition(), binding.getJdbcType()); + } else { + binding.getExtendedType().setJdbcObject(statement, + binding.getValue(), + binding.getStatementPosition(), + binding.getJdbcType(), + binding.getScale()); + } + } + + @Override + public boolean supportsBatchUpdates() { + return this.supportsBatchUpdates; + } + + public void setSupportsBatchUpdates(boolean flag) { + this.supportsBatchUpdates = flag; + } + + /** + * @since 1.2 + */ + @Override + public boolean supportsGeneratedKeys() { + return supportsGeneratedKeys; + } + + /** + * @since 1.2 + */ + public void setSupportsGeneratedKeys(boolean flag) { + this.supportsGeneratedKeys = flag; + } + + /** + * Returns a translator factory for EJBQL to SQL translation. The factory is + * normally initialized in constructor by calling + * {@link #createEJBQLTranslatorFactory()}, and can be changed later by + * calling {@link #setEjbqlTranslatorFactory(EJBQLTranslatorFactory)}. + * + * @since 3.0 + */ + public EJBQLTranslatorFactory getEjbqlTranslatorFactory() { + return ejbqlTranslatorFactory; + } + + /** + * Sets a translator factory for EJBQL to SQL translation. This property is + * normally initialized in constructor by calling + * {@link #createEJBQLTranslatorFactory()}, so users would only override it + * if they need to customize EJBQL translation. + * + * @since 3.0 + */ + public void setEjbqlTranslatorFactory(EJBQLTranslatorFactory ejbqlTranslatorFactory) { + this.ejbqlTranslatorFactory = ejbqlTranslatorFactory; + } + + /** + * @since 4.0 + */ + protected QuotingStrategy createQuotingStrategy() { + return new DefaultQuotingStrategy("\"", "\""); + } + + /** + * @since 4.0 + */ + public QuotingStrategy getQuotingStrategy() { + return quotingStrategy; + } + + /** + * Simply returns this, as JdbcAdapter is not a wrapper. + * + * @since 4.0 + */ + @Override + public DbAdapter unwrap() { + return this; + } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/929b6cb4/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2PkGenerator.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2PkGenerator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2PkGenerator.java index 9f5e3da..beed26b 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2PkGenerator.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2PkGenerator.java @@ -27,40 +27,44 @@ import org.apache.cayenne.map.DbEntity; */ public class DB2PkGenerator extends OraclePkGenerator { - DB2PkGenerator(JdbcAdapter adapter) { - super(adapter); - } + public DB2PkGenerator() { + super(); + } - private static final String _SEQUENCE_PREFIX = "S_"; + DB2PkGenerator(JdbcAdapter adapter) { + super(adapter); + } - @Override - protected String sequenceName(DbEntity entity) { - return super.sequenceName(entity).toUpperCase(); - } + private static final String _SEQUENCE_PREFIX = "S_"; - @Override - protected String getSequencePrefix() { - return _SEQUENCE_PREFIX; - } + @Override + protected String sequenceName(DbEntity entity) { + return super.sequenceName(entity).toUpperCase(); + } - @Override - protected String selectNextValQuery(String pkGeneratingSequenceName) { - return "SELECT NEXTVAL FOR " + pkGeneratingSequenceName + " FROM SYSIBM.SYSDUMMY1"; - } + @Override + protected String getSequencePrefix() { + return _SEQUENCE_PREFIX; + } - @Override - protected String selectAllSequencesQuery() { - return "SELECT SEQNAME FROM SYSCAT.SEQUENCES WHERE SEQNAME LIKE '" + _SEQUENCE_PREFIX + "%'"; - } + @Override + protected String selectNextValQuery(String pkGeneratingSequenceName) { + return "SELECT NEXTVAL FOR " + pkGeneratingSequenceName + " FROM SYSIBM.SYSDUMMY1"; + } - @Override - protected String dropSequenceString(DbEntity entity) { - return "DROP SEQUENCE " + sequenceName(entity) + " RESTRICT "; - } + @Override + protected String selectAllSequencesQuery() { + return "SELECT SEQNAME FROM SYSCAT.SEQUENCES WHERE SEQNAME LIKE '" + _SEQUENCE_PREFIX + "%'"; + } - @Override - protected String createSequenceString(DbEntity entity) { - return "CREATE SEQUENCE " + sequenceName(entity) + " AS BIGINT START WITH " + pkStartValue + - " INCREMENT BY " + getPkCacheSize() + " NO MAXVALUE NO CYCLE CACHE " + getPkCacheSize(); - } + @Override + protected String dropSequenceString(DbEntity entity) { + return "DROP SEQUENCE " + sequenceName(entity) + " RESTRICT "; + } + + @Override + protected String createSequenceString(DbEntity entity) { + return "CREATE SEQUENCE " + sequenceName(entity) + " AS BIGINT START WITH " + pkStartValue + + " INCREMENT BY " + getPkCacheSize() + " NO MAXVALUE NO CYCLE CACHE " + getPkCacheSize(); + } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/929b6cb4/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Sniffer.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Sniffer.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Sniffer.java index a4f1c1a..fc946f8 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Sniffer.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Sniffer.java @@ -19,14 +19,18 @@ package org.apache.cayenne.dba.db2; -import java.sql.DatabaseMetaData; -import java.sql.SQLException; - import org.apache.cayenne.configuration.server.DbAdapterDetector; +import org.apache.cayenne.configuration.server.PkGeneratorFactoryProvider; import org.apache.cayenne.dba.DbAdapter; +import org.apache.cayenne.dba.JdbcAdapter; +import org.apache.cayenne.dba.PkGenerator; import org.apache.cayenne.di.AdhocObjectFactory; import org.apache.cayenne.di.Inject; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.Objects; + /** * @since 1.2 */ @@ -34,17 +38,31 @@ public class DB2Sniffer implements DbAdapterDetector { protected AdhocObjectFactory objectFactory; - public DB2Sniffer(@Inject AdhocObjectFactory objectFactory) { + protected PkGeneratorFactoryProvider pkGeneratorProvider; + + public DB2Sniffer(@Inject AdhocObjectFactory objectFactory, + @Inject PkGeneratorFactoryProvider pkGeneratorProvider) { this.objectFactory = objectFactory; + this.pkGeneratorProvider = Objects.requireNonNull(pkGeneratorProvider, "Null pkGeneratorProvider"); } @Override public DbAdapter createAdapter(DatabaseMetaData md) throws SQLException { String dbName = md.getDatabaseProductName(); - return dbName != null && dbName.toUpperCase().contains("DB2") - ? (DbAdapter) objectFactory.newInstance( - DbAdapter.class, - DB2Adapter.class.getName()) - : null; + if (dbName == null || !dbName.toUpperCase().contains("DB2")) { + return null; + } + + JdbcAdapter adapter = objectFactory.newInstance( + DbAdapter.class, + DB2Adapter.class.getName()); + + PkGenerator pkGenerator = pkGeneratorProvider.get(adapter); + + if (pkGenerator != null) { + adapter.setPkGenerator(pkGenerator); + } + + return adapter; } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/929b6cb4/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyPkGenerator.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyPkGenerator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyPkGenerator.java index 00a7fbf..6b9a7c4 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyPkGenerator.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyPkGenerator.java @@ -25,38 +25,42 @@ import org.apache.cayenne.map.DbEntity; /** * PK generator for Derby that uses sequences. - * + * * @since 4.0 (old one used AUTO_PK_SUPPORT table) */ public class DerbyPkGenerator extends OraclePkGenerator { - DerbyPkGenerator(JdbcAdapter adapter) { - super(adapter); - } - - @Override - protected String sequenceName(DbEntity entity) { - return super.sequenceName(entity).toUpperCase(); - } - - @Override - protected String selectNextValQuery(String pkGeneratingSequenceName) { - return "VALUES (NEXT VALUE FOR " + pkGeneratingSequenceName + ")"; - } - - @Override - protected String selectAllSequencesQuery() { - return "SELECT SEQUENCENAME FROM SYS.SYSSEQUENCES"; - } - - @Override - protected String dropSequenceString(DbEntity entity) { - return "DROP SEQUENCE " + sequenceName(entity) + " RESTRICT"; - } - - @Override - protected String createSequenceString(DbEntity entity) { - return "CREATE SEQUENCE " + sequenceName(entity) + " AS BIGINT START WITH " + pkStartValue + - " INCREMENT BY " + getPkCacheSize() + " NO MAXVALUE NO CYCLE"; - } + public DerbyPkGenerator() { + super(); + } + + DerbyPkGenerator(JdbcAdapter adapter) { + super(adapter); + } + + @Override + protected String sequenceName(DbEntity entity) { + return super.sequenceName(entity).toUpperCase(); + } + + @Override + protected String selectNextValQuery(String pkGeneratingSequenceName) { + return "VALUES (NEXT VALUE FOR " + pkGeneratingSequenceName + ")"; + } + + @Override + protected String selectAllSequencesQuery() { + return "SELECT SEQUENCENAME FROM SYS.SYSSEQUENCES"; + } + + @Override + protected String dropSequenceString(DbEntity entity) { + return "DROP SEQUENCE " + sequenceName(entity) + " RESTRICT"; + } + + @Override + protected String createSequenceString(DbEntity entity) { + return "CREATE SEQUENCE " + sequenceName(entity) + " AS BIGINT START WITH " + pkStartValue + + " INCREMENT BY " + getPkCacheSize() + " NO MAXVALUE NO CYCLE"; + } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/929b6cb4/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbySniffer.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbySniffer.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbySniffer.java index 1933fda..7370714 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbySniffer.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbySniffer.java @@ -19,31 +19,52 @@ package org.apache.cayenne.dba.derby; -import java.sql.DatabaseMetaData; -import java.sql.SQLException; - import org.apache.cayenne.configuration.server.DbAdapterDetector; +import org.apache.cayenne.configuration.server.PkGeneratorFactoryProvider; import org.apache.cayenne.dba.DbAdapter; +import org.apache.cayenne.dba.JdbcAdapter; +import org.apache.cayenne.dba.PkGenerator; import org.apache.cayenne.di.AdhocObjectFactory; import org.apache.cayenne.di.Inject; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.Objects; + /** * Creates a DerbyAdapter if Apache Derby database is detected. - * + * * @since 1.2 */ public class DerbySniffer implements DbAdapterDetector { protected AdhocObjectFactory objectFactory; - public DerbySniffer(@Inject AdhocObjectFactory objectFactory) { + protected PkGeneratorFactoryProvider pkGeneratorProvider; + + public DerbySniffer(@Inject AdhocObjectFactory objectFactory, + @Inject PkGeneratorFactoryProvider pkGeneratorProvider) { this.objectFactory = objectFactory; + this.pkGeneratorProvider = Objects.requireNonNull(pkGeneratorProvider, "Null pkGeneratorProvider"); } @Override public DbAdapter createAdapter(DatabaseMetaData md) throws SQLException { String dbName = md.getDatabaseProductName(); - return dbName != null && dbName.toUpperCase().contains("APACHE DERBY") - ? (DbAdapter) objectFactory.newInstance(DbAdapter.class, DerbyAdapter.class.getName()) : null; + if (dbName == null || !dbName.toUpperCase().contains("APACHE DERBY")) { + return null; + } + + JdbcAdapter adapter = objectFactory.newInstance( + DbAdapter.class, + DerbyAdapter.class.getName()); + + PkGenerator pkGenerator = pkGeneratorProvider.get(adapter); + + if (pkGenerator != null) { + adapter.setPkGenerator(pkGenerator); + } + + return adapter; } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/929b6cb4/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBasePkGenerator.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBasePkGenerator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBasePkGenerator.java index 3b8ac43..ecedfdc 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBasePkGenerator.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBasePkGenerator.java @@ -39,100 +39,104 @@ import java.util.List; */ public class FrontBasePkGenerator extends JdbcPkGenerator { - public FrontBasePkGenerator(JdbcAdapter adapter) { - super(adapter); - pkStartValue = 1000000; - } - - /** - * Returns zero as PK caching is not supported by FrontBaseAdapter. - */ - @Override - public int getPkCacheSize() { - return 0; - } - - @Override - public void createAutoPk(DataNode node, List<DbEntity> dbEntities) throws Exception { - // For each entity (re)set the unique counter - for (DbEntity entity : dbEntities) { - runUpdate(node, pkCreateString(entity.getName())); - } - } - - @Override - public List<String> createAutoPkStatements(List<DbEntity> dbEntities) { - List<String> list = new ArrayList<>(); - for (DbEntity entity : dbEntities) { - list.add(pkCreateString(entity.getName())); - } - return list; - } - - @Override - public void dropAutoPk(DataNode node, List<DbEntity> dbEntities) throws Exception { - } - - @Override - protected String pkTableCreateString() { - return ""; - } - - @Override - protected String pkDeleteString(List<DbEntity> dbEntities) { - return "-- The 'Drop Primary Key Support' option is unavailable"; - } - - @Override - protected String pkCreateString(String entName) { - StringBuilder buf = new StringBuilder(); - buf.append("SET UNIQUE = ").append(pkStartValue).append(" FOR \"").append(entName).append("\""); - return buf.toString(); - } - - @Override - protected String pkSelectString(String entName) { - StringBuilder buf = new StringBuilder(); - buf.append("SELECT UNIQUE FROM \"").append(entName).append("\""); - return buf.toString(); - } - - @Override - protected String pkUpdateString(String entName) { - return ""; - } - - @Override - protected String dropAutoPkString() { - return ""; - } - - /** - * @since 3.0 - */ - @Override - protected long longPkFromDatabase(DataNode node, DbEntity entity) throws Exception { - - String template = "SELECT #result('UNIQUE' 'long') FROM " + entity.getName(); - - final long[] pkHolder = new long[1]; - - SQLTemplate query = new SQLTemplate(entity, template); - OperationObserver observer = new DoNothingOperationObserver() { - - @Override - public void nextRows(Query query, List<?> dataRows) { - if (dataRows.size() != 1) { - throw new CayenneRuntimeException("Error fetching PK. Expected one row, got %d", dataRows.size()); - } - - DataRow row = (DataRow) dataRows.get(0); - Number pk = (Number) row.get("UNIQUE"); - pkHolder[0] = pk.longValue(); - } - }; - - node.performQueries(Collections.singleton((Query) query), observer); - return pkHolder[0]; - } + public FrontBasePkGenerator() { + super(); + } + + public FrontBasePkGenerator(JdbcAdapter adapter) { + super(adapter); + pkStartValue = 1000000; + } + + /** + * Returns zero as PK caching is not supported by FrontBaseAdapter. + */ + @Override + public int getPkCacheSize() { + return 0; + } + + @Override + public void createAutoPk(DataNode node, List<DbEntity> dbEntities) throws Exception { + // For each entity (re)set the unique counter + for (DbEntity entity : dbEntities) { + runUpdate(node, pkCreateString(entity.getName())); + } + } + + @Override + public List<String> createAutoPkStatements(List<DbEntity> dbEntities) { + List<String> list = new ArrayList<>(); + for (DbEntity entity : dbEntities) { + list.add(pkCreateString(entity.getName())); + } + return list; + } + + @Override + public void dropAutoPk(DataNode node, List<DbEntity> dbEntities) throws Exception { + } + + @Override + protected String pkTableCreateString() { + return ""; + } + + @Override + protected String pkDeleteString(List<DbEntity> dbEntities) { + return "-- The 'Drop Primary Key Support' option is unavailable"; + } + + @Override + protected String pkCreateString(String entName) { + StringBuilder buf = new StringBuilder(); + buf.append("SET UNIQUE = ").append(pkStartValue).append(" FOR \"").append(entName).append("\""); + return buf.toString(); + } + + @Override + protected String pkSelectString(String entName) { + StringBuilder buf = new StringBuilder(); + buf.append("SELECT UNIQUE FROM \"").append(entName).append("\""); + return buf.toString(); + } + + @Override + protected String pkUpdateString(String entName) { + return ""; + } + + @Override + protected String dropAutoPkString() { + return ""; + } + + /** + * @since 3.0 + */ + @Override + protected long longPkFromDatabase(DataNode node, DbEntity entity) throws Exception { + + String template = "SELECT #result('UNIQUE' 'long') FROM " + entity.getName(); + + final long[] pkHolder = new long[1]; + + SQLTemplate query = new SQLTemplate(entity, template); + OperationObserver observer = new DoNothingOperationObserver() { + + @Override + public void nextRows(Query query, List<?> dataRows) { + if (dataRows.size() != 1) { + throw new CayenneRuntimeException("Error fetching PK. Expected one row, got %d", dataRows.size()); + } + + DataRow row = (DataRow) dataRows.get(0); + Number pk = (Number) row.get("UNIQUE"); + pkHolder[0] = pk.longValue(); + } + }; + + node.performQueries(Collections.singleton((Query) query), observer); + return pkHolder[0]; + } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/929b6cb4/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSniffer.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSniffer.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSniffer.java index cfd5217..57a10a6 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSniffer.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSniffer.java @@ -19,14 +19,18 @@ package org.apache.cayenne.dba.frontbase; -import java.sql.DatabaseMetaData; -import java.sql.SQLException; - import org.apache.cayenne.configuration.server.DbAdapterDetector; +import org.apache.cayenne.configuration.server.PkGeneratorFactoryProvider; import org.apache.cayenne.dba.DbAdapter; +import org.apache.cayenne.dba.JdbcAdapter; +import org.apache.cayenne.dba.PkGenerator; import org.apache.cayenne.di.AdhocObjectFactory; import org.apache.cayenne.di.Inject; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.Objects; + /** * @since 1.2 */ @@ -34,14 +38,31 @@ public class FrontBaseSniffer implements DbAdapterDetector { protected AdhocObjectFactory objectFactory; - public FrontBaseSniffer(@Inject AdhocObjectFactory objectFactory) { + protected PkGeneratorFactoryProvider pkGeneratorProvider; + + public FrontBaseSniffer(@Inject AdhocObjectFactory objectFactory, + @Inject PkGeneratorFactoryProvider pkGeneratorProvider) { this.objectFactory = objectFactory; + this.pkGeneratorProvider = Objects.requireNonNull(pkGeneratorProvider, "Null pkGeneratorProvider"); } @Override public DbAdapter createAdapter(DatabaseMetaData md) throws SQLException { String dbName = md.getDatabaseProductName(); - return dbName != null && dbName.toUpperCase().contains("FRONTBASE") - ? (DbAdapter) objectFactory.newInstance(DbAdapter.class, FrontBaseAdapter.class.getName()) : null; + if (dbName == null || !dbName.toUpperCase().contains("FRONTBASE")) { + return null; + } + + JdbcAdapter adapter = objectFactory.newInstance( + DbAdapter.class, + FrontBaseAdapter.class.getName()); + + PkGenerator pkGenerator = pkGeneratorProvider.get(adapter); + + if (pkGenerator != null) { + adapter.setPkGenerator(pkGenerator); + } + + return adapter; } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/929b6cb4/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2PkGenerator.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2PkGenerator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2PkGenerator.java index 9e2e4d3..3c49d33 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2PkGenerator.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2PkGenerator.java @@ -25,28 +25,32 @@ import org.apache.cayenne.map.DbEntity; /** * Default PK generator for H2 that uses sequences for PK generation. - * + * * @since 4.0 */ public class H2PkGenerator extends OraclePkGenerator { - protected H2PkGenerator(JdbcAdapter adapter) { - super(adapter); - } - - @Override - protected String createSequenceString(DbEntity ent) { - return "CREATE SEQUENCE " + sequenceName(ent) + " START WITH " + pkStartValue + " INCREMENT BY " - + pkCacheSize(ent) + " CACHE 1"; - } - - @Override - protected String selectNextValQuery(String sequenceName) { - return "SELECT NEXT VALUE FOR " + sequenceName; - } - - @Override - protected String selectAllSequencesQuery() { - return "SELECT LOWER(sequence_name) FROM Information_Schema.Sequences"; - } + public H2PkGenerator() { + super(); + } + + protected H2PkGenerator(JdbcAdapter adapter) { + super(adapter); + } + + @Override + protected String createSequenceString(DbEntity ent) { + return "CREATE SEQUENCE " + sequenceName(ent) + " START WITH " + pkStartValue + " INCREMENT BY " + + pkCacheSize(ent) + " CACHE 1"; + } + + @Override + protected String selectNextValQuery(String sequenceName) { + return "SELECT NEXT VALUE FOR " + sequenceName; + } + + @Override + protected String selectAllSequencesQuery() { + return "SELECT LOWER(sequence_name) FROM Information_Schema.Sequences"; + } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/929b6cb4/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Sniffer.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Sniffer.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Sniffer.java index c8d6fee..bb898d3 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Sniffer.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Sniffer.java @@ -19,14 +19,18 @@ package org.apache.cayenne.dba.h2; -import java.sql.DatabaseMetaData; -import java.sql.SQLException; - import org.apache.cayenne.configuration.server.DbAdapterDetector; +import org.apache.cayenne.configuration.server.PkGeneratorFactoryProvider; import org.apache.cayenne.dba.DbAdapter; +import org.apache.cayenne.dba.JdbcAdapter; +import org.apache.cayenne.dba.PkGenerator; import org.apache.cayenne.di.AdhocObjectFactory; import org.apache.cayenne.di.Inject; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.Objects; + /** * @since 3.0 */ @@ -34,15 +38,31 @@ public class H2Sniffer implements DbAdapterDetector { protected AdhocObjectFactory objectFactory; - public H2Sniffer(@Inject AdhocObjectFactory objectFactory) { + protected PkGeneratorFactoryProvider pkGeneratorProvider; + + public H2Sniffer(@Inject AdhocObjectFactory objectFactory, + @Inject PkGeneratorFactoryProvider pkGeneratorProvider) { this.objectFactory = objectFactory; + this.pkGeneratorProvider = Objects.requireNonNull(pkGeneratorProvider, "Null pkGeneratorProvider"); } @Override public DbAdapter createAdapter(DatabaseMetaData md) throws SQLException { String dbName = md.getDatabaseProductName(); - return dbName != null && dbName.toUpperCase().contains("H2") - ? (DbAdapter) objectFactory.newInstance(DbAdapter.class, H2Adapter.class.getName()) : null; + if (dbName == null || !dbName.toUpperCase().contains("H2")) { + return null; + } + + JdbcAdapter adapter = objectFactory.newInstance( + DbAdapter.class, + H2Adapter.class.getName()); + + PkGenerator pkGenerator = pkGeneratorProvider.get(adapter); + + if (pkGenerator != null) { + adapter.setPkGenerator(pkGenerator); + } + return adapter; } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/929b6cb4/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresPkGenerator.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresPkGenerator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresPkGenerator.java index 59a1183..c5f73f5 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresPkGenerator.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresPkGenerator.java @@ -24,22 +24,26 @@ import org.apache.cayenne.dba.oracle.OraclePkGenerator; /** * Ingres-specific sequence based PK generator. - * + * * @since 1.2 */ public class IngresPkGenerator extends OraclePkGenerator { - protected IngresPkGenerator(JdbcAdapter adapter) { - super(adapter); - } + public IngresPkGenerator() { + super(); + } + + protected IngresPkGenerator(JdbcAdapter adapter) { + super(adapter); + } - @Override - protected String selectNextValQuery(String sequenceName) { - return "SELECT " + sequenceName + ".nextval"; - } + @Override + protected String selectNextValQuery(String sequenceName) { + return "SELECT " + sequenceName + ".nextval"; + } - @Override - protected String selectAllSequencesQuery() { - return "SELECT seq_name FROM iisequences WHERE seq_owner != 'DBA'"; - } + @Override + protected String selectAllSequencesQuery() { + return "SELECT seq_name FROM iisequences WHERE seq_owner != 'DBA'"; + } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/929b6cb4/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSniffer.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSniffer.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSniffer.java index 471d65c..267791d 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSniffer.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSniffer.java @@ -19,14 +19,18 @@ package org.apache.cayenne.dba.ingres; -import java.sql.DatabaseMetaData; -import java.sql.SQLException; - import org.apache.cayenne.configuration.server.DbAdapterDetector; +import org.apache.cayenne.configuration.server.PkGeneratorFactoryProvider; import org.apache.cayenne.dba.DbAdapter; +import org.apache.cayenne.dba.JdbcAdapter; +import org.apache.cayenne.dba.PkGenerator; import org.apache.cayenne.di.AdhocObjectFactory; import org.apache.cayenne.di.Inject; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.Objects; + /** * Detects Ingres database from JDBC metadata. * @@ -36,14 +40,31 @@ public class IngresSniffer implements DbAdapterDetector { protected AdhocObjectFactory objectFactory; - public IngresSniffer(@Inject AdhocObjectFactory objectFactory) { + protected PkGeneratorFactoryProvider pkGeneratorProvider; + + public IngresSniffer(@Inject AdhocObjectFactory objectFactory, + @Inject PkGeneratorFactoryProvider pkGeneratorProvider) { this.objectFactory = objectFactory; + this.pkGeneratorProvider = Objects.requireNonNull(pkGeneratorProvider, "Null pkGeneratorProvider"); } @Override public DbAdapter createAdapter(DatabaseMetaData md) throws SQLException { String dbName = md.getDatabaseProductName(); - return dbName != null && dbName.toUpperCase().contains("INGRES") - ? (DbAdapter) objectFactory.newInstance(DbAdapter.class, IngresAdapter.class.getName()) : null; + if (dbName == null || !dbName.toUpperCase().contains("INGRES")) { + return null; + } + + JdbcAdapter adapter = objectFactory.newInstance( + DbAdapter.class, + IngresAdapter.class.getName()); + + PkGenerator pkGenerator = pkGeneratorProvider.get(adapter); + + if (pkGenerator != null) { + adapter.setPkGenerator(pkGenerator); + } + + return adapter; } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/929b6cb4/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLPkGenerator.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLPkGenerator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLPkGenerator.java index 83be87c..3c1414d 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLPkGenerator.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLPkGenerator.java @@ -38,140 +38,144 @@ import org.slf4j.LoggerFactory; */ public class MySQLPkGenerator extends JdbcPkGenerator { - private static final Logger logger = LoggerFactory.getLogger(MySQLPkGenerator.class); - - MySQLPkGenerator(JdbcAdapter adapter) { - super(adapter); - } - - /** - * Overrides superclass's implementation to perform locking of the primary - * key lookup table. - * - * @since 3.0 - */ - @Override - protected long longPkFromDatabase(DataNode node, DbEntity entity) throws Exception { - - // must work directly with JDBC connection, since we - // must unlock the AUTO_PK_SUPPORT table in case of - // failures.... ah..JDBC is fun... - - // chained SQL exception - SQLException exception = null; - long pk = -1L; - - // Start new transaction if needed, can any way lead to problems when - // using external transaction manager. We can only warn about it. - // See https://issues.apache.org/jira/browse/CAY-2186 for details. - Transaction transaction = BaseTransaction.getThreadTransaction(); - if(transaction != null && transaction.isExternal()) { - logger.warn("Using MysqlPkGenerator with external transaction manager may lead to inconsistent state."); - } - BaseTransaction.bindThreadTransaction(null); - - try (Connection con = node.getDataSource().getConnection()) { - - if (con.getAutoCommit()) { - con.setAutoCommit(false); - } - - try(Statement st = con.createStatement()) { - try { - pk = getLongPrimaryKey(st, entity.getName()); - con.commit(); - } catch (SQLException pkEx) { - try { - con.rollback(); - } catch (SQLException ignored) { - } - - exception = processSQLException(pkEx, null); - } finally { - // UNLOCK! - // THIS MUST BE EXECUTED NO MATTER WHAT, OR WE WILL LOCK THE PRIMARY KEY TABLE!! - try { - String unlockString = "UNLOCK TABLES"; - adapter.getJdbcEventLogger().log(unlockString); - st.execute(unlockString); - } catch (SQLException unlockEx) { - exception = processSQLException(unlockEx, exception); - } - } - } - } catch (SQLException otherEx) { - exception = processSQLException(otherEx, null); - } finally { - BaseTransaction.bindThreadTransaction(transaction); - } - - // check errors - if (exception != null) { - throw exception; - } - - return pk; - - } - - /** - * Appends a new SQLException to the chain. If parent is null, uses the - * exception as the chain root. - */ - protected SQLException processSQLException(SQLException exception, SQLException parent) { - if (parent == null) { - return exception; - } - - parent.setNextException(exception); - return parent; - } - - @Override - protected String dropAutoPkString() { - return "DROP TABLE IF EXISTS AUTO_PK_SUPPORT"; - } - - @Override - protected String pkTableCreateString() { - return "CREATE TABLE IF NOT EXISTS AUTO_PK_SUPPORT " + - "(TABLE_NAME CHAR(100) NOT NULL, NEXT_ID BIGINT NOT NULL, UNIQUE (TABLE_NAME)) " + - "ENGINE=" + MySQLAdapter.DEFAULT_STORAGE_ENGINE; - } - - /** - * @since 3.0 - */ - protected long getLongPrimaryKey(Statement statement, String entityName) throws SQLException { - // lock - String lockString = "LOCK TABLES AUTO_PK_SUPPORT WRITE"; - adapter.getJdbcEventLogger().log(lockString); - statement.execute(lockString); - - // select - String selectString = super.pkSelectString(entityName); - adapter.getJdbcEventLogger().log(selectString); - long pk; - try(ResultSet rs = statement.executeQuery(selectString)) { - if (!rs.next()) { - throw new SQLException("No rows for '" + entityName + "'"); - } - - pk = rs.getLong(1); - if (rs.next()) { - throw new SQLException("More than one row for '" + entityName + "'"); - } - } - - // update - String updateString = super.pkUpdateString(entityName) + " AND NEXT_ID = " + pk; - adapter.getJdbcEventLogger().log(updateString); - int updated = statement.executeUpdate(updateString); - // optimistic lock failure... - if (updated != 1) { - throw new SQLException("Error updating PK count '" + entityName + "': " + updated); - } - - return pk; - } + private static final Logger logger = LoggerFactory.getLogger(MySQLPkGenerator.class); + + public MySQLPkGenerator() { + super(); + } + + MySQLPkGenerator(JdbcAdapter adapter) { + super(adapter); + } + + /** + * Overrides superclass's implementation to perform locking of the primary + * key lookup table. + * + * @since 3.0 + */ + @Override + protected long longPkFromDatabase(DataNode node, DbEntity entity) throws Exception { + + // must work directly with JDBC connection, since we + // must unlock the AUTO_PK_SUPPORT table in case of + // failures.... ah..JDBC is fun... + + // chained SQL exception + SQLException exception = null; + long pk = -1L; + + // Start new transaction if needed, can any way lead to problems when + // using external transaction manager. We can only warn about it. + // See https://issues.apache.org/jira/browse/CAY-2186 for details. + Transaction transaction = BaseTransaction.getThreadTransaction(); + if (transaction != null && transaction.isExternal()) { + logger.warn("Using MysqlPkGenerator with external transaction manager may lead to inconsistent state."); + } + BaseTransaction.bindThreadTransaction(null); + + try (Connection con = node.getDataSource().getConnection()) { + + if (con.getAutoCommit()) { + con.setAutoCommit(false); + } + + try (Statement st = con.createStatement()) { + try { + pk = getLongPrimaryKey(st, entity.getName()); + con.commit(); + } catch (SQLException pkEx) { + try { + con.rollback(); + } catch (SQLException ignored) { + } + + exception = processSQLException(pkEx, null); + } finally { + // UNLOCK! + // THIS MUST BE EXECUTED NO MATTER WHAT, OR WE WILL LOCK THE PRIMARY KEY TABLE!! + try { + String unlockString = "UNLOCK TABLES"; + adapter.getJdbcEventLogger().log(unlockString); + st.execute(unlockString); + } catch (SQLException unlockEx) { + exception = processSQLException(unlockEx, exception); + } + } + } + } catch (SQLException otherEx) { + exception = processSQLException(otherEx, null); + } finally { + BaseTransaction.bindThreadTransaction(transaction); + } + + // check errors + if (exception != null) { + throw exception; + } + + return pk; + + } + + /** + * Appends a new SQLException to the chain. If parent is null, uses the + * exception as the chain root. + */ + protected SQLException processSQLException(SQLException exception, SQLException parent) { + if (parent == null) { + return exception; + } + + parent.setNextException(exception); + return parent; + } + + @Override + protected String dropAutoPkString() { + return "DROP TABLE IF EXISTS AUTO_PK_SUPPORT"; + } + + @Override + protected String pkTableCreateString() { + return "CREATE TABLE IF NOT EXISTS AUTO_PK_SUPPORT " + + "(TABLE_NAME CHAR(100) NOT NULL, NEXT_ID BIGINT NOT NULL, UNIQUE (TABLE_NAME)) " + + "ENGINE=" + MySQLAdapter.DEFAULT_STORAGE_ENGINE; + } + + /** + * @since 3.0 + */ + protected long getLongPrimaryKey(Statement statement, String entityName) throws SQLException { + // lock + String lockString = "LOCK TABLES AUTO_PK_SUPPORT WRITE"; + adapter.getJdbcEventLogger().log(lockString); + statement.execute(lockString); + + // select + String selectString = super.pkSelectString(entityName); + adapter.getJdbcEventLogger().log(selectString); + long pk; + try (ResultSet rs = statement.executeQuery(selectString)) { + if (!rs.next()) { + throw new SQLException("No rows for '" + entityName + "'"); + } + + pk = rs.getLong(1); + if (rs.next()) { + throw new SQLException("More than one row for '" + entityName + "'"); + } + } + + // update + String updateString = super.pkUpdateString(entityName) + " AND NEXT_ID = " + pk; + adapter.getJdbcEventLogger().log(updateString); + int updated = statement.executeUpdate(updateString); + // optimistic lock failure... + if (updated != 1) { + throw new SQLException("Error updating PK count '" + entityName + "': " + updated); + } + + return pk; + } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/929b6cb4/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSniffer.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSniffer.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSniffer.java index be5334d..72e84b8 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSniffer.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSniffer.java @@ -20,7 +20,9 @@ package org.apache.cayenne.dba.mysql; import org.apache.cayenne.configuration.server.DbAdapterDetector; +import org.apache.cayenne.configuration.server.PkGeneratorFactoryProvider; import org.apache.cayenne.dba.DbAdapter; +import org.apache.cayenne.dba.PkGenerator; import org.apache.cayenne.di.AdhocObjectFactory; import org.apache.cayenne.di.Inject; @@ -28,6 +30,7 @@ import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.Objects; /** * Detects MySQL database from JDBC metadata. @@ -38,8 +41,12 @@ public class MySQLSniffer implements DbAdapterDetector { protected AdhocObjectFactory objectFactory; - public MySQLSniffer(@Inject AdhocObjectFactory objectFactory) { + protected PkGeneratorFactoryProvider pkGeneratorProvider; + + public MySQLSniffer(@Inject AdhocObjectFactory objectFactory, + @Inject PkGeneratorFactoryProvider pkGeneratorProvider) { this.objectFactory = objectFactory; + this.pkGeneratorProvider = Objects.requireNonNull(pkGeneratorProvider, "Null pkGeneratorProvider"); } @Override @@ -53,7 +60,7 @@ public class MySQLSniffer implements DbAdapterDetector { String adapterStorageEngine = MySQLAdapter.DEFAULT_STORAGE_ENGINE; - try (Statement statement = md.getConnection().createStatement();) { + try (Statement statement = md.getConnection().createStatement()) { // http://dev.mysql.com/doc/refman/5.0/en/storage-engines.html // per link above "table type" concept is deprecated in favor of // "storage @@ -61,7 +68,7 @@ public class MySQLSniffer implements DbAdapterDetector { // and in what // version of MySQL it got introduced... - try (ResultSet rs = statement.executeQuery("SHOW VARIABLES LIKE 'table_type'");) { + try (ResultSet rs = statement.executeQuery("SHOW VARIABLES LIKE 'table_type'")) { if (rs.next()) { String storageEngine = rs.getString(2); if (storageEngine != null) { @@ -73,6 +80,12 @@ public class MySQLSniffer implements DbAdapterDetector { MySQLAdapter adapter = objectFactory.newInstance(MySQLAdapter.class, MySQLAdapter.class.getName()); adapter.setStorageEngine(adapterStorageEngine); + + PkGenerator pkGenerator = pkGeneratorProvider.get(adapter); + + if (pkGenerator != null) { + adapter.setPkGenerator(pkGenerator); + } return adapter; } }