diff --git a/Frameworks/PlugIns/MySQLPlugIn/Sources/com/webobjects/jdbcadaptor/MySQLExpression.java b/Frameworks/PlugIns/MySQLPlugIn/Sources/com/webobjects/jdbcadaptor/MySQLExpression.java
new file mode 100644
index 00000000000..924b29838fc
--- /dev/null
+++ b/Frameworks/PlugIns/MySQLPlugIn/Sources/com/webobjects/jdbcadaptor/MySQLExpression.java
@@ -0,0 +1,341 @@
+/**
+ *
+ */
+package com.webobjects.jdbcadaptor;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import com.webobjects.eoaccess.EOAttribute;
+import com.webobjects.eoaccess.EOEntity;
+import com.webobjects.eocontrol.EOKeyComparisonQualifier;
+import com.webobjects.eocontrol.EOKeyValueQualifier;
+import com.webobjects.eocontrol.EOQualifier;
+import com.webobjects.eocontrol.EOQualifierVariable;
+import com.webobjects.eocontrol.EOSortOrdering;
+import com.webobjects.foundation.NSSelector;
+import com.webobjects.foundation._NSStringUtilities;
+
+/**
+ *
+ * Overrides the default EOF MySQLExpression to adjust the sql generation of
+ * string comparisons.
+ *
+ *
+ * MySQL's default behaviour, as per usual, is to ignore the standards. Thus an
+ * sql like
performs a case insensitive comparison rather than case
+ * sensitive which is vexing.
+ *
+ *
+ * To enforce standard behaviour you can tell mysql by using
+ * like binary
for a case sensitive comparison. Thus a case
+ * insensitive comparison simply requires using a like
in order to
+ * obtain the results desired.
+ *
+ *
+ * Note: This assumes that you're not using binary columns.
+ *
+ *
+ * The alternative is to define all of your columns as binary columns and
+ * specifically specify the collation to use for case insensitive comparisons.
+ * But that's a more complex approach in the author's view.
+ *
+ *
+ * Another approach is to only every use qualifiers and sort orderings that are
+ * case sensitive (semantically) and choose in your model what external type to
+ * map to in order to control the behaviour. In my view this is bad practice
+ * because you're separating the logic of queries and returning results that are
+ * not intended according to the code.
+ *
+ *
+ * To have this class enabled as the runtime MySQLExpression define the
+ * following property.
+ *
+ *
+ * com.webobjects.jdbcadaptor.MySQLExpression.enable=true
+ *
+ *
+ * To summarise:
+ *
+ *
+ * - LIKE UPPER(foo) becomes LIKE foo
+ *
- LIKE foo becomes LIKE BINARY foo
+ *
+ *
+ * @author ldeck
+ */
+public class MySQLExpression
+ extends
+ com.webobjects.jdbcadaptor.MySQLPlugIn.MySQLExpression
+{
+
+ private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger( MySQLExpression.class );
+ private static final List< NSSelector > SORT_ORDERING_ASC_SELECTORS =
+ Arrays.asList( EOSortOrdering.CompareAscending, EOSortOrdering.CompareCaseInsensitiveAscending );
+ private static final List< NSSelector > SORT_ORDERING_BIN_SELECTORS = Arrays.asList( EOSortOrdering.CompareAscending, EOSortOrdering.CompareDescending );
+ private static final List< NSSelector > SORT_ORDERING_DESC_SELECTORS =
+ Arrays.asList( EOSortOrdering.CompareCaseInsensitiveDescending, EOSortOrdering.CompareDescending );
+
+ private final Pattern likeOperatorRegex;
+ private final Pattern upperFunctionNameRegex;
+
+ /**
+ * @param entity
+ */
+ public MySQLExpression( EOEntity entity )
+ {
+ super( entity );
+ this.upperFunctionNameRegex = Pattern.compile( "\\Q" + this._upperFunctionName + "\\E\\(([^\\)]+)\\)" );
+ this.likeOperatorRegex = Pattern.compile( "([Ll][Ii][Kk][Ee])" );
+ }
+
+ /**
+ * @see com.webobjects.eoaccess.EOSQLExpression#addOrderByAttributeOrdering(com.webobjects.eocontrol.EOSortOrdering)
+ */
+ @Override
+ public void addOrderByAttributeOrdering( EOSortOrdering sortOrdering )
+ {
+ NSSelector< ? > selector = sortOrdering.selector();
+ String attPath = sortOrdering.key();
+ String sqlString1 = sqlStringForAttributeNamed( attPath );
+ if ( sqlString1 == null )
+ {
+ throw new IllegalStateException( new StringBuilder()
+ .append( "addOrderByAttributeOrdering: attempt to generate SQL for " )
+ .append( sortOrdering.getClass().getName() )
+ .append( " " )
+ .append( sortOrdering )
+ .append( " failed because attribute identified by key '" )
+ .append( sortOrdering.key() )
+ .append( "' was not reachable from from entity '" )
+ .append( this._entity.name() )
+ .append( "'" )
+ .toString() );
+ }
+ String format;
+ boolean isAscending = false;
+ if ( ( isAscending = SORT_ORDERING_ASC_SELECTORS.contains( selector ) ) || SORT_ORDERING_DESC_SELECTORS.contains( selector ) )
+ {
+ String orderType = isAscending ? "ASC" : "DESC";
+ String binaryOperator =
+ SORT_ORDERING_BIN_SELECTORS.contains( selector )
+ && entity()._attributeForPath( attPath ).adaptorValueType() == EOAttribute.AdaptorCharactersType ? "BINARY " : "";
+
+ format = _NSStringUtilities.concat( binaryOperator, " ", sqlString1, " ", orderType );
+ }
+ else
+ {
+ format = _NSStringUtilities.concat( "(", sqlString1, ")" );
+ }
+ appendItemToListString( format, _orderByString() );
+ }
+
+ /**
+ * @see com.webobjects.jdbcadaptor.JDBCExpression#appendItemToOrderByString(java.lang.String)
+ */
+ @Override
+ protected void appendItemToOrderByString( String sqlString )
+ {
+ super.appendItemToOrderByString( replaceStringForCaseInsensitiveLike( sqlString ) );
+ }
+
+ protected Pattern likeOperatorRegex()
+ {
+ return this.likeOperatorRegex;
+ }
+
+ protected String replaceStringForCaseInsensitiveLike( String string )
+ {
+ StringBuffer result = new StringBuffer();
+
+ Matcher matcher = upperFunctionNameRegex().matcher( string );
+ while ( matcher.find() )
+ {
+ matcher.appendReplacement( result, Matcher.quoteReplacement( matcher.group( 1 ) ) );
+ }
+ matcher.appendTail( result );
+
+ return result.toString();
+ }
+
+ protected String replaceStringForCaseSensitiveLike( String string )
+ {
+ StringBuffer result = new StringBuffer();
+
+ Matcher matcher = likeOperatorRegex().matcher( string );
+ while ( matcher.find() )
+ {
+ matcher.appendReplacement( result, Matcher.quoteReplacement( matcher.group( 1 ) + " BINARY" ) );
+ }
+ matcher.appendTail( result );
+
+ return result.toString();
+ }
+
+ /**
+ * @see com.webobjects.eoaccess.EOSQLExpression#sqlStringForCaseInsensitiveLike(java.lang.String,
+ * java.lang.String)
+ */
+ @Override
+ public String sqlStringForCaseInsensitiveLike( String valueString, String keyString )
+ {
+ return replaceStringForCaseInsensitiveLike( super.sqlStringForCaseInsensitiveLike( valueString, keyString ) );
+ }
+
+ /**
+ * @see com.webobjects.eoaccess.EOSQLExpression#sqlStringForKeyComparisonQualifier(com.webobjects.eocontrol.EOKeyComparisonQualifier)
+ */
+ @Override
+ public String sqlStringForKeyComparisonQualifier( EOKeyComparisonQualifier qualifier )
+ {
+ String leftKey = qualifier.leftKey();
+ String rightKey = qualifier.rightKey();
+ if ( leftKey != null && leftKey.equals( rightKey ) )
+ {
+ return "(1=1)";
+ }
+ EOAttribute att = this._entity._attributeForPath( leftKey );
+ String leftKeyString = sqlStringForAttributeNamed( leftKey );
+ if ( leftKeyString == null )
+ {
+ throw new IllegalStateException( new StringBuilder()
+ .append( "sqlStringForKeyComparisonQualifier: attempt to generate SQL for " )
+ .append( qualifier.getClass().getName() )
+ .append( " " )
+ .append( qualifier )
+ .append( " failed because attribute identified by key '" )
+ .append( leftKey )
+ .append( "' was not reachable from from entity '" )
+ .append( this._entity.name() )
+ .append( "'" )
+ .toString() );
+ }
+ leftKeyString = formatSQLString( leftKeyString, att.readFormat() );
+ att = this._entity._attributeForPath( rightKey );
+ String rightKeyString = sqlStringForAttributeNamed( rightKey );
+ if ( rightKeyString == null )
+ {
+ throw new IllegalStateException( new StringBuilder()
+ .append( "sqlStringForKeyComparisonQualifier: attempt to generate SQL for " )
+ .append( qualifier.getClass().getName() )
+ .append( " " )
+ .append( qualifier )
+ .append( " failed because attribute identified by key '" )
+ .append( rightKey )
+ .append( "' was not reachable from from entity '" )
+ .append( this._entity.name() )
+ .append( "'" )
+ .toString() );
+ }
+ else
+ {
+ rightKeyString = formatSQLString( rightKeyString, att.readFormat() );
+ String operatorString = sqlStringForSelector( qualifier.selector(), null );
+
+ EOAttribute leftAttribute = this._entity._attributeForPath( leftKey );
+ EOAttribute rightAttribute = this._entity._attributeForPath( rightKey );
+
+ NSSelector qualifierSelector = qualifier.selector();
+ boolean isLike =
+ qualifierSelector.equals( EOQualifier.QualifierOperatorLike ) || qualifierSelector.equals( EOQualifier.QualifierOperatorCaseInsensitiveLike );
+ boolean isStringComparison =
+ isLike
+ || EOAttribute.AdaptorCharactersType == leftAttribute.adaptorValueType()
+ || EOAttribute.AdaptorCharactersType == rightAttribute.adaptorValueType();
+
+ String binaryOperator = isStringComparison ? "BINARY " : "";
+
+ return _NSStringUtilities.concat( binaryOperator, leftKeyString, " ", operatorString, " ", rightKeyString );
+ }
+ }
+
+ /**
+ * @see com.webobjects.eoaccess.EOSQLExpression#sqlStringForKeyValueQualifier(com.webobjects.eocontrol.EOKeyValueQualifier)
+ */
+ @Override
+ public String sqlStringForKeyValueQualifier( EOKeyValueQualifier qualifier )
+ {
+ String key = qualifier.key();
+ String keyString = sqlStringForAttributeNamed( key );
+ if ( keyString == null )
+ {
+ throw new IllegalStateException( new StringBuilder()
+ .append( "sqlStringForKeyValueQualifier: attempt to generate SQL for " )
+ .append( qualifier.getClass().getName() )
+ .append( " " )
+ .append( qualifier )
+ .append( " failed because attribute identified by key '" )
+ .append( key )
+ .append( "' was not reachable from from entity '" )
+ .append( this._entity.name() )
+ .append( "'" )
+ .toString() );
+ }
+ Object qualifierValue = qualifier.value();
+ if ( qualifierValue instanceof EOQualifierVariable )
+ {
+ throw new IllegalStateException( new StringBuilder()
+ .append( "sqlStringForKeyValueQualifier: attempt to generate SQL for " )
+ .append( qualifier.getClass().getName() )
+ .append( " " )
+ .append( qualifier )
+ .append( " failed because the qualifier variable '$" )
+ .append( ( ( EOQualifierVariable )qualifierValue ).key() )
+ .append( "' is unbound." )
+ .toString() );
+ }
+
+ EOAttribute attribute = this._entity._attributeForPath( key );
+
+ keyString = formatSQLString( keyString, attribute.readFormat() );
+ NSSelector qualifierSelector = qualifier.selector();
+ boolean isLike =
+ qualifierSelector.equals( EOQualifier.QualifierOperatorLike ) || qualifierSelector.equals( EOQualifier.QualifierOperatorCaseInsensitiveLike );
+ boolean isStringComparison = isLike || EOAttribute.AdaptorCharactersType == attribute.adaptorValueType();
+
+ Object value;
+ if ( isLike )
+ {
+ value = sqlPatternFromShellPattern( ( String )qualifierValue );
+ }
+ else
+ {
+ value = qualifierValue;
+ }
+ String string;
+ if ( qualifierSelector.equals( EOQualifier.QualifierOperatorCaseInsensitiveLike ) )
+ {
+ String valueString = sqlStringForValue( value, key );
+ String operatorString = sqlStringForSelector( qualifierSelector, value );
+ string = sqlStringForCaseInsensitiveLike( valueString, keyString );
+ }
+ else if ( EOQualifier.QualifierOperatorLike.equals( qualifierSelector ) || isStringComparison )
+ {
+ String valueString = sqlStringForValue( value, key );
+ String operatorString = sqlStringForSelector( qualifierSelector, value );
+ string = _NSStringUtilities.concat( "BINARY ", keyString, " ", operatorString, " ", valueString );
+ }
+ else
+ {
+ String valueString = sqlStringForValue( value, key );
+ String operatorString = sqlStringForSelector( qualifierSelector, value );
+ string = _NSStringUtilities.concat( keyString, " ", operatorString, " ", valueString );
+ }
+ if ( isLike )
+ {
+ char escapeChar = sqlEscapeChar();
+ if ( escapeChar != 0 )
+ {
+ string = _NSStringUtilities.concat( string, new StringBuilder().append( " ESCAPE '" ).append( escapeChar ).append( "'" ).toString() );
+ }
+ }
+ return string;
+ }
+
+ protected Pattern upperFunctionNameRegex()
+ {
+ return this.upperFunctionNameRegex;
+ }
+
+}
diff --git a/Frameworks/PlugIns/MySQLPlugIn/Sources/com/webobjects/jdbcadaptor/_MySQLPlugIn.java b/Frameworks/PlugIns/MySQLPlugIn/Sources/com/webobjects/jdbcadaptor/_MySQLPlugIn.java
index 651de8ae20b..0e333e1c3a9 100644
--- a/Frameworks/PlugIns/MySQLPlugIn/Sources/com/webobjects/jdbcadaptor/_MySQLPlugIn.java
+++ b/Frameworks/PlugIns/MySQLPlugIn/Sources/com/webobjects/jdbcadaptor/_MySQLPlugIn.java
@@ -25,6 +25,7 @@
import com.webobjects.foundation.NSLog;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
+import com.webobjects.foundation.NSProperties;
import com.webobjects.foundation.NSPropertyListSerialization;
import com.webobjects.foundation._NSStringUtilities;
@@ -312,6 +313,9 @@ public String databaseProductName() {
@Override
public Class defaultExpressionClass() {
+ if (NSProperties.booleanForKeyWithDefault("com.webobjects.jdbcadaptor.MySQLExpression.enable", false)) {
+ return com.webobjects.jdbcadaptor.MySQLExpression.class;
+ }
return com.webobjects.jdbcadaptor._MySQLPlugIn.MySQLExpression.class;
}