blob: 8c9923093e927b92f2e0b7391bd3b8f522372dd4 [file] [log] [blame]
/*
* 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.jdbc.identifier;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.identifier.DBIdentifier.DBIdentifierType;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.NameSet;
import org.apache.openjpa.jdbc.schema.Schema;
import org.apache.openjpa.jdbc.schema.SchemaGroup;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.lib.conf.Configurable;
import org.apache.openjpa.lib.conf.Configuration;
import org.apache.openjpa.lib.identifier.IdentifierConfiguration;
import org.apache.openjpa.lib.identifier.IdentifierRule;
import org.apache.openjpa.lib.identifier.IdentifierUtil;
import org.apache.openjpa.lib.identifier.IdentifierUtilImpl;
import org.apache.openjpa.lib.util.StringUtil;
public class DBIdentifierUtilImpl extends IdentifierUtilImpl implements DBIdentifierUtil,
Configurable {
private JDBCConfiguration _conf = null;
public DBIdentifierUtilImpl() {
}
public DBIdentifierUtilImpl(IdentifierConfiguration config) {
super(config);
}
@Override
public DBIdentifier getValidColumnIdentifier(DBIdentifier name, Table table,
int maxLen, boolean checkForUniqueness) {
if (DBIdentifier.isNull(name)) {
return name;
}
String rule = name.getType().name();
maxLen = getMaxLen(rule, name, maxLen);
DBIdentifier sName = DBIdentifier.removeLeading(name, IdentifierUtil.UNDERSCORE);
return makeIdentifierValid(sName, table, maxLen, checkForUniqueness);
}
@Override
public DBIdentifier getValidForeignKeyIdentifier(DBIdentifier name, Table table, Table toTable, int maxLen) {
if (DBIdentifier.isNull(name)) {
return name;
}
String rule = name.getType().name();
maxLen = getMaxLen(rule, name, maxLen);
DBIdentifier sName = makeName(rule, name, table, "F");
return makeIdentifierValid(sName, table.getSchema().getSchemaGroup(),
maxLen, true);
}
@Override
public DBIdentifier getValidUniqueIdentifier(DBIdentifier name, Table table, int maxLen) {
if (DBIdentifier.isNull(name)) {
return name;
}
String rule = name.getType().name();
maxLen = getMaxLen(rule, name, maxLen);
DBIdentifier sName = makeName(rule, name, table, "U");
return makeIdentifierValid(sName, table.getSchema().getSchemaGroup(),
maxLen, true);
}
@Override
public DBIdentifier getValidIndexIdentifier(DBIdentifier name, Table table, int maxLen) {
if (DBIdentifier.isNull(name)) {
return name;
}
String rule = name.getType().name();
maxLen = getMaxLen(rule, name, maxLen);
DBIdentifier sName = makeName(rule, name, table, "I");
return makeIdentifierValid(sName, table.getSchema().getSchemaGroup(),
maxLen, true);
}
@Override
public DBIdentifier getValidSequenceIdentifier(DBIdentifier name, Schema schema, int maxLen) {
if (DBIdentifier.isNull(name)) {
return name;
}
String rule = name.getType().name();
maxLen = getMaxLen(rule, name, maxLen);
DBIdentifier sName = makeName(rule, name, "S");
return makeIdentifierValid(sName, schema.getSchemaGroup(), maxLen, true);
}
@Override
public DBIdentifier getValidTableIdentifier(DBIdentifier name, Schema schema, int maxLen) {
if (DBIdentifier.isNull(name)) {
return name;
}
String rule = name.getType().name();
maxLen = getMaxLen(rule, name, maxLen);
DBIdentifier sName = makeName(rule, name, null);
return makeIdentifierValid(sName, schema.getSchemaGroup(),
maxLen, true);
}
@Override
public DBIdentifier makeNameValid(String name, NameSet set, int maxLen,
int nameType, boolean checkForUniqueness) {
DBIdentifierType id = DBIdentifierType.DEFAULT;
switch (nameType) {
case DBIdentifierUtil.TABLE:
id = DBIdentifierType.TABLE;
break;
case DBIdentifierUtil.SEQUENCE:
id = DBIdentifierType.SEQUENCE;
break;
case DBIdentifierUtil.COLUMN:
id = DBIdentifierType.COLUMN;
break;
}
return makeIdentifierValid(DBIdentifier.newIdentifier(name, id), set, maxLen, checkForUniqueness);
}
@Override
public DBIdentifier makeIdentifierValid(DBIdentifier sname, NameSet set, int maxLen,
boolean checkForUniqueness) {
DBIdentifier validName = sname;
String rule = sname.getType().name();
maxLen = getMaxLen(rule, validName, maxLen);
int nameLen = validName.getName().length();
if (nameLen > maxLen) {
validName = DBIdentifier.truncate(validName, nameLen - maxLen);
nameLen = validName.getName().length();
}
if (isReservedWord(rule, validName.getName())) {
if (nameLen == maxLen)
validName = DBIdentifier.truncate(validName, 1);
validName = DBIdentifier.append(validName, "0");
nameLen = validName.getName().length();
}
// now make sure the name is unique
if (set != null && checkForUniqueness) {
for (int version = 1, chars = 1; true; version++) {
// for table names, we check for the table itself in case the
// name set is lazy about schema reflection
if (validName.getType() == DBIdentifierType.TABLE) {
if (!((SchemaGroup) set).isKnownTable(QualifiedDBIdentifier.getPath(validName)))
break;
} else if (validName.getType() == DBIdentifierType.SEQUENCE) {
if (!((SchemaGroup) set).isKnownSequence(QualifiedDBIdentifier.getPath(validName)))
break;
} else {
if (!set.isNameTaken(validName))
break;
}
// a single char for the version is probably enough, but might
// as well be general about it...
if (version > 1) {
validName = DBIdentifier.truncate(validName, chars);
}
if (version >= Math.pow(10, chars))
chars++;
if (nameLen + chars > maxLen) {
validName = DBIdentifier.truncate(validName, nameLen + chars - maxLen);
}
validName = DBIdentifier.append(validName, Integer.toString(version));
nameLen = validName.getName().length();
}
}
if (validName.isDelimited()) {
String delimCase = getIdentifierConfiguration().getDelimitedCase();
if (delimCase.equals(CASE_LOWER)) {
return DBIdentifier.toLower(validName,true);
}
else if (delimCase.equals(CASE_UPPER)) {
return DBIdentifier.toUpper(validName,true);
}
else {
return validName;
}
}
return DBIdentifier.toUpper(validName);
}
/**
* Converts the name to a name which can be used within a SQL statement. Uses
* the appropriate delimiters and separators.
* @parm name a DBIdentifier
*/
@Override
public String toDBName(DBIdentifier name) {
return toDBName(name, true);
}
/**
* Converts the name to a name which can be used within a SQL statement. Uses
* the appropriate delimiters and separators.
* @parm name a DBIdentifier
* @param delimit If true, allows the name to be delimited, if necessary.
* Otherwise, the identifier is not delimited.
*/
@Override
public String toDBName(DBIdentifier name, boolean delimit) {
if (DBIdentifier.isNull(name)) {
return null;
}
if (getIdentifierConfiguration().getSupportsDelimitedIdentifiers() && delimit
&& getIdentifierConfiguration().delimitAll() && !name.isDelimited()) {
return delimit(name, true);
}
String rule = name.getType().name();
if (name instanceof QualifiedDBIdentifier) {
QualifiedDBIdentifier path = (QualifiedDBIdentifier)name;
return convertFull(Normalizer.getNamingConfiguration(), rule, path.getName());
}
return convert(Normalizer.getNamingConfiguration(), rule, name.getName());
}
/**
* Converts the identifier to a format appropriate for the configuration.
* Delimits if necessary
*/
@Override
public String toDBName(String name) {
return toDBName(name, true);
}
/**
* Converts the identifier to a format appropriate for the configuration using
* the default naming rule.
* @param delimit If false, do not delimit. Otherwise, delimit if necessary.
*/
@Override
public String toDBName(String name, boolean delimit) {
return toDBName(getIdentifierConfiguration().getDefaultIdentifierRule().getName(), name, delimit);
}
/**
* Converts the identifier to a format appropriate for the configuration using
* the specified naming rule.
* @param delimit If false, do not delimit. Otherwise, delimit if necessary.
*/
private String toDBName(String rule, String name, boolean delimit) {
if (name == null) {
return null;
}
if (getIdentifierConfiguration().getSupportsDelimitedIdentifiers() && delimit &&
getIdentifierConfiguration().delimitAll() && !Normalizer.isDelimited(name)) {
return delimit(rule, name, true);
}
return convert(Normalizer.getNamingConfiguration(), rule, name);
}
/**
* Creates a new identifier of a given type based upon the name returned
* from the database.
*/
@Override
public DBIdentifier fromDBName(String name, DBIdentifierType id) {
if (name == null) {
return DBIdentifier.NULL;
}
if (!getIdentifierConfiguration().getSupportsDelimitedIdentifiers()) {
return DBIdentifier.newIdentifier(name, id);
}
String delimCase = getIdentifierConfiguration().getDelimitedCase();
String nonDelimCase = getIdentifierConfiguration().getSchemaCase();
String caseName = name;
// If delimited and non-delimited case are the same, don't change
// case or try to determine whether delimiting is required. Let the
// normalizer figure it out using standard rules.
if (delimCase.equals(nonDelimCase)) {
return DBIdentifier.newIdentifier(name, id, false, false, !delimCase.equals(CASE_PRESERVE));
}
// Otherwise, try to determine whether to delimit based on an expected vs.
// actual name comparison.
if (delimCase.equals(CASE_PRESERVE)) {
if (nonDelimCase.equals(CASE_LOWER)) {
caseName = name.toLowerCase();
} else {
caseName = name.toUpperCase();
}
} else if (delimCase.equals(CASE_LOWER)) {
if (nonDelimCase.equals(CASE_UPPER)) {
caseName = name.toUpperCase();
}
} else if (delimCase.equals(CASE_UPPER)) {
if (nonDelimCase.equals(CASE_LOWER)) {
caseName = name.toLowerCase();
}
}
boolean delimit = !caseName.equals(name) || getIdentifierConfiguration().delimitAll();
return DBIdentifier.newIdentifier((delimit ? name : caseName), id, false, delimit,
!delimCase.equals(CASE_PRESERVE));
}
@Override
public DBIdentifier append(DBIdentifierType resultId, DBIdentifier...names) {
if (names == null || names.length == 0) {
return DBIdentifier.NULL;
}
DBIdentifier sName = DBIdentifier.newIdentifier("", resultId);
for (DBIdentifier name : names) {
DBIdentifier.append(sName, name.getName());
}
return sName;
}
@Override
public String appendColumns(Column[] columns) {
if (columns == null || columns.length == 0) {
return "";
}
if (columns.length == 1) {
return toDBName(columns[0].getIdentifier());
}
StringBuilder colsb = new StringBuilder("");
for (int i = 0; i < columns.length; i++) {
colsb.append(toDBName(columns[i].getIdentifier()));
if (i < (columns.length - 1)) {
colsb.append(", ");
}
}
return colsb.toString();
}
public String delimit(DBIdentifier name, boolean force) {
String rule = name.getType().name();
// If this is a compound path, each item must be delimited
// separately
if (name instanceof QualifiedDBIdentifier) {
QualifiedDBIdentifier path = (QualifiedDBIdentifier)name;
// Make sure this is a qualified path before delimiting
// separately
if (!((path.getType() == DBIdentifierType.COLUMN &&
path.isUnqualifiedColumn()) ||
(path.getType() != DBIdentifierType.COLUMN &&
path.isUnqualifiedObject()))) {
DBIdentifier[] names = QualifiedDBIdentifier.splitPath(name);
for (DBIdentifier dbIdentifier : names) {
DBIdentifier sName = dbIdentifier.getUnqualifiedName();
if (!sName.isDelimited()) {
String pRule = sName.getType().name();
dbIdentifier.setName(delimit(pRule, sName.getName(), force));
}
}
return QualifiedDBIdentifier.newPath(names).getName();
}
}
return delimit(rule, name.getName(), force);
}
public String shorten(String name, int targetLength) {
return DBDictionary.shorten(name, targetLength);
}
@Override
public DBIdentifier getGeneratedKeySequenceName(Column col, int maxLen) {
DBIdentifier tname = col.getTableIdentifier();
DBIdentifier cname = col.getIdentifier();
int max = maxLen;
int extraChars = -max + tname.getName().length() + 1 // <tname> + '_'
+ cname.getName().length() + 4; // <cname> + '_SEQ'
String tsname = tname.getName();
if (extraChars > 0) {
// this assumes that tname is longer than extraChars
tsname = tsname.substring(0, tsname.length() - extraChars);
}
return DBIdentifier.combine(DBIdentifierType.SEQUENCE, tsname, cname.getName(), "SEQ");
}
/**
* Convert the specified schema name to a name that the database will
* be able to understand in metadata operations.
*/
@Override
public DBIdentifier convertSchemaCase(DBIdentifier name) {
if (DBIdentifier.isNull(name))
return DBIdentifier.NULL;
DBIdentifier sName = name.clone();
// Handle delimited string differently. Return unquoted name.
String delimCase = getIdentifierConfiguration().getDelimitedCase();
if (/* getNamingConfiguration().delimitAll() || */ name.isDelimited()) {
if (CASE_UPPER.equals(delimCase)) {
sName = DBIdentifier.toUpper(sName,true);
}
else if (CASE_LOWER.equals(delimCase)) {
sName = DBIdentifier.toLower(sName,true);
}
return DBIdentifier.removeDelimiters(sName);
}
if (!getIdentifierConfiguration().delimitAll()) {
// Not delimited, use the base schema case expected by the DB
String schemaCase = getIdentifierConfiguration().getSchemaCase();
if (CASE_LOWER.equals(schemaCase))
return DBIdentifier.toLower(sName);
if (CASE_PRESERVE.equals(schemaCase))
return sName;
return DBIdentifier.toUpper(sName);
}
return sName;
}
/**
* Converts a column alias to use the appropriate delimiters
*/
@Override
public String convertAlias(String alias) {
if (!needsConversion(getIdentifierConfiguration())) {
return alias;
}
String[] names = Normalizer.splitName(alias);
if (names.length <= 1) {
// Nothing to split
return alias;
}
// Skip the the first name. It is the alias (T0, T1, etc.)
for (int i = 1; i < names.length; i++) {
names[i] = toDBName(getIdentifierConfiguration().getDefaultIdentifierRule().toString(), names[i], true);
}
return joinNames(getIdentifierConfiguration().getDefaultIdentifierRule(), names);
}
private DBIdentifier makeName(String rule, DBIdentifier name, Table tbl, String prefix) {
DBIdentifier sName = DBIdentifier.removeLeading(name, IdentifierUtil.UNDERSCORE);
String tableName = tbl.getIdentifier().getName();
int len = Math.min(tableName.length(), 7);
// Combine the names using the normalized configuration.
String str = combineNames(Normalizer.getNamingConfiguration(), rule,
new String[] { prefix == null ? "" : prefix,
shorten(tableName, len), sName.getName() });
sName.setName(str);
return sName;
}
private DBIdentifier makeName(String rule, DBIdentifier name, String prefix) {
DBIdentifier sName = DBIdentifier.removeLeading(name, IdentifierUtil.UNDERSCORE);
if (!StringUtil.isEmpty(prefix)) {
sName = DBIdentifier.preCombine(sName, prefix);
}
return sName;
}
private int getMaxLen(String rule, DBIdentifier name, int maxLen) {
IdentifierConfiguration config = getIdentifierConfiguration();
if (maxLen < 1) {
IdentifierRule nrule = config.getIdentifierRule(rule);
maxLen = nrule.getMaxLength();
}
// Subtract delimiter length if name is delimited or will be delimited
if (config.delimitAll() || name.isDelimited()) {
maxLen = maxLen - (config.getLeadingDelimiter().length() + config.getTrailingDelimiter().length());
}
return maxLen;
}
/**
* System configuration.
*/
public JDBCConfiguration getConfiguration() {
return _conf;
}
@Override
public void setConfiguration(Configuration conf) {
_conf = (JDBCConfiguration)conf;
}
}