http://git-wip-us.apache.org/repos/asf/cayenne/blob/26d8434d/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcPkGenerator.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcPkGenerator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcPkGenerator.java index 1866944..6474d2d 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcPkGenerator.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcPkGenerator.java @@ -19,19 +19,6 @@ package org.apache.cayenne.dba; -import org.apache.cayenne.CayenneRuntimeException; -import org.apache.cayenne.DataRow; -import org.apache.cayenne.ObjectId; -import org.apache.cayenne.ResultIterator; -import org.apache.cayenne.access.DataNode; -import org.apache.cayenne.access.OperationObserver; -import org.apache.cayenne.map.DbAttribute; -import org.apache.cayenne.map.DbEntity; -import org.apache.cayenne.map.DbKeyGenerator; -import org.apache.cayenne.query.Query; -import org.apache.cayenne.query.SQLTemplate; -import org.apache.cayenne.util.IDUtil; - import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; @@ -45,385 +32,355 @@ import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import org.apache.cayenne.CayenneRuntimeException; +import org.apache.cayenne.DataRow; +import org.apache.cayenne.ObjectId; +import org.apache.cayenne.ResultIterator; +import org.apache.cayenne.access.DataNode; +import org.apache.cayenne.access.OperationObserver; +import org.apache.cayenne.map.DbAttribute; +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.map.DbKeyGenerator; +import org.apache.cayenne.query.Query; +import org.apache.cayenne.query.SQLTemplate; +import org.apache.cayenne.util.IDUtil; + /** * Default primary key generator implementation. Uses a lookup table named * "AUTO_PK_SUPPORT" to search and increment primary keys for tables. */ public class JdbcPkGenerator implements PkGenerator { - public static final int DEFAULT_PK_CACHE_SIZE = 20; - static final long DEFAULT_PK_START_VALUE = 200; - - protected JdbcAdapter adapter; - protected ConcurrentHashMap<String, Queue<Long>> pkCache = new ConcurrentHashMap<String, Queue<Long>>(); - protected int pkCacheSize = DEFAULT_PK_CACHE_SIZE; - protected long pkStartValue = DEFAULT_PK_START_VALUE; - - public JdbcPkGenerator(JdbcAdapter adapter) { - this.adapter = adapter; - } - - public JdbcAdapter getAdapter() { - return adapter; - } - - public void createAutoPk(DataNode node, List<DbEntity> dbEntities) throws Exception { - // check if a table exists - - // create AUTO_PK_SUPPORT table - if (!autoPkTableExists(node)) { - runUpdate(node, pkTableCreateString()); - } - - // delete any existing pk entries - if (!dbEntities.isEmpty()) { - runUpdate(node, pkDeleteString(dbEntities)); - } - - // insert all needed entries - for (DbEntity ent : dbEntities) { - runUpdate(node, pkCreateString(ent.getName())); - } - } - - public List<String> createAutoPkStatements(List<DbEntity> dbEntities) { - List<String> list = new ArrayList<String>(dbEntities.size() + 2); - - list.add(pkTableCreateString()); - list.add(pkDeleteString(dbEntities)); - - for (DbEntity ent : dbEntities) { - list.add(pkCreateString(ent.getName())); - } - - return list; - } - - /** - * Drops table named "AUTO_PK_SUPPORT" if it exists in the database. - */ - public void dropAutoPk(DataNode node, List<DbEntity> dbEntities) throws Exception { - if (autoPkTableExists(node)) { - runUpdate(node, dropAutoPkString()); - } - } - - public List<String> dropAutoPkStatements(List<DbEntity> dbEntities) { - List<String> list = new ArrayList<String>(1); - list.add(dropAutoPkString()); - return list; - } - - protected String pkTableCreateString() { - StringBuilder buf = new StringBuilder(); - buf - .append("CREATE TABLE AUTO_PK_SUPPORT (") - .append(" TABLE_NAME CHAR(100) NOT NULL,") - .append(" NEXT_ID BIGINT NOT NULL,") - .append(" PRIMARY KEY(TABLE_NAME)") - .append(")"); - - return buf.toString(); - } - - protected String pkDeleteString(List<DbEntity> dbEntities) { - StringBuilder buf = new StringBuilder(); - buf.append("DELETE FROM AUTO_PK_SUPPORT WHERE TABLE_NAME IN ("); - int len = dbEntities.size(); - for (int i = 0; i < len; i++) { - if (i > 0) { - buf.append(", "); - } - DbEntity ent = dbEntities.get(i); - buf.append('\'').append(ent.getName()).append('\''); - } - buf.append(')'); - return buf.toString(); - } - - protected String pkCreateString(String entName) { - StringBuilder buf = new StringBuilder(); - buf - .append("INSERT INTO AUTO_PK_SUPPORT") - .append(" (TABLE_NAME, NEXT_ID)") - .append(" VALUES ('") - .append(entName) - .append("', ").append(pkStartValue).append(")"); - return buf.toString(); - } - - protected String pkSelectString(String entName) { - StringBuilder buf = new StringBuilder(); - buf - .append("SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = '") - .append(entName) - .append('\''); - return buf.toString(); - } - - protected String pkUpdateString(String entName) { - StringBuilder buf = new StringBuilder(); - buf - .append("UPDATE AUTO_PK_SUPPORT") - .append(" SET NEXT_ID = NEXT_ID + ") - .append(pkCacheSize) - .append(" WHERE TABLE_NAME = '") - .append(entName) - .append('\''); - return buf.toString(); - } - - protected String dropAutoPkString() { - return "DROP TABLE AUTO_PK_SUPPORT"; - } - - /** - * Checks if AUTO_PK_TABLE already exists in the database. - */ - protected boolean autoPkTableExists(DataNode node) throws SQLException { - Connection con = node.getDataSource().getConnection(); - boolean exists = false; - try { - DatabaseMetaData md = con.getMetaData(); - ResultSet tables = md.getTables(null, null, "AUTO_PK_SUPPORT", null); - try { - exists = tables.next(); - } - finally { - tables.close(); - } - } - finally { - // return connection to the pool - con.close(); - } - - return exists; - } - - /** - * Runs JDBC update over a Connection obtained from DataNode. Returns a number of - * objects returned from update. - * - * @throws SQLException in case of query failure. - */ - public int runUpdate(DataNode node, String sql) throws SQLException { - adapter.getJdbcEventLogger().logQuery(sql, Collections.EMPTY_LIST); - - Connection con = node.getDataSource().getConnection(); - try { - Statement upd = con.createStatement(); - try { - return upd.executeUpdate(sql); - } - finally { - upd.close(); - } - } - finally { - con.close(); - } - } - - /** - * Generates a unique and non-repeating primary key for specified dbEntity. - * <p> - * This implementation is naive since it does not lock the database rows when - * executing select and subsequent update. Adapter-specific implementations are more - * robust. - * </p> - * - * @since 3.0 - */ - public Object generatePk(DataNode node, DbAttribute pk) throws Exception { - - DbEntity entity = (DbEntity) pk.getEntity(); - - switch (pk.getType()) { - case Types.BINARY: - case Types.VARBINARY: - return IDUtil.pseudoUniqueSecureByteSequence(pk.getMaxLength()); - } - - DbKeyGenerator pkGenerator = entity.getPrimaryKeyGenerator(); - long cacheSize; - if (pkGenerator != null && pkGenerator.getKeyCacheSize() != null) - cacheSize = pkGenerator.getKeyCacheSize().intValue(); - else - cacheSize = pkCacheSize; - - Long value; - - // if no caching, always generate fresh - if (cacheSize <= 1) { - value = longPkFromDatabase(node, entity); - } - else { - Queue<Long> pks = pkCache.get(entity.getName()); - - if (pks == null) { - // created exhausted LongPkRange - pks = new ConcurrentLinkedQueue<Long>(); - Queue<Long> previousPks = pkCache.putIfAbsent(entity.getName(), pks); - if (previousPks != null) { - pks = previousPks; - } - } - - value = pks.poll(); - if (value == null) { - value = longPkFromDatabase(node, entity); - for (long i = value+1; i < value + cacheSize; i++) { - pks.add(i); - } - } - } - - if (pk.getType() == Types.BIGINT) { - return value; - } - else { - // leaving it up to the user to ensure that PK does not exceed max int... - return value.intValue(); - } - } + public static final int DEFAULT_PK_CACHE_SIZE = 20; + static final long DEFAULT_PK_START_VALUE = 200; + + protected JdbcAdapter adapter; + protected ConcurrentHashMap<String, Queue<Long>> pkCache = new ConcurrentHashMap<String, Queue<Long>>(); + protected int pkCacheSize = DEFAULT_PK_CACHE_SIZE; + protected long pkStartValue = DEFAULT_PK_START_VALUE; + + public JdbcPkGenerator(JdbcAdapter adapter) { + this.adapter = adapter; + } + + public JdbcAdapter getAdapter() { + return adapter; + } + + public void createAutoPk(DataNode node, List<DbEntity> dbEntities) throws Exception { + // check if a table exists + + // create AUTO_PK_SUPPORT table + if (!autoPkTableExists(node)) { + runUpdate(node, pkTableCreateString()); + } + + // delete any existing pk entries + if (!dbEntities.isEmpty()) { + runUpdate(node, pkDeleteString(dbEntities)); + } + + // insert all needed entries + for (DbEntity ent : dbEntities) { + runUpdate(node, pkCreateString(ent.getName())); + } + } + + public List<String> createAutoPkStatements(List<DbEntity> dbEntities) { + List<String> list = new ArrayList<String>(dbEntities.size() + 2); + + list.add(pkTableCreateString()); + list.add(pkDeleteString(dbEntities)); + + for (DbEntity ent : dbEntities) { + list.add(pkCreateString(ent.getName())); + } + + return list; + } + + /** + * Drops table named "AUTO_PK_SUPPORT" if it exists in the database. + */ + public void dropAutoPk(DataNode node, List<DbEntity> dbEntities) throws Exception { + if (autoPkTableExists(node)) { + runUpdate(node, dropAutoPkString()); + } + } + + public List<String> dropAutoPkStatements(List<DbEntity> dbEntities) { + List<String> list = new ArrayList<String>(1); + list.add(dropAutoPkString()); + return list; + } + + protected String pkTableCreateString() { + StringBuilder buf = new StringBuilder(); + buf.append("CREATE TABLE AUTO_PK_SUPPORT (").append(" TABLE_NAME CHAR(100) NOT NULL,") + .append(" NEXT_ID BIGINT NOT NULL,").append(" PRIMARY KEY(TABLE_NAME)").append(")"); + + return buf.toString(); + } + + protected String pkDeleteString(List<DbEntity> dbEntities) { + StringBuilder buf = new StringBuilder(); + buf.append("DELETE FROM AUTO_PK_SUPPORT WHERE TABLE_NAME IN ("); + int len = dbEntities.size(); + for (int i = 0; i < len; i++) { + if (i > 0) { + buf.append(", "); + } + DbEntity ent = dbEntities.get(i); + buf.append('\'').append(ent.getName()).append('\''); + } + buf.append(')'); + return buf.toString(); + } + + protected String pkCreateString(String entName) { + StringBuilder buf = new StringBuilder(); + buf.append("INSERT INTO AUTO_PK_SUPPORT").append(" (TABLE_NAME, NEXT_ID)").append(" VALUES ('").append(entName) + .append("', ").append(pkStartValue).append(")"); + return buf.toString(); + } + + protected String pkSelectString(String entName) { + StringBuilder buf = new StringBuilder(); + buf.append("SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = '").append(entName).append('\''); + return buf.toString(); + } + + protected String pkUpdateString(String entName) { + StringBuilder buf = new StringBuilder(); + buf.append("UPDATE AUTO_PK_SUPPORT").append(" SET NEXT_ID = NEXT_ID + ").append(pkCacheSize) + .append(" WHERE TABLE_NAME = '").append(entName).append('\''); + return buf.toString(); + } + + protected String dropAutoPkString() { + return "DROP TABLE AUTO_PK_SUPPORT"; + } + + /** + * Checks if AUTO_PK_TABLE already exists in the database. + */ + protected boolean autoPkTableExists(DataNode node) throws SQLException { + + try (Connection con = node.getDataSource().getConnection();) { + DatabaseMetaData md = con.getMetaData(); + + try (ResultSet tables = md.getTables(null, null, "AUTO_PK_SUPPORT", null);) { + return tables.next(); + } + } + } + + /** + * Runs JDBC update over a Connection obtained from DataNode. Returns a + * number of objects returned from update. + * + * @throws SQLException + * in case of query failure. + */ + public int runUpdate(DataNode node, String sql) throws SQLException { + adapter.getJdbcEventLogger().logQuery(sql, Collections.EMPTY_LIST); + + try (Connection con = node.getDataSource().getConnection();) { + try (Statement upd = con.createStatement();) { + return upd.executeUpdate(sql); + } + } + } + + /** + * Generates a unique and non-repeating primary key for specified dbEntity. + * <p> + * This implementation is naive since it does not lock the database rows + * when executing select and subsequent update. Adapter-specific + * implementations are more robust. + * </p> + * + * @since 3.0 + */ + public Object generatePk(DataNode node, DbAttribute pk) throws Exception { + + DbEntity entity = (DbEntity) pk.getEntity(); + + switch (pk.getType()) { + case Types.BINARY: + case Types.VARBINARY: + return IDUtil.pseudoUniqueSecureByteSequence(pk.getMaxLength()); + } + + DbKeyGenerator pkGenerator = entity.getPrimaryKeyGenerator(); + long cacheSize; + if (pkGenerator != null && pkGenerator.getKeyCacheSize() != null) + cacheSize = pkGenerator.getKeyCacheSize().intValue(); + else + cacheSize = pkCacheSize; + + Long value; + + // if no caching, always generate fresh + if (cacheSize <= 1) { + value = longPkFromDatabase(node, entity); + } else { + Queue<Long> pks = pkCache.get(entity.getName()); + + if (pks == null) { + // created exhausted LongPkRange + pks = new ConcurrentLinkedQueue<Long>(); + Queue<Long> previousPks = pkCache.putIfAbsent(entity.getName(), pks); + if (previousPks != null) { + pks = previousPks; + } + } + + value = pks.poll(); + if (value == null) { + value = longPkFromDatabase(node, entity); + for (long i = value + 1; i < value + cacheSize; i++) { + pks.add(i); + } + } + } + + if (pk.getType() == Types.BIGINT) { + return value; + } else { + // leaving it up to the user to ensure that PK does not exceed max + // int... + return value.intValue(); + } + } + + /** + * Performs primary key generation ignoring cache. Generates a range of + * primary keys as specified by "pkCacheSize" bean property. + * <p> + * This method is called internally from "generatePkForDbEntity" and then + * generated range of key values is saved in cache for performance. + * Subclasses that implement different primary key generation solutions + * should override this method, not "generatePkForDbEntity". + * </p> + * + * @since 3.0 + */ + protected long longPkFromDatabase(DataNode node, DbEntity entity) throws Exception { + String select = "SELECT #result('NEXT_ID' 'long' 'NEXT_ID') " + "FROM AUTO_PK_SUPPORT " + + "WHERE TABLE_NAME = '" + entity.getName() + '\''; + + // run queries via DataNode to utilize its transactional behavior + List<Query> queries = new ArrayList<Query>(2); + queries.add(new SQLTemplate(entity, select)); + queries.add(new SQLTemplate(entity, pkUpdateString(entity.getName()))); + + PkRetrieveProcessor observer = new PkRetrieveProcessor(entity.getName()); + node.performQueries(queries, observer); + return observer.getId(); + } + + /** + * Returns a size of the entity primary key cache. Default value is 20. If + * cache size is set to a value less or equals than "one", no primary key + * caching is done. + */ + public int getPkCacheSize() { + return pkCacheSize; + } + + /** + * Sets the size of the entity primary key cache. If + * <code>pkCacheSize</code> parameter is less than 1, cache size is set to + * "one". + * <p> + * <i>Note that our tests show that setting primary key cache value to + * anything much bigger than 20 does not give any significant performance + * increase. Therefore it does not make sense to use bigger values, since + * this may potentially create big gaps in the database primary key + * sequences in cases like application crashes or restarts. </i> + * </p> + */ + public void setPkCacheSize(int pkCacheSize) { + this.pkCacheSize = (pkCacheSize < 1) ? 1 : pkCacheSize; + } + + long getPkStartValue() { + return pkStartValue; + } + + void setPkStartValue(long startValue) { + this.pkStartValue = startValue; + } + + public void reset() { + pkCache.clear(); + } /** - * Performs primary key generation ignoring cache. Generates a range of primary keys - * as specified by "pkCacheSize" bean property. - * <p> - * This method is called internally from "generatePkForDbEntity" and then generated - * range of key values is saved in cache for performance. Subclasses that implement - * different primary key generation solutions should override this method, not - * "generatePkForDbEntity". - * </p> - * - * @since 3.0 - */ - protected long longPkFromDatabase(DataNode node, DbEntity entity) throws Exception { - String select = "SELECT #result('NEXT_ID' 'long' 'NEXT_ID') " - + "FROM AUTO_PK_SUPPORT " - + "WHERE TABLE_NAME = '" - + entity.getName() - + '\''; - - // run queries via DataNode to utilize its transactional behavior - List<Query> queries = new ArrayList<Query>(2); - queries.add(new SQLTemplate(entity, select)); - queries.add(new SQLTemplate(entity, pkUpdateString(entity.getName()))); - - PkRetrieveProcessor observer = new PkRetrieveProcessor(entity.getName()); - node.performQueries(queries, observer); - return observer.getId(); - } - - /** - * Returns a size of the entity primary key cache. Default value is 20. If cache size - * is set to a value less or equals than "one", no primary key caching is done. - */ - public int getPkCacheSize() { - return pkCacheSize; - } - - /** - * Sets the size of the entity primary key cache. If <code>pkCacheSize</code> - * parameter is less than 1, cache size is set to "one". - * <p> - * <i>Note that our tests show that setting primary key cache value to anything much - * bigger than 20 does not give any significant performance increase. Therefore it - * does not make sense to use bigger values, since this may potentially create big - * gaps in the database primary key sequences in cases like application crashes or - * restarts. </i> - * </p> - */ - public void setPkCacheSize(int pkCacheSize) { - this.pkCacheSize = (pkCacheSize < 1) ? 1 : pkCacheSize; - } - - long getPkStartValue() { - return pkStartValue; - } - - void setPkStartValue(long startValue) { - this.pkStartValue = startValue; - } - - public void reset() { - pkCache.clear(); - } - - /** - * OperationObserver for primary key retrieval. - */ - final class PkRetrieveProcessor implements OperationObserver { - - Number id; - String entityName; - - PkRetrieveProcessor(String entityName) { - this.entityName = entityName; - } - - public boolean isIteratedResult() { - return false; - } - - public long getId() { - if (id == null) { - throw new CayenneRuntimeException("No key was retrieved for entity " - + entityName); - } - - return id.longValue(); - } - - public void nextRows(Query query, List<?> dataRows) { - - // process selected object, issue an update query - if (dataRows == null || dataRows.size() == 0) { - throw new CayenneRuntimeException( - "Error generating PK : entity not supported: " + entityName); - } - - if (dataRows.size() > 1) { - throw new CayenneRuntimeException( - "Error generating PK : too many rows for entity: " + entityName); - } - - DataRow lastPk = (DataRow) dataRows.get(0); - id = (Number) lastPk.get("NEXT_ID"); - } - - public void nextCount(Query query, int resultCount) { - if (resultCount != 1) { - throw new CayenneRuntimeException("Error generating PK for entity '" - + entityName - + "': update count is wrong - " - + resultCount); - } - } - - public void nextBatchCount(Query query, int[] resultCount) { - } - - @Override - public void nextGeneratedRows(Query query, ResultIterator keys, ObjectId idToUpdate) { - } - - public void nextRows(Query q, ResultIterator it) { - } - - public void nextQueryException(Query query, Exception ex) { - - throw new CayenneRuntimeException("Error generating PK for entity '" - + entityName - + "'.", ex); - } - - public void nextGlobalException(Exception ex) { - - throw new CayenneRuntimeException("Error generating PK for entity: " - + entityName, ex); - } - } + * OperationObserver for primary key retrieval. + */ + final class PkRetrieveProcessor implements OperationObserver { + + Number id; + String entityName; + + PkRetrieveProcessor(String entityName) { + this.entityName = entityName; + } + + public boolean isIteratedResult() { + return false; + } + + public long getId() { + if (id == null) { + throw new CayenneRuntimeException("No key was retrieved for entity " + entityName); + } + + return id.longValue(); + } + + public void nextRows(Query query, List<?> dataRows) { + + // process selected object, issue an update query + if (dataRows == null || dataRows.size() == 0) { + throw new CayenneRuntimeException("Error generating PK : entity not supported: " + entityName); + } + + if (dataRows.size() > 1) { + throw new CayenneRuntimeException("Error generating PK : too many rows for entity: " + entityName); + } + + DataRow lastPk = (DataRow) dataRows.get(0); + id = (Number) lastPk.get("NEXT_ID"); + } + + public void nextCount(Query query, int resultCount) { + if (resultCount != 1) { + throw new CayenneRuntimeException("Error generating PK for entity '" + entityName + + "': update count is wrong - " + resultCount); + } + } + + public void nextBatchCount(Query query, int[] resultCount) { + } + + @Override + public void nextGeneratedRows(Query query, ResultIterator keys, ObjectId idToUpdate) { + } + + public void nextRows(Query q, ResultIterator it) { + } + + public void nextQueryException(Query query, Exception ex) { + + throw new CayenneRuntimeException("Error generating PK for entity '" + entityName + "'.", ex); + } + + public void nextGlobalException(Exception ex) { + + throw new CayenneRuntimeException("Error generating PK for entity: " + entityName, ex); + } + } }
http://git-wip-us.apache.org/repos/asf/cayenne/blob/26d8434d/cayenne-server/src/main/java/org/apache/cayenne/dba/TypesHandler.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/TypesHandler.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/TypesHandler.java index 1e1f3ea..100ca5c 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/TypesHandler.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/TypesHandler.java @@ -36,124 +36,98 @@ import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; -/** - * TypesHandler provides JDBC-RDBMS types mapping. Loads types info from - * an XML file. +/** + * TypesHandler provides JDBC-RDBMS types mapping. Loads types info from an XML + * file. * */ public class TypesHandler { - private static Map<URL, TypesHandler> handlerMap = new HashMap<URL, TypesHandler>(); - - protected Map<Integer, String[]> typesMap; - - /** - * @since 1.1 - */ - public static TypesHandler getHandler(URL typesConfig) { - synchronized (handlerMap) { - TypesHandler handler = handlerMap.get(typesConfig); - - if (handler == null) { - handler = new TypesHandler(typesConfig); - handlerMap.put(typesConfig, handler); - } - - return handler; - } - } - - /** - * Creates new TypesHandler loading configuration info from the XML - * file specified as <code>typesConfigPath</code> parameter. - * - * @since 1.1 - */ - public TypesHandler(URL typesConfig) { - try { - InputStream in = typesConfig.openStream(); - - try { - XMLReader parser = Util.createXmlReader(); - TypesParseHandler ph = new TypesParseHandler(); - parser.setContentHandler(ph); - parser.setErrorHandler(ph); - parser.parse(new InputSource(in)); - - typesMap = ph.getTypes(); - } - catch (Exception ex) { - throw new CayenneRuntimeException( - "Error creating TypesHandler '" + typesConfig + "'.", - ex); - } - finally { - try { - in.close(); - } - catch (IOException ioex) { - } - } - } - catch (IOException ioex) { - throw new CayenneRuntimeException( - "Error opening config file '" + typesConfig + "'.", - ioex); - } - } - - public String[] externalTypesForJdbcType(int type) { - return typesMap.get(type); - } - - /** - * Helper class to load types data from XML. - */ - final class TypesParseHandler extends DefaultHandler { - private static final String JDBC_TYPE_TAG = "jdbc-type"; - private static final String DB_TYPE_TAG = "db-type"; - private static final String NAME_ATTR = "name"; - - private Map<Integer, String[]> types = new HashMap<Integer, String[]>(); - private List<String> currentTypes = new ArrayList<String>(); - private int currentType = TypesMapping.NOT_DEFINED; - - public Map<Integer, String[]> getTypes() { - return types; - } - - @Override - public void startElement( - String namespaceURI, - String localName, - String qName, - Attributes atts) - throws SAXException { - if (JDBC_TYPE_TAG.equals(localName)) { - currentTypes.clear(); - String strType = atts.getValue("", NAME_ATTR); - - // convert to Types int value - try { - currentType = Types.class.getDeclaredField(strType).getInt(null); - } - catch (Exception ex) { - currentType = TypesMapping.NOT_DEFINED; - } - } - else if (DB_TYPE_TAG.equals(localName)) { - currentTypes.add(atts.getValue("", NAME_ATTR)); - } - } - - @Override - public void endElement(String namespaceURI, String localName, String qName) - throws SAXException { - if (JDBC_TYPE_TAG.equals(localName) - && currentType != TypesMapping.NOT_DEFINED) { - String[] typesAsArray = new String[currentTypes.size()]; - types.put(Integer.valueOf(currentType), currentTypes.toArray(typesAsArray)); - } - } - } + private static Map<URL, TypesHandler> handlerMap = new HashMap<URL, TypesHandler>(); + + protected Map<Integer, String[]> typesMap; + + /** + * @since 1.1 + */ + public static TypesHandler getHandler(URL typesConfig) { + synchronized (handlerMap) { + TypesHandler handler = handlerMap.get(typesConfig); + + if (handler == null) { + handler = new TypesHandler(typesConfig); + handlerMap.put(typesConfig, handler); + } + + return handler; + } + } + + /** + * Creates new TypesHandler loading configuration info from the XML file + * specified as <code>typesConfigPath</code> parameter. + * + * @since 1.1 + */ + public TypesHandler(URL typesConfig) { + + try (InputStream in = typesConfig.openStream();) { + XMLReader parser = Util.createXmlReader(); + TypesParseHandler ph = new TypesParseHandler(); + parser.setContentHandler(ph); + parser.setErrorHandler(ph); + parser.parse(new InputSource(in)); + + typesMap = ph.getTypes(); + } catch (Exception ex) { + throw new CayenneRuntimeException("Error creating TypesHandler '" + typesConfig + "'.", ex); + } + } + + public String[] externalTypesForJdbcType(int type) { + return typesMap.get(type); + } + + /** + * Helper class to load types data from XML. + */ + final class TypesParseHandler extends DefaultHandler { + private static final String JDBC_TYPE_TAG = "jdbc-type"; + private static final String DB_TYPE_TAG = "db-type"; + private static final String NAME_ATTR = "name"; + + private Map<Integer, String[]> types = new HashMap<Integer, String[]>(); + private List<String> currentTypes = new ArrayList<String>(); + private int currentType = TypesMapping.NOT_DEFINED; + + public Map<Integer, String[]> getTypes() { + return types; + } + + @Override + public void startElement(String namespaceURI, String localName, String qName, Attributes atts) + throws SAXException { + if (JDBC_TYPE_TAG.equals(localName)) { + currentTypes.clear(); + String strType = atts.getValue("", NAME_ATTR); + + // convert to Types int value + try { + currentType = Types.class.getDeclaredField(strType).getInt(null); + } catch (Exception ex) { + currentType = TypesMapping.NOT_DEFINED; + } + } else if (DB_TYPE_TAG.equals(localName)) { + currentTypes.add(atts.getValue("", NAME_ATTR)); + } + } + + @Override + public void endElement(String namespaceURI, String localName, String qName) throws SAXException { + if (JDBC_TYPE_TAG.equals(localName) && currentType != TypesMapping.NOT_DEFINED) { + String[] typesAsArray = new String[currentTypes.size()]; + types.put(Integer.valueOf(currentType), currentTypes.toArray(typesAsArray)); + } + } + } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/26d8434d/cayenne-server/src/main/java/org/apache/cayenne/dba/TypesMapping.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/TypesMapping.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/TypesMapping.java index cfc5cda..e0f867e 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/TypesMapping.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/TypesMapping.java @@ -36,505 +36,490 @@ import java.util.Map; import static java.sql.Types.*; /** - * A utility class that handles mappings of JDBC data types to the database types and Java - * types. Also contains methods that provide information about JDBC types. + * A utility class that handles mappings of JDBC data types to the database + * types and Java types. Also contains methods that provide information about + * JDBC types. */ public class TypesMapping { - // Never use "-1" or any other normal integer, since there - // is a big chance it is being reserved in java.sql.Types - public static final int NOT_DEFINED = Integer.MAX_VALUE; - - // char constants for the sql data types - public static final String SQL_ARRAY = "ARRAY"; - public static final String SQL_BIGINT = "BIGINT"; - public static final String SQL_BINARY = "BINARY"; - public static final String SQL_BIT = "BIT"; - public static final String SQL_BLOB = "BLOB"; - - /** - * @since 1.2 - */ - public static final String SQL_BOOLEAN = "BOOLEAN"; - - public static final String SQL_CLOB = "CLOB"; - public static final String SQL_NCLOB = "NCLOB"; - public static final String SQL_CHAR = "CHAR"; - public static final String SQL_NCHAR = "NCHAR"; - public static final String SQL_DATE = "DATE"; - public static final String SQL_DECIMAL = "DECIMAL"; - public static final String SQL_DOUBLE = "DOUBLE"; - public static final String SQL_FLOAT = "FLOAT"; - public static final String SQL_INTEGER = "INTEGER"; - public static final String SQL_LONGVARCHAR = "LONGVARCHAR"; - public static final String SQL_LONGNVARCHAR = "LONGNVARCHAR"; - public static final String SQL_LONGVARBINARY = "LONGVARBINARY"; - public static final String SQL_NUMERIC = "NUMERIC"; - public static final String SQL_REAL = "REAL"; - public static final String SQL_SMALLINT = "SMALLINT"; - public static final String SQL_TINYINT = "TINYINT"; - public static final String SQL_TIME = "TIME"; - public static final String SQL_TIMESTAMP = "TIMESTAMP"; - public static final String SQL_VARBINARY = "VARBINARY"; - public static final String SQL_VARCHAR = "VARCHAR"; - public static final String SQL_NVARCHAR = "NVARCHAR"; - public static final String SQL_SQLXML = "SQLXML"; - public static final String SQL_OTHER = "OTHER"; - public static final String SQL_NULL = "NULL"; - - // char constants for Java data types - public static final String JAVA_LONG = "java.lang.Long"; - public static final String JAVA_BYTES = "byte[]"; - public static final String JAVA_BOOLEAN = "java.lang.Boolean"; - public static final String JAVA_STRING = "java.lang.String"; - public static final String JAVA_SQLDATE = "java.sql.Date"; - public static final String JAVA_UTILDATE = "java.util.Date"; - public static final String JAVA_BIGDECIMAL = "java.math.BigDecimal"; - public static final String JAVA_DOUBLE = "java.lang.Double"; - public static final String JAVA_FLOAT = "java.lang.Float"; - public static final String JAVA_INTEGER = "java.lang.Integer"; - public static final String JAVA_SHORT = "java.lang.Short"; - public static final String JAVA_BYTE = "java.lang.Byte"; - public static final String JAVA_TIME = "java.sql.Time"; - public static final String JAVA_TIMESTAMP = "java.sql.Timestamp"; - public static final String JAVA_BLOB = "java.sql.Blob"; - - /** - * Keys: SQL string type names, Values: SQL int type definitions from java.sql.Types - */ - private static final Map<String, Integer> SQL_STRING_TYPE = new HashMap<String, Integer>(); - - /** - * Keys: SQL int type definitions from java.sql.Types, Values: SQL string type names - */ - private static final Map<Integer, String> SQL_ENUM_TYPE = new HashMap<Integer, String>(); - - /** - * Keys: SQL int type definitions from java.sql.Types, Values: java class names - */ - private static final Map<Integer, String> SQL_ENUM_JAVA = new HashMap<Integer, String>(); - - /** - * Keys: java class names, Values: SQL int type definitions from java.sql.Types - */ - private static final Map<String, Integer> JAVA_SQL_ENUM = new HashMap<String, Integer>(); - - static { -// SQL_STRING_TYPE.put(SQL_ARRAY, ARRAY); - SQL_STRING_TYPE.put(SQL_BIGINT, BIGINT); - SQL_STRING_TYPE.put(SQL_BINARY, BINARY); - SQL_STRING_TYPE.put(SQL_BIT, BIT); - SQL_STRING_TYPE.put(SQL_BLOB, BLOB); - SQL_STRING_TYPE.put(SQL_BOOLEAN, BOOLEAN); - SQL_STRING_TYPE.put(SQL_CLOB, CLOB); - SQL_STRING_TYPE.put(SQL_NCLOB, NCLOB); - SQL_STRING_TYPE.put(SQL_CHAR, CHAR); - SQL_STRING_TYPE.put(SQL_NCHAR, NCHAR); - SQL_STRING_TYPE.put(SQL_DATE, DATE); - SQL_STRING_TYPE.put(SQL_DECIMAL, DECIMAL); - SQL_STRING_TYPE.put(SQL_DOUBLE, DOUBLE); - SQL_STRING_TYPE.put(SQL_FLOAT, FLOAT); - SQL_STRING_TYPE.put(SQL_INTEGER, INTEGER); - SQL_STRING_TYPE.put(SQL_LONGVARCHAR, LONGVARCHAR); - SQL_STRING_TYPE.put(SQL_LONGNVARCHAR, LONGNVARCHAR); - SQL_STRING_TYPE.put(SQL_LONGVARBINARY, LONGVARBINARY); - SQL_STRING_TYPE.put(SQL_NUMERIC, NUMERIC); - SQL_STRING_TYPE.put(SQL_REAL, REAL); - SQL_STRING_TYPE.put(SQL_SMALLINT, SMALLINT); - SQL_STRING_TYPE.put(SQL_TINYINT, TINYINT); - SQL_STRING_TYPE.put(SQL_TIME, TIME); - SQL_STRING_TYPE.put(SQL_TIMESTAMP, TIMESTAMP); - SQL_STRING_TYPE.put(SQL_VARBINARY, VARBINARY); - SQL_STRING_TYPE.put(SQL_VARCHAR, VARCHAR); - SQL_STRING_TYPE.put(SQL_NVARCHAR, NVARCHAR); - SQL_STRING_TYPE.put(SQL_OTHER, OTHER); - SQL_STRING_TYPE.put(SQL_NULL, NULL); - - SQL_ENUM_TYPE.put(ARRAY, SQL_ARRAY); - SQL_ENUM_TYPE.put(BIGINT, SQL_BIGINT); - SQL_ENUM_TYPE.put(BINARY, SQL_BINARY); - SQL_ENUM_TYPE.put(BIT, SQL_BIT); - SQL_ENUM_TYPE.put(BOOLEAN, SQL_BOOLEAN); - SQL_ENUM_TYPE.put(BLOB, SQL_BLOB); - SQL_ENUM_TYPE.put(CLOB, SQL_CLOB); - SQL_ENUM_TYPE.put(NCLOB, SQL_NCLOB); - SQL_ENUM_TYPE.put(CHAR, SQL_CHAR); - SQL_ENUM_TYPE.put(NCHAR, SQL_NCHAR); - SQL_ENUM_TYPE.put(DATE, SQL_DATE); - SQL_ENUM_TYPE.put(DECIMAL, SQL_DECIMAL); - SQL_ENUM_TYPE.put(DOUBLE, SQL_DOUBLE); - SQL_ENUM_TYPE.put(FLOAT, SQL_FLOAT); - SQL_ENUM_TYPE.put(INTEGER, SQL_INTEGER); - SQL_ENUM_TYPE.put(LONGVARCHAR, SQL_LONGVARCHAR); - SQL_ENUM_TYPE.put(LONGNVARCHAR, SQL_LONGNVARCHAR); - SQL_ENUM_TYPE.put(LONGVARBINARY, SQL_LONGVARBINARY); - SQL_ENUM_TYPE.put(NUMERIC, SQL_NUMERIC); - SQL_ENUM_TYPE.put(REAL, SQL_REAL); - SQL_ENUM_TYPE.put(SMALLINT, SQL_SMALLINT); - SQL_ENUM_TYPE.put(TINYINT, SQL_TINYINT); - SQL_ENUM_TYPE.put(TIME, SQL_TIME); - SQL_ENUM_TYPE.put(TIMESTAMP, SQL_TIMESTAMP); - SQL_ENUM_TYPE.put(VARBINARY, SQL_VARBINARY); - SQL_ENUM_TYPE.put(VARCHAR, SQL_VARCHAR); - SQL_ENUM_TYPE.put(NVARCHAR, SQL_NVARCHAR); - SQL_ENUM_TYPE.put(SQLXML, SQL_SQLXML); - SQL_ENUM_TYPE.put(OTHER, SQL_OTHER); - SQL_ENUM_TYPE.put(NULL, SQL_NULL); - - SQL_ENUM_JAVA.put(BIGINT, JAVA_LONG); - SQL_ENUM_JAVA.put(BINARY, JAVA_BYTES); - SQL_ENUM_JAVA.put(BIT, JAVA_BOOLEAN); - SQL_ENUM_JAVA.put(BOOLEAN, JAVA_BOOLEAN); - SQL_ENUM_JAVA.put(BLOB, JAVA_BYTES); - SQL_ENUM_JAVA.put(CLOB, JAVA_STRING); - SQL_ENUM_JAVA.put(NCLOB, JAVA_STRING); - SQL_ENUM_JAVA.put(CHAR, JAVA_STRING); - SQL_ENUM_JAVA.put(NCHAR, JAVA_STRING); - SQL_ENUM_JAVA.put(DATE, JAVA_UTILDATE); - SQL_ENUM_JAVA.put(DECIMAL, JAVA_BIGDECIMAL); - SQL_ENUM_JAVA.put(DOUBLE, JAVA_DOUBLE); - SQL_ENUM_JAVA.put(FLOAT, JAVA_FLOAT); - SQL_ENUM_JAVA.put(INTEGER, JAVA_INTEGER); - SQL_ENUM_JAVA.put(LONGVARCHAR, JAVA_STRING); - SQL_ENUM_JAVA.put(LONGNVARCHAR, JAVA_STRING); - SQL_ENUM_JAVA.put(LONGVARBINARY, JAVA_BYTES); - SQL_ENUM_JAVA.put(NUMERIC, JAVA_BIGDECIMAL); - SQL_ENUM_JAVA.put(REAL, JAVA_FLOAT); - SQL_ENUM_JAVA.put(SMALLINT, JAVA_SHORT); - SQL_ENUM_JAVA.put(TINYINT, JAVA_SHORT); - SQL_ENUM_JAVA.put(TIME, JAVA_UTILDATE); - SQL_ENUM_JAVA.put(TIMESTAMP, JAVA_UTILDATE); - SQL_ENUM_JAVA.put(VARBINARY, JAVA_BYTES); - SQL_ENUM_JAVA.put(VARCHAR, JAVA_STRING); - SQL_ENUM_JAVA.put(NVARCHAR, JAVA_STRING); - SQL_ENUM_JAVA.put(SQLXML, JAVA_STRING); - - JAVA_SQL_ENUM.put(JAVA_LONG, BIGINT); - JAVA_SQL_ENUM.put(JAVA_BYTES, BINARY); - JAVA_SQL_ENUM.put(JAVA_BOOLEAN, BIT); - JAVA_SQL_ENUM.put(JAVA_STRING, VARCHAR); - JAVA_SQL_ENUM.put(JAVA_SQLDATE, DATE); - JAVA_SQL_ENUM.put(JAVA_UTILDATE, DATE); - JAVA_SQL_ENUM.put(JAVA_TIMESTAMP, TIMESTAMP); - JAVA_SQL_ENUM.put(JAVA_BIGDECIMAL, DECIMAL); - JAVA_SQL_ENUM.put(JAVA_DOUBLE, DOUBLE); - JAVA_SQL_ENUM.put(JAVA_FLOAT, FLOAT); - JAVA_SQL_ENUM.put(JAVA_INTEGER, INTEGER); - JAVA_SQL_ENUM.put(JAVA_SHORT, SMALLINT); - JAVA_SQL_ENUM.put(JAVA_BYTE, SMALLINT); - JAVA_SQL_ENUM.put(JAVA_TIME, TIME); - JAVA_SQL_ENUM.put(JAVA_TIMESTAMP, TIMESTAMP); - - // add primitives - JAVA_SQL_ENUM.put("byte", TINYINT); - JAVA_SQL_ENUM.put("int", INTEGER); - JAVA_SQL_ENUM.put("short", SMALLINT); - JAVA_SQL_ENUM.put("char", CHAR); - JAVA_SQL_ENUM.put("double", DOUBLE); - JAVA_SQL_ENUM.put("long", BIGINT); - JAVA_SQL_ENUM.put("float", FLOAT); - JAVA_SQL_ENUM.put("boolean", BIT); - } - - /** - * @deprecated - * - * Returns true if supplied type can have a length attribute as a part of column - * definition. - */ - public static boolean supportsLength(int type) { - return JdbcAdapter.supportsLength(type); - } - - /** - * Returns true if supplied type is a numeric type. - */ - public static boolean isNumeric(int type) { - return type == BIGINT - || type == BIT - || type == DECIMAL - || type == DOUBLE - || type == FLOAT - || type == INTEGER - || type == NUMERIC - || type == REAL - || type == SMALLINT - || type == TINYINT; - } - - /** - * Returns true if supplied type is a decimal type. - */ - public static boolean isDecimal(int type) { - return type == DECIMAL - || type == DOUBLE - || type == FLOAT - || type == REAL - || type == NUMERIC; - } - - /** - * Returns an array of string names of the default JDBC data types. - */ - public static String[] getDatabaseTypes() { - Collection<String> types = SQL_STRING_TYPE.keySet(); - return types.toArray(new String[types.size()]); - } - - /** - * Method implements an algorithm to pick a data type from a list of alternatives that - * most closely matches JDBC data type. - */ - protected static String pickDataType(int jdbcType, TypeInfo[] alts) { - int len = alts.length; - - if (len == 0) { - return null; - } - - if (len == 1) { - return alts[0].name; - } - - // now the fun starts.. try to guess the right type - - String jdbcName = getSqlNameByType(jdbcType).toUpperCase(); - - // 1. exact match - for (TypeInfo alt : alts) { - if (jdbcName.equalsIgnoreCase(alt.name)) { - return alt.name; - } - } - - // 2. filter those with biggest precision - long maxPrec = 0; - for (TypeInfo alt : alts) { - if (maxPrec < alt.precision) { - maxPrec = alt.precision; - } - } - - List<TypeInfo> list = new ArrayList<TypeInfo>(); - for (TypeInfo alt : alts) { - if (maxPrec == alt.precision) { - list.add(alt); - } - } - - // work with smaller list now..... - int slen = list.size(); - if (slen == 1) { - return list.get(0).name; - } - - // start/end match - for (TypeInfo aList : list) { - String uppercase = aList.name.toUpperCase(); - if (uppercase.startsWith(jdbcName) || uppercase.endsWith(jdbcName)) { - return aList.name; - } - } - - // in the middle match - for (TypeInfo aList : list) { - String uppercase = aList.name.toUpperCase(); - - if (uppercase.contains(jdbcName)) { - return aList.name; - } - } - - // out of ideas... return the first one - return list.get(0).name; - } - - /** - * Returns a JDBC int type for SQL typem name. - */ - public static int getSqlTypeByName(String typeName) { - Integer tmp = SQL_STRING_TYPE.get(typeName); - return tmp == null ? NOT_DEFINED : tmp; - } - - /** - * Returns a String representation of the SQL type from its JDBC code. - */ - public static String getSqlNameByType(int type) { - return SQL_ENUM_TYPE.get(type); - } - - /** - * Returns default java.sql.Types type by the Java type name. - * - * @param className Fully qualified Java Class name. - * @return The SQL type or NOT_DEFINED if no type found. - */ - public static int getSqlTypeByJava(String className) { - if (className == null) { - return NOT_DEFINED; - } - - Integer type = JAVA_SQL_ENUM.get(className); - if (type != null) { - return type; - } - - // try to load a Java class - some nonstandard mappings may work - - Class<?> aClass; - try { - aClass = Util.getJavaClass(className); - } - catch (Throwable th) { - return NOT_DEFINED; - } - - return getSqlTypeByJava(aClass); - } - - /** - * Guesses a default JDBC type for the Java class. - * - * @since 1.1 - */ - public static int getSqlTypeByJava(Class<?> javaClass) { - if (javaClass == null) { - return NOT_DEFINED; - } - - // check standard mapping of class and superclasses - Class<?> aClass = javaClass; - while (aClass != null) { - - String name; - - if (aClass.isArray()) { - name = aClass.getComponentType().getName() + "[]"; - } - else { - name = aClass.getName(); - } - - Object type = JAVA_SQL_ENUM.get(name); - if (type != null) { - return ((Number) type).intValue(); - } - - aClass = aClass.getSuperclass(); - } - - // check non-standard JDBC types that are still supported by JPA - if (javaClass.isArray()) { - - Class<?> elementType = javaClass.getComponentType(); - if (Character.class.isAssignableFrom(elementType) - || Character.TYPE.isAssignableFrom(elementType)) { - return VARCHAR; - } - else if (Byte.class.isAssignableFrom(elementType) - || Byte.TYPE.isAssignableFrom(elementType)) { - return VARBINARY; - } - } - - if (Calendar.class.isAssignableFrom(javaClass)) { - return TIMESTAMP; - } - - if (BigInteger.class.isAssignableFrom(javaClass)) { - return BIGINT; - } - - // serializable check should be the last one when all other mapping attempts - // failed - if (Serializable.class.isAssignableFrom(javaClass)) { - return VARBINARY; - } - - return NOT_DEFINED; - } - - /** - * Get the corresponding Java type by its java.sql.Types counterpart. Note that this - * method should be used as a last resort, with explicit mapping provided by user used - * as a first choice, as it can only guess how to map certain types, such as NUMERIC, - * etc. - * - * @return Fully qualified Java type name or null if not found. - */ - public static String getJavaBySqlType(int type) { - return SQL_ENUM_JAVA.get(type); - } - - - protected Map<Integer, List<TypeInfo>> databaseTypes = new HashMap<Integer, List<TypeInfo>>(); - - public TypesMapping(DatabaseMetaData metaData) throws SQLException { - // map database types to standard JDBC types - ResultSet rs = metaData.getTypeInfo(); - - try { - while (rs.next()) { - TypeInfo info = new TypeInfo(); - info.name = rs.getString("TYPE_NAME"); - info.jdbcType = rs.getInt("DATA_TYPE"); - info.precision = rs.getLong("PRECISION"); - - Integer key = info.jdbcType; - List<TypeInfo> infos = databaseTypes.get(key); - - if (infos == null) { - infos = new ArrayList<TypeInfo>(); - databaseTypes.put(key, infos); - } - - infos.add(info); - } - } - finally { - rs.close(); - } - - // do some tricks to substitute for missing datatypes - - // 1. swap TIMESTAMP - DATE - swapTypes(TIMESTAMP, DATE); - - // 2. Swap CLOB - LONGVARCHAR - swapTypes(CLOB, LONGVARCHAR); - - // 3. Swap BLOB - LONGVARBINARY - swapTypes(BLOB, LONGVARBINARY); - - // 4. Swap NCLOB - LONGNVARCHAR - swapTypes(NCLOB, LONGNVARCHAR); - } - - private void swapTypes(int type1, int type2) { - List<TypeInfo> type1Info = databaseTypes.get(type1); - List<TypeInfo> type2Info = databaseTypes.get(type2); - - if (type1Info != null && type2Info == null) { - databaseTypes.put(type2, type1Info); - } - if (type2Info != null && type1Info == null) { - databaseTypes.put(type1, type2Info); - } - } - - /** Stores (incomplete) information about database data type */ - static class TypeInfo { - - String name; - int jdbcType; - long precision; - - @Override - public String toString() { - return "[ TypeInfo: " + name + "\n JDBC Type: " + TypesMapping.getSqlNameByType(jdbcType) - + "\n Precision: " + precision + "\n]"; - } - } + // Never use "-1" or any other normal integer, since there + // is a big chance it is being reserved in java.sql.Types + public static final int NOT_DEFINED = Integer.MAX_VALUE; + + // char constants for the sql data types + public static final String SQL_ARRAY = "ARRAY"; + public static final String SQL_BIGINT = "BIGINT"; + public static final String SQL_BINARY = "BINARY"; + public static final String SQL_BIT = "BIT"; + public static final String SQL_BLOB = "BLOB"; + + /** + * @since 1.2 + */ + public static final String SQL_BOOLEAN = "BOOLEAN"; + + public static final String SQL_CLOB = "CLOB"; + public static final String SQL_NCLOB = "NCLOB"; + public static final String SQL_CHAR = "CHAR"; + public static final String SQL_NCHAR = "NCHAR"; + public static final String SQL_DATE = "DATE"; + public static final String SQL_DECIMAL = "DECIMAL"; + public static final String SQL_DOUBLE = "DOUBLE"; + public static final String SQL_FLOAT = "FLOAT"; + public static final String SQL_INTEGER = "INTEGER"; + public static final String SQL_LONGVARCHAR = "LONGVARCHAR"; + public static final String SQL_LONGNVARCHAR = "LONGNVARCHAR"; + public static final String SQL_LONGVARBINARY = "LONGVARBINARY"; + public static final String SQL_NUMERIC = "NUMERIC"; + public static final String SQL_REAL = "REAL"; + public static final String SQL_SMALLINT = "SMALLINT"; + public static final String SQL_TINYINT = "TINYINT"; + public static final String SQL_TIME = "TIME"; + public static final String SQL_TIMESTAMP = "TIMESTAMP"; + public static final String SQL_VARBINARY = "VARBINARY"; + public static final String SQL_VARCHAR = "VARCHAR"; + public static final String SQL_NVARCHAR = "NVARCHAR"; + public static final String SQL_SQLXML = "SQLXML"; + public static final String SQL_OTHER = "OTHER"; + public static final String SQL_NULL = "NULL"; + + // char constants for Java data types + public static final String JAVA_LONG = "java.lang.Long"; + public static final String JAVA_BYTES = "byte[]"; + public static final String JAVA_BOOLEAN = "java.lang.Boolean"; + public static final String JAVA_STRING = "java.lang.String"; + public static final String JAVA_SQLDATE = "java.sql.Date"; + public static final String JAVA_UTILDATE = "java.util.Date"; + public static final String JAVA_BIGDECIMAL = "java.math.BigDecimal"; + public static final String JAVA_DOUBLE = "java.lang.Double"; + public static final String JAVA_FLOAT = "java.lang.Float"; + public static final String JAVA_INTEGER = "java.lang.Integer"; + public static final String JAVA_SHORT = "java.lang.Short"; + public static final String JAVA_BYTE = "java.lang.Byte"; + public static final String JAVA_TIME = "java.sql.Time"; + public static final String JAVA_TIMESTAMP = "java.sql.Timestamp"; + public static final String JAVA_BLOB = "java.sql.Blob"; + + /** + * Keys: SQL string type names, Values: SQL int type definitions from + * java.sql.Types + */ + private static final Map<String, Integer> SQL_STRING_TYPE = new HashMap<String, Integer>(); + + /** + * Keys: SQL int type definitions from java.sql.Types, Values: SQL string + * type names + */ + private static final Map<Integer, String> SQL_ENUM_TYPE = new HashMap<Integer, String>(); + + /** + * Keys: SQL int type definitions from java.sql.Types, Values: java class + * names + */ + private static final Map<Integer, String> SQL_ENUM_JAVA = new HashMap<Integer, String>(); + + /** + * Keys: java class names, Values: SQL int type definitions from + * java.sql.Types + */ + private static final Map<String, Integer> JAVA_SQL_ENUM = new HashMap<String, Integer>(); + + static { + // SQL_STRING_TYPE.put(SQL_ARRAY, ARRAY); + SQL_STRING_TYPE.put(SQL_BIGINT, BIGINT); + SQL_STRING_TYPE.put(SQL_BINARY, BINARY); + SQL_STRING_TYPE.put(SQL_BIT, BIT); + SQL_STRING_TYPE.put(SQL_BLOB, BLOB); + SQL_STRING_TYPE.put(SQL_BOOLEAN, BOOLEAN); + SQL_STRING_TYPE.put(SQL_CLOB, CLOB); + SQL_STRING_TYPE.put(SQL_NCLOB, NCLOB); + SQL_STRING_TYPE.put(SQL_CHAR, CHAR); + SQL_STRING_TYPE.put(SQL_NCHAR, NCHAR); + SQL_STRING_TYPE.put(SQL_DATE, DATE); + SQL_STRING_TYPE.put(SQL_DECIMAL, DECIMAL); + SQL_STRING_TYPE.put(SQL_DOUBLE, DOUBLE); + SQL_STRING_TYPE.put(SQL_FLOAT, FLOAT); + SQL_STRING_TYPE.put(SQL_INTEGER, INTEGER); + SQL_STRING_TYPE.put(SQL_LONGVARCHAR, LONGVARCHAR); + SQL_STRING_TYPE.put(SQL_LONGNVARCHAR, LONGNVARCHAR); + SQL_STRING_TYPE.put(SQL_LONGVARBINARY, LONGVARBINARY); + SQL_STRING_TYPE.put(SQL_NUMERIC, NUMERIC); + SQL_STRING_TYPE.put(SQL_REAL, REAL); + SQL_STRING_TYPE.put(SQL_SMALLINT, SMALLINT); + SQL_STRING_TYPE.put(SQL_TINYINT, TINYINT); + SQL_STRING_TYPE.put(SQL_TIME, TIME); + SQL_STRING_TYPE.put(SQL_TIMESTAMP, TIMESTAMP); + SQL_STRING_TYPE.put(SQL_VARBINARY, VARBINARY); + SQL_STRING_TYPE.put(SQL_VARCHAR, VARCHAR); + SQL_STRING_TYPE.put(SQL_NVARCHAR, NVARCHAR); + SQL_STRING_TYPE.put(SQL_OTHER, OTHER); + SQL_STRING_TYPE.put(SQL_NULL, NULL); + + SQL_ENUM_TYPE.put(ARRAY, SQL_ARRAY); + SQL_ENUM_TYPE.put(BIGINT, SQL_BIGINT); + SQL_ENUM_TYPE.put(BINARY, SQL_BINARY); + SQL_ENUM_TYPE.put(BIT, SQL_BIT); + SQL_ENUM_TYPE.put(BOOLEAN, SQL_BOOLEAN); + SQL_ENUM_TYPE.put(BLOB, SQL_BLOB); + SQL_ENUM_TYPE.put(CLOB, SQL_CLOB); + SQL_ENUM_TYPE.put(NCLOB, SQL_NCLOB); + SQL_ENUM_TYPE.put(CHAR, SQL_CHAR); + SQL_ENUM_TYPE.put(NCHAR, SQL_NCHAR); + SQL_ENUM_TYPE.put(DATE, SQL_DATE); + SQL_ENUM_TYPE.put(DECIMAL, SQL_DECIMAL); + SQL_ENUM_TYPE.put(DOUBLE, SQL_DOUBLE); + SQL_ENUM_TYPE.put(FLOAT, SQL_FLOAT); + SQL_ENUM_TYPE.put(INTEGER, SQL_INTEGER); + SQL_ENUM_TYPE.put(LONGVARCHAR, SQL_LONGVARCHAR); + SQL_ENUM_TYPE.put(LONGNVARCHAR, SQL_LONGNVARCHAR); + SQL_ENUM_TYPE.put(LONGVARBINARY, SQL_LONGVARBINARY); + SQL_ENUM_TYPE.put(NUMERIC, SQL_NUMERIC); + SQL_ENUM_TYPE.put(REAL, SQL_REAL); + SQL_ENUM_TYPE.put(SMALLINT, SQL_SMALLINT); + SQL_ENUM_TYPE.put(TINYINT, SQL_TINYINT); + SQL_ENUM_TYPE.put(TIME, SQL_TIME); + SQL_ENUM_TYPE.put(TIMESTAMP, SQL_TIMESTAMP); + SQL_ENUM_TYPE.put(VARBINARY, SQL_VARBINARY); + SQL_ENUM_TYPE.put(VARCHAR, SQL_VARCHAR); + SQL_ENUM_TYPE.put(NVARCHAR, SQL_NVARCHAR); + SQL_ENUM_TYPE.put(SQLXML, SQL_SQLXML); + SQL_ENUM_TYPE.put(OTHER, SQL_OTHER); + SQL_ENUM_TYPE.put(NULL, SQL_NULL); + + SQL_ENUM_JAVA.put(BIGINT, JAVA_LONG); + SQL_ENUM_JAVA.put(BINARY, JAVA_BYTES); + SQL_ENUM_JAVA.put(BIT, JAVA_BOOLEAN); + SQL_ENUM_JAVA.put(BOOLEAN, JAVA_BOOLEAN); + SQL_ENUM_JAVA.put(BLOB, JAVA_BYTES); + SQL_ENUM_JAVA.put(CLOB, JAVA_STRING); + SQL_ENUM_JAVA.put(NCLOB, JAVA_STRING); + SQL_ENUM_JAVA.put(CHAR, JAVA_STRING); + SQL_ENUM_JAVA.put(NCHAR, JAVA_STRING); + SQL_ENUM_JAVA.put(DATE, JAVA_UTILDATE); + SQL_ENUM_JAVA.put(DECIMAL, JAVA_BIGDECIMAL); + SQL_ENUM_JAVA.put(DOUBLE, JAVA_DOUBLE); + SQL_ENUM_JAVA.put(FLOAT, JAVA_FLOAT); + SQL_ENUM_JAVA.put(INTEGER, JAVA_INTEGER); + SQL_ENUM_JAVA.put(LONGVARCHAR, JAVA_STRING); + SQL_ENUM_JAVA.put(LONGNVARCHAR, JAVA_STRING); + SQL_ENUM_JAVA.put(LONGVARBINARY, JAVA_BYTES); + SQL_ENUM_JAVA.put(NUMERIC, JAVA_BIGDECIMAL); + SQL_ENUM_JAVA.put(REAL, JAVA_FLOAT); + SQL_ENUM_JAVA.put(SMALLINT, JAVA_SHORT); + SQL_ENUM_JAVA.put(TINYINT, JAVA_SHORT); + SQL_ENUM_JAVA.put(TIME, JAVA_UTILDATE); + SQL_ENUM_JAVA.put(TIMESTAMP, JAVA_UTILDATE); + SQL_ENUM_JAVA.put(VARBINARY, JAVA_BYTES); + SQL_ENUM_JAVA.put(VARCHAR, JAVA_STRING); + SQL_ENUM_JAVA.put(NVARCHAR, JAVA_STRING); + SQL_ENUM_JAVA.put(SQLXML, JAVA_STRING); + + JAVA_SQL_ENUM.put(JAVA_LONG, BIGINT); + JAVA_SQL_ENUM.put(JAVA_BYTES, BINARY); + JAVA_SQL_ENUM.put(JAVA_BOOLEAN, BIT); + JAVA_SQL_ENUM.put(JAVA_STRING, VARCHAR); + JAVA_SQL_ENUM.put(JAVA_SQLDATE, DATE); + JAVA_SQL_ENUM.put(JAVA_UTILDATE, DATE); + JAVA_SQL_ENUM.put(JAVA_TIMESTAMP, TIMESTAMP); + JAVA_SQL_ENUM.put(JAVA_BIGDECIMAL, DECIMAL); + JAVA_SQL_ENUM.put(JAVA_DOUBLE, DOUBLE); + JAVA_SQL_ENUM.put(JAVA_FLOAT, FLOAT); + JAVA_SQL_ENUM.put(JAVA_INTEGER, INTEGER); + JAVA_SQL_ENUM.put(JAVA_SHORT, SMALLINT); + JAVA_SQL_ENUM.put(JAVA_BYTE, SMALLINT); + JAVA_SQL_ENUM.put(JAVA_TIME, TIME); + JAVA_SQL_ENUM.put(JAVA_TIMESTAMP, TIMESTAMP); + + // add primitives + JAVA_SQL_ENUM.put("byte", TINYINT); + JAVA_SQL_ENUM.put("int", INTEGER); + JAVA_SQL_ENUM.put("short", SMALLINT); + JAVA_SQL_ENUM.put("char", CHAR); + JAVA_SQL_ENUM.put("double", DOUBLE); + JAVA_SQL_ENUM.put("long", BIGINT); + JAVA_SQL_ENUM.put("float", FLOAT); + JAVA_SQL_ENUM.put("boolean", BIT); + } + + /** + * @deprecated + * + * Returns true if supplied type can have a length attribute as + * a part of column definition. + */ + public static boolean supportsLength(int type) { + return JdbcAdapter.supportsLength(type); + } + + /** + * Returns true if supplied type is a numeric type. + */ + public static boolean isNumeric(int type) { + return type == BIGINT || type == BIT || type == DECIMAL || type == DOUBLE || type == FLOAT || type == INTEGER + || type == NUMERIC || type == REAL || type == SMALLINT || type == TINYINT; + } + + /** + * Returns true if supplied type is a decimal type. + */ + public static boolean isDecimal(int type) { + return type == DECIMAL || type == DOUBLE || type == FLOAT || type == REAL || type == NUMERIC; + } + + /** + * Returns an array of string names of the default JDBC data types. + */ + public static String[] getDatabaseTypes() { + Collection<String> types = SQL_STRING_TYPE.keySet(); + return types.toArray(new String[types.size()]); + } + + /** + * Method implements an algorithm to pick a data type from a list of + * alternatives that most closely matches JDBC data type. + */ + protected static String pickDataType(int jdbcType, TypeInfo[] alts) { + int len = alts.length; + + if (len == 0) { + return null; + } + + if (len == 1) { + return alts[0].name; + } + + // now the fun starts.. try to guess the right type + + String jdbcName = getSqlNameByType(jdbcType).toUpperCase(); + + // 1. exact match + for (TypeInfo alt : alts) { + if (jdbcName.equalsIgnoreCase(alt.name)) { + return alt.name; + } + } + + // 2. filter those with biggest precision + long maxPrec = 0; + for (TypeInfo alt : alts) { + if (maxPrec < alt.precision) { + maxPrec = alt.precision; + } + } + + List<TypeInfo> list = new ArrayList<TypeInfo>(); + for (TypeInfo alt : alts) { + if (maxPrec == alt.precision) { + list.add(alt); + } + } + + // work with smaller list now..... + int slen = list.size(); + if (slen == 1) { + return list.get(0).name; + } + + // start/end match + for (TypeInfo aList : list) { + String uppercase = aList.name.toUpperCase(); + if (uppercase.startsWith(jdbcName) || uppercase.endsWith(jdbcName)) { + return aList.name; + } + } + + // in the middle match + for (TypeInfo aList : list) { + String uppercase = aList.name.toUpperCase(); + + if (uppercase.contains(jdbcName)) { + return aList.name; + } + } + + // out of ideas... return the first one + return list.get(0).name; + } + + /** + * Returns a JDBC int type for SQL typem name. + */ + public static int getSqlTypeByName(String typeName) { + Integer tmp = SQL_STRING_TYPE.get(typeName); + return tmp == null ? NOT_DEFINED : tmp; + } + + /** + * Returns a String representation of the SQL type from its JDBC code. + */ + public static String getSqlNameByType(int type) { + return SQL_ENUM_TYPE.get(type); + } + + /** + * Returns default java.sql.Types type by the Java type name. + * + * @param className + * Fully qualified Java Class name. + * @return The SQL type or NOT_DEFINED if no type found. + */ + public static int getSqlTypeByJava(String className) { + if (className == null) { + return NOT_DEFINED; + } + + Integer type = JAVA_SQL_ENUM.get(className); + if (type != null) { + return type; + } + + // try to load a Java class - some nonstandard mappings may work + + Class<?> aClass; + try { + aClass = Util.getJavaClass(className); + } catch (Throwable th) { + return NOT_DEFINED; + } + + return getSqlTypeByJava(aClass); + } + + /** + * Guesses a default JDBC type for the Java class. + * + * @since 1.1 + */ + public static int getSqlTypeByJava(Class<?> javaClass) { + if (javaClass == null) { + return NOT_DEFINED; + } + + // check standard mapping of class and superclasses + Class<?> aClass = javaClass; + while (aClass != null) { + + String name; + + if (aClass.isArray()) { + name = aClass.getComponentType().getName() + "[]"; + } else { + name = aClass.getName(); + } + + Object type = JAVA_SQL_ENUM.get(name); + if (type != null) { + return ((Number) type).intValue(); + } + + aClass = aClass.getSuperclass(); + } + + // check non-standard JDBC types that are still supported by JPA + if (javaClass.isArray()) { + + Class<?> elementType = javaClass.getComponentType(); + if (Character.class.isAssignableFrom(elementType) || Character.TYPE.isAssignableFrom(elementType)) { + return VARCHAR; + } else if (Byte.class.isAssignableFrom(elementType) || Byte.TYPE.isAssignableFrom(elementType)) { + return VARBINARY; + } + } + + if (Calendar.class.isAssignableFrom(javaClass)) { + return TIMESTAMP; + } + + if (BigInteger.class.isAssignableFrom(javaClass)) { + return BIGINT; + } + + // serializable check should be the last one when all other mapping + // attempts + // failed + if (Serializable.class.isAssignableFrom(javaClass)) { + return VARBINARY; + } + + return NOT_DEFINED; + } + + /** + * Get the corresponding Java type by its java.sql.Types counterpart. Note + * that this method should be used as a last resort, with explicit mapping + * provided by user used as a first choice, as it can only guess how to map + * certain types, such as NUMERIC, etc. + * + * @return Fully qualified Java type name or null if not found. + */ + public static String getJavaBySqlType(int type) { + return SQL_ENUM_JAVA.get(type); + } + + protected Map<Integer, List<TypeInfo>> databaseTypes = new HashMap<Integer, List<TypeInfo>>(); + + public TypesMapping(DatabaseMetaData metaData) throws SQLException { + // map database types to standard JDBC types + + try (ResultSet rs = metaData.getTypeInfo();) { + while (rs.next()) { + TypeInfo info = new TypeInfo(); + info.name = rs.getString("TYPE_NAME"); + info.jdbcType = rs.getInt("DATA_TYPE"); + info.precision = rs.getLong("PRECISION"); + + Integer key = info.jdbcType; + List<TypeInfo> infos = databaseTypes.get(key); + + if (infos == null) { + infos = new ArrayList<TypeInfo>(); + databaseTypes.put(key, infos); + } + + infos.add(info); + } + } + + // do some tricks to substitute for missing datatypes + + // 1. swap TIMESTAMP - DATE + swapTypes(TIMESTAMP, DATE); + + // 2. Swap CLOB - LONGVARCHAR + swapTypes(CLOB, LONGVARCHAR); + + // 3. Swap BLOB - LONGVARBINARY + swapTypes(BLOB, LONGVARBINARY); + + // 4. Swap NCLOB - LONGNVARCHAR + swapTypes(NCLOB, LONGNVARCHAR); + } + + private void swapTypes(int type1, int type2) { + List<TypeInfo> type1Info = databaseTypes.get(type1); + List<TypeInfo> type2Info = databaseTypes.get(type2); + + if (type1Info != null && type2Info == null) { + databaseTypes.put(type2, type1Info); + } + if (type2Info != null && type1Info == null) { + databaseTypes.put(type1, type2Info); + } + } + + /** Stores (incomplete) information about database data type */ + static class TypeInfo { + + String name; + int jdbcType; + long precision; + + @Override + public String toString() { + return "[ TypeInfo: " + name + "\n JDBC Type: " + TypesMapping.getSqlNameByType(jdbcType) + + "\n Precision: " + precision + "\n]"; + } + } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/26d8434d/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 f487c2b..667d6db 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 @@ -39,164 +39,150 @@ import org.apache.cayenne.map.DbEntity; */ public class DB2PkGenerator extends JdbcPkGenerator { - DB2PkGenerator(JdbcAdapter adapter) { - super(adapter); - } - - private static final String _SEQUENCE_PREFIX = "S_"; - - /** - * @since 3.0 - */ - @Override - protected long longPkFromDatabase(DataNode node, DbEntity entity) throws Exception { - - String pkGeneratingSequenceName = sequenceName(entity); - - Connection con = node.getDataSource().getConnection(); - try { - Statement st = con.createStatement(); - try { - String sql = "SELECT NEXTVAL FOR " + pkGeneratingSequenceName + " FROM SYSIBM.SYSDUMMY1"; - adapter.getJdbcEventLogger().logQuery(sql, Collections.EMPTY_LIST); - ResultSet rs = st.executeQuery(sql); - try { - // Object pk = null; - if (!rs.next()) { - throw new CayenneRuntimeException("Error generating pk for DbEntity " + entity.getName()); - } - return rs.getLong(1); - } finally { - rs.close(); - } - } finally { - st.close(); - } - } finally { - con.close(); - } - } - - @Override - public void createAutoPk(DataNode node, List<DbEntity> dbEntities) throws Exception { - Collection<String> sequences = getExistingSequences(node); - for (DbEntity entity : dbEntities) { - if (!sequences.contains(sequenceName(entity))) { - this.runUpdate(node, createSequenceString(entity)); - } - } - } - - /** - * Creates a list of CREATE SEQUENCE statements for the list of DbEntities. - */ - @Override - public List<String> createAutoPkStatements(List<DbEntity> dbEntities) { - List<String> list = new ArrayList<String>(dbEntities.size()); - for (DbEntity entity : dbEntities) { - list.add(createSequenceString(entity)); - } - return list; - } - - /** - * Drops PK sequences for all specified DbEntities. - */ - @Override - public void dropAutoPk(DataNode node, List<DbEntity> dbEntities) throws Exception { - Collection<String> sequences = getExistingSequences(node); - - for (DbEntity ent : dbEntities) { - String name; - if (ent.getDataMap().isQuotingSQLIdentifiers()) { - DbEntity tempEnt = new DbEntity(); - DataMap dm = new DataMap(); - dm.setQuotingSQLIdentifiers(false); - tempEnt.setDataMap(dm); - tempEnt.setName(ent.getName()); - name = sequenceName(tempEnt); - } else { - name = sequenceName(ent); - } - if (sequences.contains(name)) { - runUpdate(node, dropSequenceString(ent)); - } - } - } - - /** - * Creates a list of DROP SEQUENCE statements for the list of DbEntities. - */ - @Override - public List<String> dropAutoPkStatements(List<DbEntity> dbEntities) { - List<String> list = new ArrayList<String>(dbEntities.size()); - for (DbEntity entity : dbEntities) { - list.add(dropSequenceString(entity)); - } - return list; - } - - /** - * Fetches a list of existing sequences that might match Cayenne generated - * ones. - */ - protected List<String> getExistingSequences(DataNode node) throws SQLException { - - // check existing sequences - Connection con = node.getDataSource().getConnection(); - - try { - Statement sel = con.createStatement(); - try { - StringBuilder buffer = new StringBuilder(); - buffer.append("SELECT SEQNAME FROM SYSCAT.SEQUENCES ").append("WHERE SEQNAME LIKE '") - .append(_SEQUENCE_PREFIX).append("%'"); - - String sql = buffer.toString(); - adapter.getJdbcEventLogger().logQuery(sql, Collections.EMPTY_LIST); - ResultSet rs = sel.executeQuery(sql); - try { - List<String> sequenceList = new ArrayList<String>(); - while (rs.next()) { - sequenceList.add(rs.getString(1)); - } - return sequenceList; - } finally { - rs.close(); - } - } finally { - sel.close(); - } - } finally { - con.close(); - } - } - - /** - * Returns default sequence name for DbEntity. - */ - protected String sequenceName(DbEntity entity) { - String entName = entity.getName(); - String seqName = _SEQUENCE_PREFIX + entName; - - return adapter.getQuotingStrategy().quotedIdentifier(entity, entity.getCatalog(), entity.getSchema(), seqName); - } - - /** - * Returns DROP SEQUENCE statement. - */ - protected String dropSequenceString(DbEntity entity) { - return "DROP SEQUENCE " + sequenceName(entity) + " RESTRICT "; - } - - /** - * Returns CREATE SEQUENCE statement for entity. - */ - protected String createSequenceString(DbEntity entity) { - StringBuilder buf = new StringBuilder(); - buf.append("CREATE SEQUENCE ").append(sequenceName(entity)).append(" START WITH ").append(pkStartValue).append(" INCREMENT BY ") - .append(getPkCacheSize()).append(" NO MAXVALUE ").append(" NO CYCLE ").append(" CACHE ") - .append(getPkCacheSize()); - return buf.toString(); - } + DB2PkGenerator(JdbcAdapter adapter) { + super(adapter); + } + + private static final String _SEQUENCE_PREFIX = "S_"; + + /** + * @since 3.0 + */ + @Override + protected long longPkFromDatabase(DataNode node, DbEntity entity) throws Exception { + + String pkGeneratingSequenceName = sequenceName(entity); + + try (Connection con = node.getDataSource().getConnection();) { + + try (Statement st = con.createStatement();) { + String sql = "SELECT NEXTVAL FOR " + pkGeneratingSequenceName + " FROM SYSIBM.SYSDUMMY1"; + adapter.getJdbcEventLogger().logQuery(sql, Collections.EMPTY_LIST); + + try (ResultSet rs = st.executeQuery(sql);) { + // Object pk = null; + if (!rs.next()) { + throw new CayenneRuntimeException("Error generating pk for DbEntity " + entity.getName()); + } + return rs.getLong(1); + } + } + } + } + + @Override + public void createAutoPk(DataNode node, List<DbEntity> dbEntities) throws Exception { + Collection<String> sequences = getExistingSequences(node); + for (DbEntity entity : dbEntities) { + if (!sequences.contains(sequenceName(entity))) { + this.runUpdate(node, createSequenceString(entity)); + } + } + } + + /** + * Creates a list of CREATE SEQUENCE statements for the list of DbEntities. + */ + @Override + public List<String> createAutoPkStatements(List<DbEntity> dbEntities) { + List<String> list = new ArrayList<String>(dbEntities.size()); + for (DbEntity entity : dbEntities) { + list.add(createSequenceString(entity)); + } + return list; + } + + /** + * Drops PK sequences for all specified DbEntities. + */ + @Override + public void dropAutoPk(DataNode node, List<DbEntity> dbEntities) throws Exception { + Collection<String> sequences = getExistingSequences(node); + + for (DbEntity ent : dbEntities) { + String name; + if (ent.getDataMap().isQuotingSQLIdentifiers()) { + DbEntity tempEnt = new DbEntity(); + DataMap dm = new DataMap(); + dm.setQuotingSQLIdentifiers(false); + tempEnt.setDataMap(dm); + tempEnt.setName(ent.getName()); + name = sequenceName(tempEnt); + } else { + name = sequenceName(ent); + } + if (sequences.contains(name)) { + runUpdate(node, dropSequenceString(ent)); + } + } + } + + /** + * Creates a list of DROP SEQUENCE statements for the list of DbEntities. + */ + @Override + public List<String> dropAutoPkStatements(List<DbEntity> dbEntities) { + List<String> list = new ArrayList<String>(dbEntities.size()); + for (DbEntity entity : dbEntities) { + list.add(dropSequenceString(entity)); + } + return list; + } + + /** + * Fetches a list of existing sequences that might match Cayenne generated + * ones. + */ + protected List<String> getExistingSequences(DataNode node) throws SQLException { + + // check existing sequences + + try (Connection con = node.getDataSource().getConnection();) { + + try (Statement sel = con.createStatement();) { + StringBuilder buffer = new StringBuilder(); + buffer.append("SELECT SEQNAME FROM SYSCAT.SEQUENCES ").append("WHERE SEQNAME LIKE '") + .append(_SEQUENCE_PREFIX).append("%'"); + + String sql = buffer.toString(); + adapter.getJdbcEventLogger().logQuery(sql, Collections.EMPTY_LIST); + + try (ResultSet rs = sel.executeQuery(sql);) { + List<String> sequenceList = new ArrayList<String>(); + while (rs.next()) { + sequenceList.add(rs.getString(1)); + } + return sequenceList; + } + } + } + } + + /** + * Returns default sequence name for DbEntity. + */ + protected String sequenceName(DbEntity entity) { + String entName = entity.getName(); + String seqName = _SEQUENCE_PREFIX + entName; + + return adapter.getQuotingStrategy().quotedIdentifier(entity, entity.getCatalog(), entity.getSchema(), seqName); + } + + /** + * Returns DROP SEQUENCE statement. + */ + protected String dropSequenceString(DbEntity entity) { + return "DROP SEQUENCE " + sequenceName(entity) + " RESTRICT "; + } + + /** + * Returns CREATE SEQUENCE statement for entity. + */ + protected String createSequenceString(DbEntity entity) { + StringBuilder buf = new StringBuilder(); + buf.append("CREATE SEQUENCE ").append(sequenceName(entity)).append(" START WITH ").append(pkStartValue) + .append(" INCREMENT BY ").append(getPkCacheSize()).append(" NO MAXVALUE ").append(" NO CYCLE ") + .append(" CACHE ").append(getPkCacheSize()); + return buf.toString(); + } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/26d8434d/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2ProcedureAction.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2ProcedureAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2ProcedureAction.java index b33e17a..aa9b61f 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2ProcedureAction.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2ProcedureAction.java @@ -35,59 +35,45 @@ import org.apache.cayenne.query.ProcedureQuery; */ class DB2ProcedureAction extends ProcedureAction { - DB2ProcedureAction(ProcedureQuery query, DataNode dataNode) { - super(query, dataNode); - } + DB2ProcedureAction(ProcedureQuery query, DataNode dataNode) { + super(query, dataNode); + } - @Override - public void performAction(Connection connection, OperationObserver observer) throws SQLException, Exception { + @Override + public void performAction(Connection connection, OperationObserver observer) throws SQLException, Exception { - // cloned from super except for result processing consistent with - // CAY-1874 - - processedResultSets = 0; + // cloned from super except for result processing consistent with + // CAY-1874 - ProcedureTranslator transl = createTranslator(connection); + processedResultSets = 0; - CallableStatement statement = (CallableStatement) transl.createStatement(); + ProcedureTranslator transl = createTranslator(connection); - try { - initStatement(statement); - boolean hasResultSet = statement.execute(); + try (CallableStatement statement = (CallableStatement) transl.createStatement();) { + initStatement(statement); + boolean hasResultSet = statement.execute(); - // read out parameters - readProcedureOutParameters(statement, observer); + // read out parameters + readProcedureOutParameters(statement, observer); - // read the rest of the query - while (true) { - if (hasResultSet) { - ResultSet rs = statement.getResultSet(); + // read the rest of the query + while (true) { + if (hasResultSet) { - try { - RowDescriptor descriptor = describeResultSet(rs, processedResultSets++); - readResultSet(rs, descriptor, query, observer); - } finally { - try { - rs.close(); - } catch (SQLException ex) { - } - } - } else { - int updateCount = statement.getUpdateCount(); - if (updateCount == -1) { - break; - } - dataNode.getJdbcEventLogger().logUpdateCount(updateCount); - observer.nextCount(query, updateCount); - } - hasResultSet = statement.getMoreResults(); - } - } finally { - try { - statement.close(); - } catch (SQLException ex) { - - } - } - } + try (ResultSet rs = statement.getResultSet();) { + RowDescriptor descriptor = describeResultSet(rs, processedResultSets++); + readResultSet(rs, descriptor, query, observer); + } + } else { + int updateCount = statement.getUpdateCount(); + if (updateCount == -1) { + break; + } + dataNode.getJdbcEventLogger().logUpdateCount(updateCount); + observer.nextCount(query, updateCount); + } + hasResultSet = statement.getMoreResults(); + } + } + } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/26d8434d/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 583bb33..4ed7469 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 @@ -32,72 +32,55 @@ import org.apache.cayenne.log.JdbcEventLogger; import org.apache.cayenne.map.DbEntity; /** - * Default PK generator for Derby that uses updateable ResultSet to get the next id from - * the lookup table. + * Default PK generator for Derby that uses updateable ResultSet to get the next + * id from the lookup table. * * @since 1.2 */ public class DerbyPkGenerator extends JdbcPkGenerator { - DerbyPkGenerator(JdbcAdapter adapter) { - super(adapter); - } - - static final String SELECT_QUERY = "SELECT NEXT_ID FROM AUTO_PK_SUPPORT" - + " WHERE TABLE_NAME = ? FOR UPDATE"; - - /** - * @since 3.0 - */ - @Override - protected long longPkFromDatabase(DataNode node, DbEntity entity) throws Exception { - - JdbcEventLogger logger = adapter.getJdbcEventLogger(); - if (logger.isLoggable()) { - logger.logQuery(SELECT_QUERY, Collections - .singletonList(entity.getName())); - } - - Connection c = node.getDataSource().getConnection(); - try { - PreparedStatement select = c.prepareStatement( - SELECT_QUERY, - ResultSet.TYPE_FORWARD_ONLY, - ResultSet.CONCUR_UPDATABLE); - try { - select.setString(1, entity.getName()); - ResultSet rs = select.executeQuery(); - - try { - if (!rs.next()) { - throw new CayenneException("PK lookup failed for table: " - + entity.getName()); - } - - long nextId = rs.getLong(1); - - rs.updateLong(1, nextId + pkCacheSize); - rs.updateRow(); - - if (rs.next()) { - throw new CayenneException("More than one PK record for table: " - + entity.getName()); - } - - c.commit(); - - return nextId; - } - finally { - rs.close(); - } - } - finally { - select.close(); - } - } - finally { - c.close(); - } - } + DerbyPkGenerator(JdbcAdapter adapter) { + super(adapter); + } + + static final String SELECT_QUERY = "SELECT NEXT_ID FROM AUTO_PK_SUPPORT" + " WHERE TABLE_NAME = ? FOR UPDATE"; + + /** + * @since 3.0 + */ + @Override + protected long longPkFromDatabase(DataNode node, DbEntity entity) throws Exception { + + JdbcEventLogger logger = adapter.getJdbcEventLogger(); + if (logger.isLoggable()) { + logger.logQuery(SELECT_QUERY, Collections.singletonList(entity.getName())); + } + + try (Connection c = node.getDataSource().getConnection();) { + + try (PreparedStatement select = c.prepareStatement(SELECT_QUERY, ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_UPDATABLE);) { + select.setString(1, entity.getName()); + + try (ResultSet rs = select.executeQuery();) { + if (!rs.next()) { + throw new CayenneException("PK lookup failed for table: " + entity.getName()); + } + + long nextId = rs.getLong(1); + + rs.updateLong(1, nextId + pkCacheSize); + rs.updateRow(); + + if (rs.next()) { + throw new CayenneException("More than one PK record for table: " + entity.getName()); + } + + c.commit(); + + return nextId; + } + } + } + } }
