package com.webobjects.jdbcadaptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.Enumeration;
import com.webobjects.eoaccess.EOAttribute;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOJoin;
import com.webobjects.eoaccess.EOModel;
import com.webobjects.eoaccess.EORelationship;
import com.webobjects.eocontrol.EOFetchSpecification;
import com.webobjects.eocontrol.EOQualifier;
import com.webobjects.eocontrol.EOSortOrdering;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSData;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSForwardException;
import com.webobjects.foundation.NSKeyValueCoding;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSTimestamp;
import com.webobjects.foundation.NSTimestampFormatter;
import com.webobjects.foundation._NSStringUtilities;
/**
* DB2 use SQL92 join statements. Has a FETCH FIRST n ROWS statement for limit
*
* @author simpson: Took the postgress plugin and made it into a db2 plugin
* @author ak: Regex, NSData
* @author Giorgio Valoti: refactoring, typecasting, schema building
* @author Arturo Perez: JOIN clauses
* @author David Teran: Timestamps handling
* @author Tim Cummings: case sensitive table and column names
* @author cug: hacks for identifier quoting while creating tables
*/
public class DB2Expression extends JDBCExpression {
/**
* formatter to use when handling date columns
*/
protected static final NSTimestampFormatter DATE_FORMATTER = new NSTimestampFormatter("%Y-%m-%d");
/**
* formatter to use when handling timestamps
*/
protected static final NSTimestampFormatter TIMESTAMP_FORMATTER = new NSTimestampFormatter("%Y-%m-%d %H:%M:%S.%F");
/**
* Method to get the string value from a BigDecimals from.
*/
protected static Method _bigDecimalToString = null;
/**
* If true, queries will be created by using
*/
private Boolean _disableBindVariables = null;
/**
* Holds array of join clauses.
*/
protected NSMutableArray<JoinClause> _alreadyJoined = new NSMutableArray<>();
/**
* Fetch spec limit ivar
*/
protected int _fetchLimit;
private Boolean _enableIdentifierQuoting;
private Boolean _enableBooleanQuoting;
private Boolean _useLowercaseForCaseInsensitiveLike;
/**
* Overridden to remove the rtrim usage. The original implementation
* will remove every trailing space from character based column which
* should not be OK for DB2.
* @param entity entity for this expression
*/
public DB2Expression(EOEntity entity) {
super(entity);
if (useLowercaseForCaseInsensitiveLike()) {
_upperFunctionName = "LOWER";
}
}
/**
* Checks the system property
* <code>com.webobjects.jdbcadaptor.DB2Expression.enableBooleanQuoting</code>
* to enable or disable quoting (default) of boolean items.
*
*/
private boolean enableBooleanQuoting() {
if(_enableBooleanQuoting == null) {
_enableBooleanQuoting = Boolean.getBoolean(getClass().getName() + ".enableBooleanQuoting") ? Boolean.TRUE : Boolean.FALSE;
}
return _enableBooleanQuoting.booleanValue();
}
/**
* Checks the system property
* <code>com.webobjects.jdbcadaptor.DB2Expression.enableIdentifierQuoting</code>
* to enable or disable quoting (default) of schema names, table names and
* field names. Required if names which are case sensitive or reserved words
* or have special characters.
*
*/
private boolean enableIdentifierQuoting() {
if(_enableIdentifierQuoting == null) {
_enableIdentifierQuoting = Boolean.getBoolean(getClass().getName() + ".enableIdentifierQuoting") ? Boolean.TRUE : Boolean.FALSE;
}
return _enableIdentifierQuoting.booleanValue();
}
/**
* Overridden to fix an issue with NStimestamp classes and "T" value-typed attributes.
*/
@Override
public NSMutableDictionary<String, Object> bindVariableDictionaryForAttribute(EOAttribute eoattribute, Object obj) {
NSMutableDictionary<String, Object> result = super.bindVariableDictionaryForAttribute(eoattribute, obj);
if((obj instanceof NSTimestamp) && (isTimestampAttribute(eoattribute))) {
NSTimestamp nstimestamp = (NSTimestamp)obj;
long millis = nstimestamp.getTime();
// AK: since NSTimestamp places fractional millis in the getTime,
// the driver is getting very confused and refuses to update the columns as
// they get translated to 0 as the fractional values.
Timestamp timestamp = new Timestamp(millis);
timestamp.setNanos(timestamp.getNanos()+nstimestamp.getNanos());
result.setObjectForKey(timestamp, "BindVariableValue");
}
return result;
}
/**
* Overridden to not call the super implementation.
*
* @param leftName the table name on the left side of the clause
* @param rightName the table name on the right side of the clause
* @param semantic the join semantic
*/
@Override
public void addJoinClause(String leftName,
String rightName,
int semantic) {
assembleJoinClause(leftName, rightName, semantic);
}
/**
* Overridden to construct a valid SQL92 JOIN clause as opposed to
* the Oracle-like SQL the superclass produces.
*
* @param leftName the table name on the left side of the clause
* @param rightName the table name on the right side of the clause
* @param semantic the join semantic
* @return the join clause
*/
@Override
public String assembleJoinClause(String leftName,
String rightName,
int semantic) {
if (!useAliases()) {
return super.assembleJoinClause(leftName, rightName, semantic);
}
String leftAlias = leftName.substring(0, leftName.indexOf("."));
String rightAlias = rightName.substring(0, rightName.indexOf("."));
NSArray<String> k;
EOEntity rightEntity;
EOEntity leftEntity;
String relationshipKey = null;
EORelationship r;
if (leftAlias.equals("t0")) {
leftEntity = entity();
} else {
k = aliasesByRelationshipPath().allKeysForObject(leftAlias);
relationshipKey = k.count()>0? (String)k.lastObject() : "";
leftEntity = entityForKeyPath(relationshipKey);
}
if (rightAlias.equals("t0")) {
rightEntity = entity();
} else {
k = aliasesByRelationshipPath().allKeysForObject(rightAlias);
relationshipKey = k.count()>0? (String)k.lastObject() : "";
rightEntity = entityForKeyPath(relationshipKey);
}
int dotIndex = relationshipKey.indexOf( "." );
relationshipKey = dotIndex == -1
? relationshipKey
: relationshipKey.substring( relationshipKey.lastIndexOf( "." ) + 1 );
r = rightEntity.anyRelationshipNamed( relationshipKey );
// fix from Michael Müller for the case Foo.fooBars.bar has a Bar.foo relationship (instead of Bar.foos)
if( r == null || r.destinationEntity() != leftEntity ) {
r = leftEntity.anyRelationshipNamed( relationshipKey );
}
//timc 2006-02-26 IMPORTANT or quotes are ignored and mixed case field names won't work
String rightTable;
String leftTable;
if(enableIdentifierQuoting()) {
rightTable = rightEntity.valueForSQLExpression(this);
leftTable = leftEntity.valueForSQLExpression(this);
} else {
rightTable = rightEntity.externalName();
leftTable = leftEntity.externalName();
}
JoinClause jc = new JoinClause();
jc.setTable1(leftTable, leftAlias);
switch (semantic) {
case EORelationship.LeftOuterJoin:
jc.op = " LEFT OUTER JOIN ";
break;
case EORelationship.RightOuterJoin:
jc.op = " RIGHT OUTER JOIN ";
break;
case EORelationship.FullOuterJoin:
jc.op = " FULL OUTER JOIN ";
break;
case EORelationship.InnerJoin:
jc.op = " INNER JOIN ";
break;
}
jc.table2 = rightTable + " " + rightAlias;
NSArray<EOJoin> joins = r.joins();
int joinsCount = joins.count();
NSMutableArray<String> joinStrings = new NSMutableArray<>(joinsCount);
for( int i = 0; i < joinsCount; i++ ) {
EOJoin currentJoin = joins.objectAtIndex(i);
String left;
String right;
if(enableIdentifierQuoting()) {
left = leftAlias +"."+ sqlStringForSchemaObjectName(currentJoin.sourceAttribute().columnName());
right = rightAlias +"."+ sqlStringForSchemaObjectName(currentJoin.destinationAttribute().columnName());
} else {
left = leftAlias +"."+currentJoin.sourceAttribute().columnName();
right = rightAlias +"."+currentJoin.destinationAttribute().columnName();
}
joinStrings.addObject( left + " = " + right);
}
jc.joinCondition = " ON " + joinStrings.componentsJoinedByString( " AND " );
if( !_alreadyJoined.containsObject( jc ) ) {
_alreadyJoined.insertObjectAtIndex(jc, 0);
return jc.toString();
}
return null;
}
/**
* Overridden to handle correct placements of join conditions and
* to handle DISTINCT fetches with compareCaseInsensitiveA(De)scending sort orders.
* Change from Postgres to use Fetch First rows only rather then limit
*
* @param attributes the attributes to select
* @param lock flag for locking rows in the database
* @param qualifier the qualifier to restrict the selection
* @param fetchOrder specifies the fetch order
* @param columnList the SQL columns to be fetched
* @param tableList the the SQL tables to be fetched
* @param whereClause the SQL where clause
* @param joinClause the SQL join clause
* @param orderByClause the SQL sort order clause
* @param lockClause the SQL lock clause
* @return the select statement
*/
@Override
public String assembleSelectStatementWithAttributes(NSArray attributes,
boolean lock,
EOQualifier qualifier,
NSArray fetchOrder,
String selectString,
String columnList,
String tableList,
String whereClause,
String joinClause,
String orderByClause,
String lockClause) {
StringBuilder sb = new StringBuilder();
sb.append(selectString);
sb.append(columnList);
// AK: using DISTINCT with ORDER BY UPPER(foo) is an error if it is not also present in the columns list...
// This implementation sucks, but should be good enough for the normal case
// JVS: Just a note that any column in the order by clause with distinct needs to be in the column list
if(selectString.indexOf(" DISTINCT") != -1) {
String [] columns = orderByClause.split(",");
for(int i = 0; i < columns.length; i++) {
String column = columns[i].replaceFirst("\\s+(ASC|DESC)\\s*", "");
if(columnList.indexOf(column) == -1) {
sb.append(", ");
sb.append(column);
}
}
}
sb.append(" FROM ");
String fieldString;
if (_alreadyJoined.count() > 0) {
fieldString = joinClauseString();
} else {
fieldString = tableList;
}
sb.append(fieldString);
if ((whereClause != null && whereClause.length() > 0) ||
(joinClause != null && joinClause.length() > 0)) {
sb.append(" WHERE ");
if (joinClause != null && joinClause.length() > 0) {
sb.append(joinClause);
if (whereClause != null && whereClause.length() > 0)
sb.append(" AND ");
}
if (whereClause != null && whereClause.length() > 0) {
sb.append(whereClause);
}
}
if (orderByClause != null && orderByClause.length() > 0) {
sb.append(" ORDER BY ");
sb.append(orderByClause);
}
if (lockClause != null && lockClause.length() > 0) {
sb.append(' ');
sb.append(lockClause);
}
if (_fetchLimit != 0) {
sb.append(" FETCH FIRST ");
sb.append(_fetchLimit);
sb.append(" ROWS ONLY ");
}
return sb.toString();
}
/**
* Utility that traverses a key path to find the last destination entity
*
* @param keyPath the key path
* @return the entity at the end of the keypath
*/
private EOEntity entityForKeyPath(String keyPath) {
NSArray keys = NSArray.componentsSeparatedByString(keyPath, ".");
EOEntity ent = entity();
for (int i = 0; i < keys.count(); i++) {
String k = (String)keys.objectAtIndex(i);
EORelationship rel = ent.anyRelationshipNamed(k);
if (rel == null) {
// it may be an attribute
if (ent.anyAttributeNamed(k) != null) {
break;
}
throw new IllegalArgumentException("relationship " + keyPath + " generated null");
}
ent = rel.destinationEntity();
}
return ent;
}
/**
* Overridden because the original version throws when the
* data contains negative byte values.
*
* @param obj the object used in the SQL statement
* @param eoattribute the attribute associated with <code>obj</code>
* @return the formatted string
*/
@Override
public String formatValueForAttribute(Object obj, EOAttribute eoattribute) {
String value;
if(obj instanceof NSData) {
value = sqlStringForData((NSData)obj);
} else if((obj instanceof NSTimestamp) && isTimestampAttribute(eoattribute)) {
value = "'" + TIMESTAMP_FORMATTER.format(obj) + "'";
} else if((obj instanceof NSTimestamp) && isDateAttribute(eoattribute)) {
value = "'" + DATE_FORMATTER.format(obj) + "'";
} else if(obj instanceof String) {
value = formatStringValue((String)obj);
} else if(obj instanceof Number) {
if(obj instanceof BigDecimal) {
value = fixBigDecimal((BigDecimal) obj, eoattribute);
} else {
Object convertedObj = eoattribute.adaptorValueByConvertingAttributeValue(obj);
if (convertedObj instanceof Number) {
String valueType = eoattribute.valueType();
if (valueType == null || "i".equals(valueType)) {
value = String.valueOf(((Number)convertedObj).intValue());
}
else if ("l".equals(valueType)) {
value = String.valueOf(((Number)convertedObj).longValue());
}
else if ("f".equals(valueType)) {
value = String.valueOf(((Number)convertedObj).floatValue());
}
else if ("d".equals(valueType)) {
value = String.valueOf(((Number)convertedObj).doubleValue());
}
else if ("s".equals(valueType)) {
value = String.valueOf(((Number)convertedObj).shortValue());
}
else if ("c".equals(valueType)) {
return String.valueOf(((Number)convertedObj).intValue());
}
else {
throw new IllegalArgumentException("Unknown number value type '" + valueType + "' for attribute " + eoattribute.entity().name() + "." + eoattribute.name() + ".");
//value = convertedObj.toString();
}
}
else if (convertedObj instanceof String) {
String str = (String)convertedObj;
String valueType = eoattribute.valueType();
if (valueType == null || "i".equals(valueType)) {
return String.valueOf(Integer.parseInt(str));
}
else if ("l".equals(valueType)) {
return String.valueOf(Long.parseLong(str));
}
else if ("f".equals(valueType)) {
return String.valueOf(Float.parseFloat(str));
}
else if ("d".equals(valueType)) {
return String.valueOf(Double.parseDouble(str));
}
else if ("s".equals(valueType)) {
return String.valueOf(Short.parseShort(str));
}
else if ("c".equals(valueType)) {
return String.valueOf(Integer.parseInt(str));
}
else {
throw new IllegalArgumentException("Unknown number value type '" + valueType + "' for attribute " + eoattribute.entity().name() + "." + eoattribute.name() + ".");
}
}
else {
throw new IllegalArgumentException("Unknown number value '" + obj + "' for attribute " + eoattribute.entity().name() + "." + eoattribute.name() + ".");
}
}
} else if(obj instanceof Boolean) {
// GN: when booleans are stored as strings in the db, we need the values quoted
if (enableBooleanQuoting() || "S".equals(eoattribute.valueType())) {
value = "'" + ((Boolean)obj).toString() + "'";
}
else if (!"bool".equals(eoattribute.externalType().toLowerCase()) && "NSNumber".equals(eoattribute.valueClassName()) || "java.lang.Number".equals(eoattribute.valueClassName()) || "Number".equals(eoattribute.valueClassName())) {
value = ((Boolean)obj).booleanValue() ? "1" : "0";
}
else {
value = ((Boolean)obj).toString();
}
} else if(obj instanceof Timestamp) {
value = "'" + ((Timestamp)obj).toString() + "'";
} else if (obj == null || obj == NSKeyValueCoding.NullValue) {
value = "NULL";
} else {
// AK: I don't really like this, but we might want to prevent infinite recursion
try {
Object adaptorValue = eoattribute.adaptorValueByConvertingAttributeValue(obj);
if(adaptorValue instanceof NSData || adaptorValue instanceof NSTimestamp
|| adaptorValue instanceof String || adaptorValue instanceof Number
|| adaptorValue instanceof Boolean) {
value = formatValueForAttribute(adaptorValue, eoattribute);
} else {
throw new IllegalArgumentException(getClass().getName() + ": Can't convert: " + obj + ":" + obj.getClass() + " -> " + adaptorValue + ":" +adaptorValue.getClass());
}
} catch(Exception ex) {
throw new IllegalArgumentException(getClass().getName() + ": Exception while converting " + obj.getClass().getName(), ex);
}
}
return value;
}
/**
* Fixes an incompatibility with JDK 1.5 and using toString() instead of toPlainString() for BigDecimals.
* From what I understand, you will only need this if you disable bind variables.
* @param value
* @param eoattribute
* @author ak
*/
private String fixBigDecimal(BigDecimal value, EOAttribute eoattribute) {
String result;
if(System.getProperty("java.version").compareTo("1.5") >= 0) {
try {
if(_bigDecimalToString == null) {
_bigDecimalToString = BigDecimal.class.getMethod("toPlainString", (Class[])null);
}
result = (String) _bigDecimalToString.invoke(value, (Object[])null);
} catch (IllegalArgumentException e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
} catch (IllegalAccessException e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
} catch (InvocationTargetException e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
} catch (SecurityException e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
} catch (NoSuchMethodException e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
}
} else {
result = value.toString();
}
return result;
}
/**
* Helper to check for timestamp columns that have a "D" value type.
* @param eoattribute
*/
private boolean isDateAttribute(EOAttribute eoattribute) {
return "D".equals(eoattribute.valueType());
}
/**
* Helper to check for timestamp columns that have a "T" value type.
* @param eoattribute
*/
private boolean isTimestampAttribute(EOAttribute eoattribute) {
return "T".equals(eoattribute.valueType());
}
/**
* Helper to check for data columns that are not keys.
* @param eoattribute
*/
private boolean isDataAttribute(EOAttribute attribute) {
return (attribute.className().equals("com.webobjects.foundation.NSData") ||
attribute.externalType().equals("bytea") ||
attribute.externalType().equals("bit"))
&& entity().classProperties().containsObject(attribute);
}
/**
* Overrides the parent implementation to compose the final string
* expression for the join clauses.
*/
@Override
public String joinClauseString() {
NSMutableDictionary<String, Boolean> seenIt = new NSMutableDictionary<>();
StringBuilder sb = new StringBuilder();
JoinClause jc;
EOSortOrdering.sortArrayUsingKeyOrderArray
( _alreadyJoined, new NSArray<>( EOSortOrdering.sortOrderingWithKey( "sortKey", EOSortOrdering.CompareCaseInsensitiveAscending ) ) );
if (_alreadyJoined.count() > 0) {
jc = _alreadyJoined.objectAtIndex(0);
sb.append(jc);
seenIt.setObjectForKey(Boolean.TRUE, jc.table1);
seenIt.setObjectForKey(Boolean.TRUE, jc.table2);
}
for (int i = 1; i < _alreadyJoined.count(); i++) {
jc = _alreadyJoined.objectAtIndex(i);
sb.append(jc.op);
if (seenIt.objectForKey(jc.table1) == null) {
sb.append(jc.table1);
seenIt.setObjectForKey(Boolean.TRUE, jc.table1);
}
else if (seenIt.objectForKey(jc.table2) == null) {
sb.append(jc.table2);
seenIt.setObjectForKey(Boolean.TRUE, jc.table2);
}
sb.append(jc.joinCondition);
}
return sb.toString();
}
/**
*
*
* cug: Also handles identifier quoting now
*
* @param relationship the relationship
* @param sourceColumns the source columns for the constraints
* @param destinationColumns the destination columns for the constraints
*/
@Override
public void prepareConstraintStatementForRelationship(EORelationship relationship, NSArray sourceColumns, NSArray destinationColumns) {
EOEntity entity = relationship.entity();
String tableName = entity.externalName();
int lastDot = tableName.lastIndexOf('.');
if (lastDot >= 0) {
tableName = tableName.substring(lastDot + 1);
}
String constraintName = _NSStringUtilities.concat(tableName, "_", relationship.name(), "_FK");
// quotes the identifier in the array
String sourceKeyList = quoteArrayContents(sourceColumns).componentsJoinedByString(", ");
String destinationKeyList = quoteArrayContents(destinationColumns).componentsJoinedByString(", ");
EOModel sourceModel = entity.model();
EOModel destModel = relationship.destinationEntity().model();
if (sourceModel != destModel && !sourceModel.connectionDictionary().equals(destModel.connectionDictionary())) {
throw new IllegalArgumentException(new StringBuilder().append("prepareConstraintStatementForRelationship unable to create a constraint for ").append(relationship.name()).append(" because the source and destination entities reside in different databases").toString());
}
setStatement(new StringBuilder()
.append("ALTER TABLE ")
.append(sqlStringForSchemaObjectName(tableName))
.append(" ADD CONSTRAINT ")
.append(quoteIdentifier(constraintName))
.append(" FOREIGN KEY (")
.append(sourceKeyList)
.append(") REFERENCES ")
.append(sqlStringForSchemaObjectName(relationship.destinationEntity().externalName()))
.append(" (")
.append(destinationKeyList)
.append(')')
.toString());
}
/**
* Takes an array of strings and quotes every single string, if set to do so
*
* @param a - array of strings
*
* @return array of quoted or unquoted strings, depends on enableIdentifierQuoting
*/
private NSArray<String> quoteArrayContents(NSArray<String> a) {
Enumeration enumeration = a.objectEnumerator();
NSMutableArray<String> result = new NSMutableArray<>();
while (enumeration.hasMoreElements()) {
String identifier = (String) enumeration.nextElement();
String quotedString = quoteIdentifier(identifier);
result.addObject(quotedString);
}
return result;
}
/**
* Quotes the string if necessary (checks the Property)
*
* @param identifier - the string to quote
*
* @return quoted or unquoted string (check with enableIdentifierQuoting)
*/
private String quoteIdentifier(String identifier) {
return externalNameQuoteCharacter() + identifier + externalNameQuoteCharacter();
}
/**
* Overridden so we can get the fetch limit from the fetchSpec.
*
* @param attributes the array of attributes
* @param lock locking flag
* @param eofetchspecification the fetch specification
*/
@Override
public void prepareSelectExpressionWithAttributes(NSArray<EOAttribute> attributes, boolean lock, EOFetchSpecification eofetchspecification) {
if(!eofetchspecification.promptsAfterFetchLimit()) {
_fetchLimit = eofetchspecification.fetchLimit();
}
super.prepareSelectExpressionWithAttributes(attributes, lock, eofetchspecification);
}
protected boolean shouldAllowNull(EOAttribute attribute) {
boolean shouldAllowNull = attribute.allowsNull();
// If you allow nulls, then there's never a problem ...
if (!shouldAllowNull) {
EOEntity entity = attribute.entity();
EOEntity parentEntity = entity.parentEntity();
String externalName = entity.externalName();
if (externalName != null) {
// If you have a parent entity and that parent entity shares your table name, then you're single table inheritance
boolean singleTableInheritance = (parentEntity != null && externalName.equals(parentEntity.externalName()));
if (singleTableInheritance) {
EOAttribute parentAttribute = parentEntity.attributeNamed(attribute.name());
if (parentAttribute == null) {
// If this attribute is new in the subclass, you have to allow nulls
shouldAllowNull = true;
}
}
}
}
return shouldAllowNull;
}
@Override
public void addCreateClauseForAttribute(EOAttribute attribute) {
NSDictionary userInfo = attribute.userInfo();
Object defaultValue = null;
if (userInfo != null) {
defaultValue = userInfo.valueForKey("er.extensions.eoattribute.default");
}
String allowsNullClauseForConstraint = allowsNullClauseForConstraint(shouldAllowNull(attribute));
String sql;
if (defaultValue == null) {
sql = _NSStringUtilities.concat(quoteIdentifier(attribute.columnName()), " ", columnTypeStringForAttribute(attribute), " ", allowsNullClauseForConstraint);
}
else {
sql = _NSStringUtilities.concat(quoteIdentifier(attribute.columnName()), " ", columnTypeStringForAttribute(attribute), " DEFAULT ", formatValueForAttribute(defaultValue, attribute), " ", allowsNullClauseForConstraint);
}
appendItemToListString(sql, _listString());
}
/**
* cug: Quick hack for bug in WebObjects 5.4 where the "not null" statement is added without a space,
* and "addCreateClauseForAttribute" is not called anymore. Will probably change.
*/
@Override
public String allowsNullClauseForConstraint(boolean allowsNull) {
if(allowsNull)
return "";
Object value = jdbcInfo().objectForKey("NON_NULLABLE_COLUMNS");
if(value != null && value.equals("T"))
return " NOT NULL";
return "";
}
/**
* Overridden because the original version does not correctly quote mixed case fields in all cases.
* SELECT statements were OK (useAliases is true) INSERT, UPDATE, DELETE didn't quote mixed case field names.
*
* @param attribute the attribute (column name) to be converted to a SQL string
* @return SQL string for the attribute
*/
@Override
public String sqlStringForAttribute(EOAttribute attribute) {
String sql = null;
if ( attribute.isDerived() || useAliases() || attribute.columnName() == null || !enableIdentifierQuoting()) {
sql = super.sqlStringForAttribute(attribute);
} else {
sql = sqlStringForSchemaObjectName(attribute.columnName());
}
//NSLog.out.appendln("PostgresqlExpression.sqlStringForAttribute " + attribute.columnName() + ", isDerived() = " + attribute.isDerived() + ", useAliases() = " + useAliases() + ", sql = " + sql);
return sql;
}
/**
* Overridden because the original version does not correctly quote mixed case table names in all cases.
* SELECT statements were OK (useAliases is true) INSERT, UPDATE, DELETE didn't quote mixed case field names.
*
* @return the SQL string for the table names
*/
@Override
public String tableListWithRootEntity(EOEntity entity) {
String sql = null;
if ( useAliases()) {
sql = super.tableListWithRootEntity(entity);
} else {
sql = entity.valueForSQLExpression(this);
}
//NSLog.out.appendln("PostgresqlExpression.tableListWithRootEntity " + entity.externalName() + ", useAliases() = " + useAliases() + ", sql = " + sql);
return sql;
}
/**
* Helper class that stores a join definition and
* helps <code>DB2Expression</code> to assemble
* the correct join clause.
*/
public static class JoinClause {
String table1;
String op;
String table2;
String joinCondition;
String sortKey;
@Override
public String toString() {
return table1 + op + table2 + joinCondition;
}
@Override
public boolean equals(Object obj) {
if( obj == null || !(obj instanceof JoinClause) ) {
return false;
}
return toString().equals( obj.toString() );
}
public void setTable1(String leftTable, String leftAlias) {
table1 = leftTable + " " + leftAlias;
sortKey = leftAlias.substring(1);
if (sortKey.length() < 2) {
// add padding for cases with >9 joins
sortKey = " " + sortKey;
}
}
/**
* Property that makes this class "sortable".
* Needed to correctly assemble a join clause.
*/
public String sortKey() {
return sortKey;
}
}
/**
* Checks the system property <code>com.webobjects.jdbcadaptor.DB2Expression.disableBindVariables</code> to enable
* or disable bind variables in general.
*/
private boolean disableBindVariables() {
if (_disableBindVariables == null) {
_disableBindVariables = Boolean.getBoolean("com.webobjects.jdbcadaptor.DB2Expression.disableBindVariables") ? Boolean.TRUE : Boolean.FALSE;
}
return _disableBindVariables.booleanValue();
}
/**
* Overridden to return the negated value of <code>disableBindVariables</code>.
*/
@Override
public boolean useBindVariables() {
return !disableBindVariables();
}
/**
* Overridden to set the <code>disableBindVariables</code> value correctly.
* @param value new value
*/
@Override
public void setUseBindVariables(boolean value) {
_disableBindVariables = (value ? Boolean.FALSE : Boolean.TRUE);
}
/**
* Overridden to return true only if bind variables are enabled or the is a data type.
*/
@Override
public boolean shouldUseBindVariableForAttribute(EOAttribute attribute) {
return useBindVariables() || isDataAttribute(attribute);
}
/**
* Overridden to return true only if bind variables are enabled or the is a data type.
*/
@Override
public boolean mustUseBindVariableForAttribute(EOAttribute attribute) {
return useBindVariables() || isDataAttribute(attribute);
}
/**
* Replaces a given string by another string in a string.
* @param old string to be replaced
* @param newString to be inserted
* @param buffer string to have the replacement done on it
* @return string after having all of the replacement done.
*/
public static String replaceStringByStringInString(String old, String newString, String buffer) {
int begin, end;
int oldLength = old.length();
int length = buffer.length();
StringBuilder convertedString = new StringBuilder(length + 100);
begin = 0;
while(begin < length)
{
end = buffer.indexOf(old, begin);
if(end == -1)
{
convertedString.append(buffer.substring(begin));
break;
}
if(end == 0)
convertedString.append(newString);
else {
convertedString.append(buffer.substring(begin, end));
convertedString.append(newString);
}
begin = end+oldLength;
}
return convertedString.toString();
}
/**
* Checks the system property
* <code>com.webobjects.jdbcadaptor.DB2Expression.useLowercaseForCaseInsensitiveLike</code>
* to use the "lower" function for caseInsensitive compares
*/
private boolean useLowercaseForCaseInsensitiveLike() {
if (_useLowercaseForCaseInsensitiveLike == null) {
_useLowercaseForCaseInsensitiveLike = Boolean.getBoolean(getClass().getName() + ".useLowercaseForCaseInsensitiveLike") ? Boolean.TRUE : Boolean.FALSE;
}
return _useLowercaseForCaseInsensitiveLike.booleanValue();
}
}