diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/TableJDBCSeq.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/TableJDBCSeq.java index 3cd848c22..15bccc00f 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/TableJDBCSeq.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/TableJDBCSeq.java @@ -366,8 +366,18 @@ public class TableJDBCSeq * Creates the object-level representation of the sequence table. */ private void buildTable() { - String tableName = Strings.getClassName(_table); - String schemaName = Strings.getPackageName(_table); + String tableName = null; + String schemaName = ""; + if (StringUtils.contains(_table,'.')) { + String[] tableParts = StringUtils.split(_table, '.'); + // TODO: do we need to check for length? Could we have xxx. or .xxx? + schemaName = tableParts[0]; + tableName = tableParts[1]; + } + else { + tableName = _table; + } + if (schemaName.length() == 0) schemaName = Schemas.getNewTableSchema(_conf); diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/SequenceMapping.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/SequenceMapping.java index 981d4ae06..c2a446bf6 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/SequenceMapping.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/SequenceMapping.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.List; import org.apache.commons.lang.StringUtils; +import org.apache.openjpa.conf.OpenJPAConfiguration; import org.apache.openjpa.jdbc.conf.JDBCSeqValue; import org.apache.openjpa.jdbc.kernel.ClassTableJDBCSeq; import org.apache.openjpa.jdbc.kernel.TableJDBCSeq; @@ -159,10 +160,37 @@ public class SequenceMapping protected void addStandardProperties(StringBuffer props) { super.addStandardProperties(props); - appendProperty(props, PROP_TABLE, _table); - appendProperty(props, PROP_SEQUENCE_COL, _sequenceColumn); - appendProperty(props, PROP_PK_COL, _primaryKeyColumn); - appendProperty(props, PROP_PK_VALUE, _primaryKeyValue); + // Quotes are conditionally added to the following because the props + // are eventually passed to the Configurations.parseProperties() + // method, which strips off quotes. This is a problem when these + // properties are intentionally delimited with quotes. So, an extra + // set preserves the intended ones. While this is an ugly solution, + // it's less ugly than other ones. + String table = _table; + if (table != null && table.startsWith("\"") + && table.endsWith("\"")) { + table = "\"" + table + "\""; + } + String sequenceColumn = _sequenceColumn; + if (sequenceColumn != null && sequenceColumn.startsWith("\"") + && sequenceColumn.endsWith("\"")) { + sequenceColumn = "\"" + sequenceColumn + "\""; + } + String primaryKeyColumn = _primaryKeyColumn; + if (primaryKeyColumn !=null && primaryKeyColumn.startsWith("\"") + && primaryKeyColumn.endsWith("\"")) { + primaryKeyColumn = "\"" + primaryKeyColumn + "\""; + } + String primaryKeyValue = _primaryKeyValue; + if (primaryKeyValue != null && primaryKeyValue.startsWith("\"") + && primaryKeyValue.endsWith("\"")) { + primaryKeyValue = "\"" + primaryKeyValue + "\""; + } + + appendProperty(props, PROP_TABLE, table); + appendProperty(props, PROP_SEQUENCE_COL, sequenceColumn); + appendProperty(props, PROP_PK_COL, primaryKeyColumn); + appendProperty(props, PROP_PK_VALUE, primaryKeyValue); // Array of unique column names are passed to configuration // as a single string "x|y|z". The configurable (TableJDBCSeq) must // parse it back. diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaGenerator.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaGenerator.java index 1ef75518e..e1b53418d 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaGenerator.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaGenerator.java @@ -488,9 +488,17 @@ public class SchemaGenerator { Table table; String tableSchema; for (int i = 0; cols != null && i < cols.length; i++) { - tableName = cols[i].getTableName(); - tableSchema = StringUtils.trimToNull(cols[i].getSchemaName()); - + // TODO: Is this where we should handle this? + if (tableName == null || tableName.equals("%")) { + tableName = cols[i].getTableName(); + } + if (schemaName == null) { + tableSchema = StringUtils.trimToNull(cols[i].getSchemaName()); + } + else { + tableSchema = schemaName; + } + // ignore special tables if (!_openjpaTables && (tableName.toUpperCase().startsWith("OPENJPA_") diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaTool.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaTool.java index 05e81971d..8cdf00c6c 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaTool.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaTool.java @@ -470,6 +470,7 @@ public class SchemaTool { Table dbTable; Column[] cols; Column col; + String delim = _dict.getDelimiter(); for (int i = 0; i < schemas.length; i++) { tabs = schemas[i].getTables(); for (int j = 0; j < tabs.length; j++) { @@ -477,14 +478,22 @@ public class SchemaTool { dbTable = db.findTable(schemas[i], tabs[j].getFullName()); for (int k = 0; k < cols.length; k++) { if (dbTable != null) { - col = dbTable.getColumn(cols[k].getName()); + String colName = cols[k].getName(); + boolean delimCol = false; + if (colName.startsWith(delim) + && colName.endsWith(delim)) { + colName = colName.substring(1, colName.length()-1); + delimCol = true; + } + col = dbTable.getColumn(colName); if (col == null) { if (addColumn(cols[k])) dbTable.importColumn(cols[k]); else _log.warn(_loc.get("add-col", cols[k], tabs[j])); - } else if (!cols[k].equalsColumn(col)) { + // TODO: Find a way to compare these with delimCol + } else if (!delimCol && !cols[k].equalsColumn(col)) { _log.warn(_loc.get("bad-col", new Object[]{ col, dbTable, col.getDescription(), cols[k].getDescription() })); diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DB2Dictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DB2Dictionary.java index aa255362d..49f8b29c3 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DB2Dictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DB2Dictionary.java @@ -24,6 +24,7 @@ import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.sql.Types; import java.util.Arrays; +import java.util.EnumSet; import java.util.Set; import java.util.StringTokenizer; @@ -64,7 +65,7 @@ public class DB2Dictionary public static final int db2ZOSV8xOrLater = 3; public static final int db2UDBV82OrLater = 4; public static final int db2ISeriesV5R4OrLater = 5; - protected static final String forUpdate = "FOR UPDATE"; + protected static final String forUpdate = "FOR UPDATE"; protected static final String withURClause = "WITH UR"; protected static final String withCSClause = "WITH CS"; protected static final String withRSClause = "WITH RS"; @@ -82,6 +83,9 @@ public class DB2Dictionary protected int min = 0; private int defaultBatchLimit = 100; + + private EnumSet unsupportedDelimitedIds = + EnumSet.of(DBIdentifiers.COLUMN_COLUMN_DEFINITION); public DB2Dictionary() { platform = "DB2"; @@ -244,9 +248,9 @@ public class DB2Dictionary } public void connectedConfiguration(Connection conn) throws SQLException { - super.connectedConfiguration(conn); + super.connectedConfiguration(conn); - DatabaseMetaData metaData = conn.getMetaData(); + DatabaseMetaData metaData = conn.getMetaData(); String driverName = metaData.getDriverName(); if (driverName != null && driverName.startsWith("IBM DB2")) driverVendor = VENDOR_IBM; @@ -267,21 +271,21 @@ public class DB2Dictionary db2ServerType = db2ISeriesV5R4OrLater; } - if (db2ServerType == 0) { - if (isJDBC3) { - maj = metaData.getDatabaseMajorVersion(); - min = metaData.getDatabaseMinorVersion(); - } - else - getProductVersionMajorMinor(); + if (db2ServerType == 0) { + if (isJDBC3) { + maj = metaData.getDatabaseMajorVersion(); + min = metaData.getDatabaseMinorVersion(); + } + else + getProductVersionMajorMinor(); - // Determine the type of DB2 database for ZOS & UDB - if (isDB2UDBV81OrEarlier()) - db2ServerType = db2UDBV81OrEarlier; - else if (isDB2ZOSV8xOrLater()) - db2ServerType = db2ZOSV8xOrLater; - else if (isDB2UDBV82OrLater()) - db2ServerType = db2UDBV82OrLater; + // Determine the type of DB2 database for ZOS & UDB + if (isDB2UDBV81OrEarlier()) + db2ServerType = db2UDBV81OrEarlier; + else if (isDB2ZOSV8xOrLater()) + db2ServerType = db2ZOSV8xOrLater; + else if (isDB2UDBV82OrLater()) + db2ServerType = db2UDBV82OrLater; } // verify that database product is supported @@ -289,14 +293,14 @@ public class DB2Dictionary throw new UnsupportedException(_loc.get("db-not-supported", new Object[] {databaseProductName, databaseProductVersion })); - if (maj >= 9 || (maj == 8 && min >= 2)) { - supportsLockingWithMultipleTables = true; - supportsLockingWithInnerJoin = true; - supportsLockingWithOuterJoin = true; - forUpdateClause = "WITH RR USE AND KEEP UPDATE LOCKS"; - if (maj >=9) - supportsXMLColumn = true; - } + if (maj >= 9 || (maj == 8 && min >= 2)) { + supportsLockingWithMultipleTables = true; + supportsLockingWithInnerJoin = true; + supportsLockingWithOuterJoin = true; + forUpdateClause = "WITH RR USE AND KEEP UPDATE LOCKS"; + if (maj >=9) + supportsXMLColumn = true; + } // platform specific settings switch (db2ServerType) { @@ -843,10 +847,10 @@ public class DB2Dictionary } String nullSafe(String s) { - return s == null ? "" : s; + return s == null ? "" : s; } - @Override + @Override protected Boolean matchErrorState(int subtype, Set errorStates, SQLException ex) { Boolean recoverable = null; @@ -864,4 +868,17 @@ public class DB2Dictionary } return recoverable; } + + /** + * @return the unsupportedDelimitedIds + */ + @Override + protected EnumSet getUnsupportedDelimitedIds() { + return unsupportedDelimitedIds; + } + + @Override + protected void setDelimitedCase(DatabaseMetaData metaData) { + delimitedCase = SCHEMA_CASE_PRESERVE; + } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java index 2383c2f3b..319354883 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java @@ -53,6 +53,7 @@ import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -348,6 +349,36 @@ public class DBDictionary protected final Set systemTableSet = new HashSet(); protected final Set fixedSizeTypeNameSet = new HashSet(); protected final Set typeModifierSet = new HashSet(); + + private boolean delimitIds = false; + protected boolean supportsDelimitedIds = false; + protected String delimiter = "\""; + // Assume mixed case by default. + protected String delimitedCase = SCHEMA_CASE_PRESERVE; + + // TODO: complete the list + public static enum DBIdentifiers { + TABLE_NAME, + TABLE_SCHEMA, + TABLE_CATALOG, + SECONDARY_TABLE_NAME, + SECONDARY_TABLE_SCHEMA, + SECONDARY_TABLE_CATALOG, + TABLE_GEN_TABLE, + TABLE_GEN_SCHEMA, + TABLE_GEN_PK_COLUMN, + TABLE_GEN_VALUE_COLUMN, + COLUMN_NAME, + COLUMN_COLUMN_DEFINITION, + COLUMN_TABLE + } + + // TODO: describe; maybe make private + protected EnumSet unsupportedDelimitedIds = + EnumSet.noneOf(DBIdentifiers.class); + // TODO + // Should this be EnumSet.noneOf(....) + // or EnumSet.noneOf....? /** * If a native query begins with any of the values found here then it will @@ -418,6 +449,10 @@ public class DBDictionary conn.getAutoCommit(), conn.getHoldability(), conn.getTransactionIsolation()})); } + + // While we have the metaData, set some values from it + setSupportsDelimitedIds(metaData); + setDelimitedCase(metaData); } connected = true; } @@ -2972,7 +3007,18 @@ public class DBDictionary name = name.substring(1); String tableName = table.getName(); int len = Math.min(tableName.length(), 7); - name = "I_" + shorten(tableName, len) + "_" + name; + String shortTableName = shorten(tableName, len); + String delim = getDelimiter(); + if (shortTableName.startsWith(delim) + && shortTableName.endsWith(delim)) { + name = delim + "I_" + + shortTableName.substring(1, shortTableName.length() - 1) + + "_" + name + delim; + } + else { + name = "I_" + shortTableName + "_" + name; + } + return makeNameValid(name, table.getSchema().getSchemaGroup(), maxIndexNameLength, NAME_ANY); } @@ -3052,17 +3098,26 @@ public class DBDictionary * '1', etc. * Note that the given max len may be 0 if the database metadata is * incomplete. + * + * Note: If the name is delimited, make sure the ending delimiter is + * not stripped off. */ protected String makeNameValid(String name, NameSet set, int maxLen, int nameType, boolean checkForUniqueness) { + boolean delimited = false; + String delimiter = getDelimiter(); + if (name.startsWith(delimiter) && name.endsWith(delimiter)) { + delimited = true; + } if (maxLen < 1) maxLen = 255; if (name.length() > maxLen) - name = name.substring(0, maxLen); + name = removeEndingChars(name, name.length() - maxLen, + delimited, delimiter); if (reservedWordSet.contains(name.toUpperCase())) { if (name.length() == maxLen) - name = name.substring(0, name.length() - 1); - name += "0"; + name = removeEndingChars(name, 1, delimited, delimiter); + name = addCharsToEnd(name, "0", delimited, delimiter); } // now make sure the name is unique @@ -3088,16 +3143,65 @@ public class DBDictionary // a single char for the version is probably enough, but might // as well be general about it... if (version > 1) - name = name.substring(0, name.length() - chars); + name = removeEndingChars(name, chars, delimited, delimiter); if (version >= Math.pow(10, chars)) chars++; if (name.length() + chars > maxLen) - name = name.substring(0, maxLen - chars); - name = name + version; + name = removeEndingChars(name, + name.length() + chars - maxLen, + delimited, delimiter); + name = addCharsToEnd(name, new Integer(version).toString(), + delimited, delimiter); } } + + if (delimited) { + String delimCase = getDelimitedCase(); + if (delimCase.equals(SCHEMA_CASE_LOWER)) { + return name.toLowerCase(); + } + else if (delimCase.equals(SCHEMA_CASE_UPPER)) { + return name.toUpperCase(); + } + else { + return name; + } + } + // TODO: This is the original. Should the db supported case be checked? return name.toUpperCase(); } + + private String removeEndingChars(String name, + int charsToRemove, + boolean delimited, + String delimiter) { + if (delimited) { + name = name.substring(0, name.length() - delimiter.length()); + name = name.substring(0, name.length() - charsToRemove); + name = name + delimiter; + } + else { + name = name.substring(0, name.length() - charsToRemove); + } + + return name; + } + + private String addCharsToEnd(String name, + String charsToAdd, + boolean delimited, + String delimiter) { + if (delimited) { + name = name.substring(0, name.length() - delimiter.length()); + name = name + charsToAdd; + name = name + delimiter; + } + else { + name = name + charsToAdd; + } + + return name; + } /** * Return a series of SQL statements to create the given table, complete @@ -3997,6 +4101,25 @@ public class DBDictionary if (objectName == null) return null; + // Handle delimited string differently. Return unquoted name. + if (delimitIds || + objectName.startsWith(getDelimiter()) && + objectName.endsWith(getDelimiter())) { + String delimCase = getDelimitedCase(); + if (SCHEMA_CASE_UPPER.equals(delimCase)) { + objectName.toUpperCase(); + } + else if (SCHEMA_CASE_LOWER.equals(delimCase)) { + objectName.toLowerCase(); + } + + // TODO: maybe have a different method to remove quotes and + // call it from the calling methods + int delimLen = getDelimiter().length(); + return objectName.substring(delimLen, + objectName.length() - delimLen); + } + String scase = getSchemaCase(); if (SCHEMA_CASE_LOWER.equals(scase)) return objectName.toLowerCase(); @@ -4728,4 +4851,101 @@ public class DBDictionary length)); return name; } + + public String delimitString(String name, DBIdentifiers type) { + if (StringUtils.isEmpty(name)) { + return null; + } + + if (!getSupportsDelimitedIds()) { + // TODO: log (or maybe log in the method itself; so maybe + // merge with next if stmt + return name; + } + + if (!delimitIds) { + return name; + } + // TODO: merge with if stmt above (maybe not, may want to log this) + if (!supportsDelimitedId(type)) { + // TODO: log + return name; + } + String delimitedString = delimiter + name + delimiter; + return delimitedString; + } + + /** + * @return the unsupportedDelimitedIds + */ + protected EnumSet getUnsupportedDelimitedIds() { + return unsupportedDelimitedIds; + } + + protected boolean supportsDelimitedId(DBIdentifiers type) { + if (getUnsupportedDelimitedIds().contains(type)) { + return false; + } + return true; + } + + /** + * @return the delimiter + */ + public String getDelimiter() { + return delimiter; + } + + protected String getDelimitedCase() { + return delimitedCase; + } + + protected void setDelimitedCase(DatabaseMetaData metaData) { + try { + if (metaData.storesMixedCaseQuotedIdentifiers()) { + delimitedCase = SCHEMA_CASE_PRESERVE; + } + else if (metaData.storesUpperCaseQuotedIdentifiers()) { + delimitedCase = SCHEMA_CASE_UPPER; + } + else if (metaData.storesLowerCaseQuotedIdentifiers()) { + delimitedCase = SCHEMA_CASE_LOWER; + } + } catch (SQLException e) { + // TODO log this + } + } + + /** + * @return the supportsDelimitedIds + */ + public boolean getSupportsDelimitedIds() { + return supportsDelimitedIds; + } + + /** + * @param supportsDelimitedIds the supportsDelimitedIds to set + */ + public void setSupportsDelimitedIds(DatabaseMetaData metaData) { + try { + supportsDelimitedIds = + metaData.supportsMixedCaseQuotedIdentifiers(); + } catch (SQLException e) { + // TODO log this, or should we throw an exception? + } + } + + /** + * @return the delimitIds + */ + public boolean isDelimitIds() { + return delimitIds; + } + + /** + * @param delimitIds the delimitIds to set + */ + public void setDelimitIds(boolean delimitIds) { + this.delimitIds = delimitIds; + } } \ No newline at end of file diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DerbyDictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DerbyDictionary.java index 9bfdad4e9..f36f3dd2a 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DerbyDictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DerbyDictionary.java @@ -20,9 +20,8 @@ package org.apache.openjpa.jdbc.sql; import java.sql.DriverManager; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; +import java.util.EnumSet; import java.util.Set; import javax.sql.DataSource; @@ -40,6 +39,9 @@ public class DerbyDictionary * the DataSource. */ public boolean shutdownOnClose = true; + + private EnumSet unsupportedDelimitedIds = + EnumSet.of(DBIdentifiers.COLUMN_COLUMN_DEFINITION); public DerbyDictionary() { platform = "Apache Derby"; @@ -120,4 +122,13 @@ public class DerbyDictionary } return recoverable; } + + /** + * @return the unsupportedDelimitedIds + */ + @Override + protected EnumSet getUnsupportedDelimitedIds() { + return unsupportedDelimitedIds; + } + } diff --git a/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/AnnotationPersistenceMappingParser.java b/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/AnnotationPersistenceMappingParser.java index 77a07bbdf..cd0361553 100644 --- a/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/AnnotationPersistenceMappingParser.java +++ b/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/AnnotationPersistenceMappingParser.java @@ -115,6 +115,8 @@ public class AnnotationPersistenceMappingParser private static final Map _tags = new HashMap(); + + private DBDictionary _dict; static { _tags.put(AssociationOverride.class, ASSOC_OVERRIDE); @@ -199,6 +201,7 @@ public class AnnotationPersistenceMappingParser public AnnotationPersistenceMappingParser(JDBCConfiguration conf) { super(conf); + _dict = conf.getDBDictionaryInstance(); } @Override @@ -252,9 +255,17 @@ public class AnnotationPersistenceMappingParser meta = (SequenceMapping) getRepository().addSequenceMetaData(name); meta.setSequencePlugin(SequenceMapping.IMPL_VALUE_TABLE); - meta.setTable(toTableName(gen.schema(), gen.table())); - meta.setPrimaryKeyColumn(gen.pkColumnName()); - meta.setSequenceColumn(gen.valueColumnName()); + String schema = _dict.delimitString(gen.schema(), + DBDictionary.DBIdentifiers.TABLE_GEN_SCHEMA); + String table = _dict.delimitString(gen.table(), + DBDictionary.DBIdentifiers.TABLE_GEN_TABLE); + meta.setTable(toTableName(schema,table)); + String pkColumnName = _dict.delimitString(gen.pkColumnName(), + DBDictionary.DBIdentifiers.TABLE_GEN_PK_COLUMN); + meta.setPrimaryKeyColumn(pkColumnName); + String valueColumnName = _dict.delimitString(gen.valueColumnName(), + DBDictionary.DBIdentifiers.TABLE_GEN_VALUE_COLUMN); + meta.setSequenceColumn(valueColumnName); meta.setPrimaryKeyValue(gen.pkColumnValue()); meta.setInitialValue(gen.initialValue()); meta.setAllocate(gen.allocationSize()); @@ -494,13 +505,18 @@ public class AnnotationPersistenceMappingParser Log log = getLog(); String name; + String schema; List joins = null; for (SecondaryTable table : tables) { - name = table.name(); + name = _dict.delimitString(table.name(), + DBDictionary.DBIdentifiers.SECONDARY_TABLE_NAME) ; if (StringUtils.isEmpty(name)) throw new MetaDataException(_loc.get("second-name", cm)); - if (!StringUtils.isEmpty(table.schema())) - name = table.schema() + "." + name; + if (!StringUtils.isEmpty(table.schema())) { + schema = _dict.delimitString(table.schema(), + DBDictionary.DBIdentifiers.SECONDARY_TABLE_SCHEMA); + name = schema + "." + name; + } if (table.pkJoinColumns().length > 0) { joins = new ArrayList(table.pkJoinColumns().length); for (PrimaryKeyJoinColumn join : table.pkJoinColumns()) @@ -517,42 +533,46 @@ public class AnnotationPersistenceMappingParser * Set class table. */ private void parseTable(ClassMapping cm, Table table) { - String tableName = toTableName(table.schema(), table.name()); - if (tableName != null) - cm.getMappingInfo().setTableName(tableName); + String tableName = _dict.delimitString(table.name(), + DBDictionary.DBIdentifiers.TABLE_NAME); + String schemaName = _dict.delimitString(table.schema(), + DBDictionary.DBIdentifiers.TABLE_SCHEMA); + String fullTableName = toTableName(schemaName, tableName); + if (fullTableName != null) + cm.getMappingInfo().setTableName(fullTableName); - addUniqueConstraints(tableName, cm, cm.getMappingInfo(), + addUniqueConstraints(fullTableName, cm, cm.getMappingInfo(), table.uniqueConstraints()); } Unique createUniqueConstraint(MetaDataContext ctx, UniqueConstraint anno) { - String[] columnNames = anno.columnNames(); - if (columnNames == null || columnNames.length == 0) + String[] columnNames = anno.columnNames(); + if (columnNames == null || columnNames.length == 0) throw new UserException(_loc.get("unique-no-column", ctx)); - Unique uniqueConstraint = new Unique(); - for (int i=0; i i) - setupColumn((Column) cols.get(i), pcols[i]); + setupColumn((Column) cols.get(i), pcols[i], _dict); else { if (cols.isEmpty()) cols = new ArrayList(pcols.length); - cols.add(newColumn(pcols[i])); + cols.add(newColumn(pcols[i], _dict)); } if (xmlTypeClass != null @@ -1469,7 +1489,9 @@ public class AnnotationPersistenceMappingParser } unique |= (pcols[i].unique()) ? TRUE : FALSE; - secondary = trackSecondaryTable(fm, secondary, pcols[i].table(), i); + String secTable = _dict.delimitString(pcols[i].table(), + DBDictionary.DBIdentifiers.SECONDARY_TABLE_NAME); + secondary = trackSecondaryTable(fm, secondary, secTable, i); } if (fm.isElementCollection()) @@ -1483,20 +1505,24 @@ public class AnnotationPersistenceMappingParser /** * Create a new schema column with information from the given annotation. */ - private static Column newColumn(javax.persistence.Column anno) { + private static Column newColumn(javax.persistence.Column anno, + DBDictionary dict) { Column col = new Column(); - setupColumn(col, anno); + setupColumn(col, anno, dict); return col; } /** * Setup the given column with information from the given annotation. */ - private static void setupColumn(Column col, javax.persistence.Column anno) { + private static void setupColumn(Column col, javax.persistence.Column anno, + DBDictionary dict) { if (!StringUtils.isEmpty(anno.name())) - col.setName(anno.name()); + col.setName(dict.delimitString(anno.name(), + DBDictionary.DBIdentifiers.COLUMN_NAME)); if (!StringUtils.isEmpty(anno.columnDefinition())) - col.setTypeName(anno.columnDefinition()); + col.setTypeName(dict.delimitString(anno.columnDefinition(), + DBDictionary.DBIdentifiers.COLUMN_COLUMN_DEFINITION)); if (anno.precision() != 0) col.setSize(anno.precision()); else if (anno.length() != 255) diff --git a/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/PersistenceMappingDefaults.java b/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/PersistenceMappingDefaults.java index ac596d54e..dcd33c6b5 100644 --- a/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/PersistenceMappingDefaults.java +++ b/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/PersistenceMappingDefaults.java @@ -89,9 +89,9 @@ public class PersistenceMappingDefaults int nColumn = vers.getMappingInfo().getColumns().size(); switch (nColumn) { - case 0 : return NoneVersionStrategy.getInstance(); - case 1 : return new NumberVersionStrategy(); - default: return new MultiColumnVersionStrategy(); + case 0 : return NoneVersionStrategy.getInstance(); + case 1 : return new NumberVersionStrategy(); + default: return new MultiColumnVersionStrategy(); } } @@ -135,7 +135,17 @@ public class PersistenceMappingDefaults ClassMapping clm = fm.getDefiningMapping(); Table table = getTable(clm); - String name = table.getName() + "_"; + String name = table.getName(); + String delim = dict.getDelimiter(); + boolean isDelimited = false; + if (name.startsWith(delim) && name.endsWith(delim)) { + isDelimited = true; + name = name.substring(0,name.length()-1) + "_"; + } + else { + name = name + "_"; + } + // if this is an assocation table, spec says to suffix with table of // the related type. spec doesn't cover other cases; we're going to @@ -147,6 +157,11 @@ public class PersistenceMappingDefaults name += rel.getTable().getName(); else name += fm.getName(); + + if (isDelimited) { + name += "\""; + } + return name.replace('$', '_'); } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/delimited/identifiers/EntityF.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/delimited/identifiers/EntityF.java new file mode 100644 index 000000000..55f3d9f7a --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/delimited/identifiers/EntityF.java @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.persistence.delimited.identifiers; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.MapKeyColumn; +import javax.persistence.SecondaryTable; +import javax.persistence.Table; + +@Entity +@Table(name="\"primary entityF\"", schema="\"delim id\"") +@SecondaryTable(name="\"secondary entityF\"", schema="\"delim id\"") +//@Table(name="primary_entityF", schema="delim_id") +public class EntityF { + @Id + private int id; + @Column(name="\"f name\"") + private String name; + @Column(name="f_nonDelimName") + private String nonDelimName; + @Column(name="\"secondary name\"", table="\"secondary entityF\"") + private String secName; + + @ElementCollection + // CollectionTable with default name generation + @CollectionTable + private Set collectionSet = new HashSet(); + + @ElementCollection + @CollectionTable(name="\"collectionDelimSet\"", schema="\"delim id\"") + private Set collectionDelimSet = new HashSet(); + + @ElementCollection + @MapKeyColumn + private Map collectionMap = new HashMap(); + + @ElementCollection + @MapKeyColumn(name="\"mapKey\"") + private Map delimCollectionMap = new HashMap(); + + public EntityF(String name) { + this.name = name; + } + + public EntityF(int id, String name) { + this.id = id; + this.name = name; + } + + /** + * @return the id + */ + public int getId() { + return id; + } + /** + * @param id the id to set + */ + public void setId(int id) { + this.id = id; + } + /** + * @return the name + */ + public String getName() { + return name; + } + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the nonDelimName + */ + public String getNonDelimName() { + return nonDelimName; + } + + /** + * @param nonDelimName the nonDelimName to set + */ + public void setNonDelimName(String nonDelimName) { + this.nonDelimName = nonDelimName; + } + + /** + * @return the secName + */ + public String getSecName() { + return secName; + } + + /** + * @param secName the secName to set + */ + public void setSecName(String secName) { + this.secName = secName; + } + + /** + * @return the collectionSet + */ + public Set getCollectionSet() { + return collectionSet; + } + + /** + * @param collectionSet the collectionSet to set + */ + public void setCollectionSet(Set collectionSet) { + this.collectionSet = collectionSet; + } + + public void addCollectionSet(String item) { + collectionSet.add(item); + } + + /** + * @return the collectionNamedSet + */ + public Set getCollectionDelimSet() { + return collectionDelimSet; + } + + /** + * @param collectionNamedSet the collectionNamedSet to set + */ + public void setCollectionDelimSet(Set collectionDelimSet) { + this.collectionDelimSet = collectionDelimSet; + } + + public void addCollectionDelimSet(String item) { + this.collectionDelimSet.add(item); + } + + /** + * @return the collectionMap + */ + public Map getCollectionMap() { + return collectionMap; + } + + /** + * @param collectionMap the collectionMap to set + */ + public void setCollectionMap(Map collectionMap) { + this.collectionMap = collectionMap; + } + + public void addCollectionMap(String key, String value) { + collectionMap.put(key, value); + } + + /** + * @return the delimCollectionMap + */ + public Map getDelimCollectionMap() { + return delimCollectionMap; + } + + /** + * @param delimCollectionMap the delimCollectionMap to set + */ + public void setDelimCollectionMap(Map delimCollectionMap) { + this.delimCollectionMap = delimCollectionMap; + } + + public void addDelimCollectionMap(String key, String value) { + delimCollectionMap.put(key, value); + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/delimited/identifiers/TestManualDelimId.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/delimited/identifiers/TestManualDelimId.java new file mode 100644 index 000000000..cc18b7128 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/delimited/identifiers/TestManualDelimId.java @@ -0,0 +1,115 @@ +package org.apache.openjpa.persistence.delimited.identifiers; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; + +import org.apache.openjpa.jdbc.conf.JDBCConfiguration; +import org.apache.openjpa.jdbc.schema.Column; +import org.apache.openjpa.jdbc.sql.DBDictionary; +import org.apache.openjpa.persistence.OpenJPAEntityManager; +import org.apache.openjpa.persistence.test.SQLListenerTestCase; + +public class TestManualDelimId extends SQLListenerTestCase { + OpenJPAEntityManager em; + int id = 0; + EntityF entityF; + JDBCConfiguration conf; + DBDictionary dict; + + public void setUp() throws Exception { + // TODO: retest with DROP to figure out problem +// super.setUp(EntityF2.class,DROP_TABLES); + super.setUp( + org.apache.openjpa.persistence.delimited.identifiers.EntityF.class); + assertNotNull(emf); + + em = emf.createEntityManager(); + assertNotNull(em); + + conf = (JDBCConfiguration) emf.getConfiguration(); + dict = conf.getDBDictionaryInstance(); + } + + public void createEntityF(int id) { + entityF = new EntityF(id, "fName"); + entityF.setNonDelimName("fNonDelimName"); + entityF.setSecName("sec name"); + entityF.addCollectionSet("xxx"); + entityF.addCollectionSet("yyy"); + entityF.addCollectionDelimSet("aaa"); + entityF.addCollectionDelimSet("bbb"); + entityF.addCollectionMap("aaa", "xxx"); + entityF.addCollectionMap("bbb", "yyy"); + entityF.addDelimCollectionMap("www", "xxx"); + entityF.addDelimCollectionMap("yyy", "zzz"); + } + + // TODO: temp - test on multiple DBs +// public void testDBCapability() { +// Connection conn = (Connection)em.getConnection(); +// try { +// DatabaseMetaData meta = conn.getMetaData(); +// System.out.println("LC - " + meta.storesLowerCaseIdentifiers()); +// System.out.println("LCQ - " + meta.storesLowerCaseQuotedIdentifiers()); +// System.out.println("MC - " + meta.storesMixedCaseIdentifiers()); +// System.out.println("MCQ - " + meta.storesMixedCaseQuotedIdentifiers()); +// System.out.println("UC - " + meta.storesUpperCaseIdentifiers()); +// System.out.println("UCQ - " + meta.storesUpperCaseQuotedIdentifiers()); +// System.out.println(""); +// System.out.println("db product name - " + meta.getDatabaseProductName()); +// System.out.println("db product version - " + meta.getDatabaseProductVersion()); +// System.out.println("driver name - " + meta.getDriverName()); +// System.out.println("driver version - " + meta.getDriverVersion()); +// } catch (SQLException e) { +// e.printStackTrace(); +// } +// } + + public void testCreateF() { + id++; + createEntityF(id); + + em.getTransaction().begin(); + em.persist(entityF); + em.getTransaction().commit(); + + System.out.println(super.toString(sql)); + +// getColumnInfo("\"primary_entityF\"", "\"f_name\"", "\"delim_id\""); +// getColumnInfo("\"primary entityF\"", null, "\"delim id\""); +// getColumnInfo("\"secondary entityF\"", null, "\"delim id\""); + } + + // TODO: change to boolean return and remove assert +// private void getColumnInfo(String tableName, String columnName, String schemaName) { +// Connection conn = (Connection)em.getConnection(); +// try { +// DatabaseMetaData meta = conn.getMetaData(); +// // tableName = "\"" + tableName + "\""; +// Column[] columns = dict.getColumns(meta, conn.getCatalog(), schemaName, tableName, columnName, conn); +// System.out.println("columns.length - " + columns.length); +// +//// assertEquals(1, columns.length); +// +// for (Column column : columns) { +// System.out.println("column name - " + column.getName()); +// System.out.println("column fullName - " + column.getFullName()); +// System.out.println("column schemaName - " + column.getSchemaName()); +// System.out.println("column tableName - " + column.getTableName()); +// System.out.println("column description - " + column.getDescription()); +// } +// } catch (SQLException e) { +// e.printStackTrace(); +// } +// finally { +// try { +// conn.commit(); +// conn.close(); +// } catch (SQLException e) { +// e.printStackTrace(); +// fail("problem closing connection"); +// } +// } +// } +}