package com.webobjects.jdbcadaptor;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.FieldPosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.Properties;
import com.webobjects.eoaccess.EOAdaptor;
import com.webobjects.eoaccess.EOAdaptorContext;
import com.webobjects.eoaccess.EOAttribute;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOGeneralAdaptorException;
import com.webobjects.eoaccess.EOJoin;
import com.webobjects.eoaccess.EORelationship;
import com.webobjects.eoaccess.EOSQLExpression;
import com.webobjects.eoaccess.EOSchemaSynchronization;
import com.webobjects.eoaccess.EOSynchronizationFactory;
import com.webobjects.eocontrol.EOAndQualifier;
import com.webobjects.eocontrol.EOKeyValueQualifier;
import com.webobjects.eocontrol.EOQualifier;
import com.webobjects.eocontrol.EOQualifierVariable;
import com.webobjects.eocontrol.EOSortOrdering;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSData;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSKeyValueCoding;
import com.webobjects.foundation.NSLog;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSPropertyListSerialization;
import com.webobjects.foundation.NSRange;
import com.webobjects.foundation.NSSelector;
import com.webobjects.foundation.NSTimestamp;
import com.webobjects.foundation._NSUtilities;
/**
* <span class="en">
* This is the wo5 java runtime plugin for FrontBase.
* </span>
*
* <span class="ja">
* FrontBase の WO5 Java ランタイム・プラグイン
* </span>
*
* @author Cail Borrell
*/
public class _FrontBasePlugIn extends JDBCPlugIn {
private static final String QUERY_STRING_USE_BUNDLED_JDBC_INFO = "useBundledJdbcInfo";
static final boolean USE_NAMED_CONSTRAINTS = true;
static final String _frontbaseIncludeSynonyms = System.getProperty("jdbcadaptor.frontbase.includeSynonyms", null);
static final String _frontbaseWildcardPatternForAttributes = System.getProperty("jdbcadaptor.frontbase.wildcardPatternForAttributes", null);
static final String _frontbaseWildcardPatternForTables = System.getProperty("jdbcadaptor.frontbase.wildcardPatternForTables", "%");
static final String _frontbaseWildcardPatternForSchema = System.getProperty("jdbcadaptor.frontbase.wildcardPatternForSchema", null);
static final String _frontbaseSqlStatementForGettingProcedureNames = System.getProperty("jdbcadaptor.frontbase.sqlStatementForGettingProcedureNames", null);
static final String _frontbaseStoredProcedureCatalogPattern = System.getProperty("jdbcadaptor.frontbase.storedProcedureCatalogPattern", null);
static final String _frontbaseStoredProcedureSchemaPattern = System.getProperty("jdbcadaptor.frontbase.storedProcedureSchemaPattern", null);
static final String _frontbaseSqlStatementForGettingTableNames = System.getProperty("jdbcadaptor.frontbase.sqlStatementForGettingTableNames", null);
static final String _frontbaseContainsOperatorFix = System.getProperty("jdbcadaptor.frontbase.frontbaseContainsOperatorFix", null);
/**
* Formatter to use when handling date columns. Each thread has its own copy.
*/
private static final ThreadLocal<SimpleDateFormat> DATE_FORMATTER = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
/**
* Formatter to use when handling timestamp columns. Each thread has its own copy.
*/
private static final ThreadLocal<SimpleDateFormat> TIMESTAMP_FORMATTER = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
}
};
/**
* Formatter to use when handling time only columns. Each thread has its own copy.
*/
private static final ThreadLocal<SimpleDateFormat> TIME_FORMATTER = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("HH:mm:ss.SSS");
}
};
public _FrontBasePlugIn(JDBCAdaptor jdbcadaptor) {
super(jdbcadaptor);
}
public static String getPlugInVersion() {
return "2.6.4";
}
@Override
public boolean canDescribeStoredProcedure(String s) {
return true;
}
@Override
public EOSynchronizationFactory createSynchronizationFactory() {
return new _FrontBasePlugIn.FrontbaseSynchronizationFactory(_adaptor);
}
@Override
public String defaultDriverName() {
return "com.frontbase.jdbc.FBJDriver";
}
@Override
public String databaseProductName() {
return "FrontBase";
}
/**
* <P>
* WebObjects 5.4's version of JDBCAdaptor will use this in order to assemble the name of the prototype to use when
* it loads models.
* </P>
*
* @return the name of the plugin.
*/
@Override
public String name() {
return "FrontBase";
}
/**
* <P>
* This method returns true if the connection URL for the database has a special flag on it which indicates to the
* system that the jdbcInfo which has been bundled into the plugin is acceptable to use in place of actually going
* to the database and getting it.
*
* @return <code>true</code> jdbcInfo which has been bundled into the plugin is acceptable to use
*/
protected boolean shouldUseBundledJdbcInfo() {
boolean shouldUseBundledJdbcInfo = false;
String url = connectionURL();
if (url != null) {
shouldUseBundledJdbcInfo = url.toLowerCase().matches(".*/" + _FrontBasePlugIn.QUERY_STRING_USE_BUNDLED_JDBC_INFO.toLowerCase() + "=(true|yes)(\\/|$)");
}
return shouldUseBundledJdbcInfo;
}
@Override
public Class<? extends JDBCExpression> defaultExpressionClass() {
return FrontbaseExpression.class;
}
@Override
public String wildcardPatternForSchema() {
if (_frontbaseWildcardPatternForSchema != null)
return _frontbaseWildcardPatternForSchema;
else {
String schema = (String) adaptor().connectionDictionary().objectForKey("schema");
return (schema != null) ? schema.toUpperCase() : "CURRENT_SCHEMA";
}
}
@Override
public String schemaNameForEntity(EOEntity eoentity) {
String s = super.schemaNameForEntity(eoentity);
if (s == null) {
s = (String) adaptor().connectionDictionary().objectForKey("schema");
return (s != null) ? s.toUpperCase() : "CURRENT_SCHEMA";
}
else
return s;
}
@Override
public String storedProcedureSchemaPattern() {
if (_frontbaseStoredProcedureSchemaPattern != null)
return _frontbaseStoredProcedureSchemaPattern;
else
return "CURRENT_SCHEMA";
}
@Override
public Properties connectionPropertiesForConnectionDictionary(NSDictionary connectionDictionary) {
Properties properties = super.connectionPropertiesForConnectionDictionary(connectionDictionary);
// Check for dbPasswd in connection Dictionary
Object temp = connectionDictionary.objectForKey("dbpasswd");
if (temp != null) {
properties.put("dbpasswd", temp);
}
// Check for session in connection Dictionary
temp = connectionDictionary.objectForKey("session");
if (temp != null) {
properties.put("session", temp);
}
// Check for session in connection Dictionary
temp = connectionDictionary.objectForKey("system");
if (temp != null) {
properties.put("system", temp);
}
// Check for session in connection Dictionary
temp = connectionDictionary.objectForKey("isolation");
if (temp != null) {
properties.put("isolation", temp);
}
// Check for session in connection Dictionary
temp = connectionDictionary.objectForKey("locking");
if (temp != null) {
properties.put("locking", temp);
}
// Check for session in connection Dictionary
temp = connectionDictionary.objectForKey("readOnly");
if (temp != null) {
properties.put("readOnly", temp);
}
return properties;
}
/**
* <P>
* This is usually extracted from the the database using JDBC, but this is really inconvenient for users who are
* trying to generate SQL at some. A specific version of the data has been written into the property list of the
* framework and this can be used as a hard-coded equivalent.
* </P>
*/
@Override
public NSDictionary<String, Object> jdbcInfo() {
// you can swap this code out to write the property list out in order // to get a fresh copy of the
// JDBCInfo.plist
// try {
// String jdbcInfoS = NSPropertyListSerialization.stringFromPropertyList(super.jdbcInfo());
// FileOutputStream fos = new FileOutputStream("/tmp/JDBCInfo.plist");
// fos.write(jdbcInfoS.getBytes());
// fos.close();
// }
// catch (Exception e) {
// throw new IllegalStateException("problem writing JDBCInfo.plist", e);
// }
boolean shouldUseBundledJdbcInfo = shouldUseBundledJdbcInfo();
NSDictionary<String, Object> jdbcInfo;
// have a look at the JDBC connection URL to see if the flag has been set to
// specify that the hard-coded jdbcInfo information should be used.
if (shouldUseBundledJdbcInfo) {
if (NSLog.debugLoggingAllowedForLevel(NSLog.DebugLevelDetailed)) {
NSLog.debug.appendln("Loading jdbcInfo from JDBCInfo.plist as opposed to using the JDBCPlugIn default implementation.");
}
// MS: Note that the name is not just /JDBCInfo.plist like it used to be. Because we're loading
// resources from the classpath instead of the Resources folder (so that it loads properly in EM),
// if you have two plugins loaded, their resource names will overlap.
InputStream jdbcInfoStream = getClass().getResourceAsStream("/FrontBaseJDBCInfo.plist");
if (jdbcInfoStream == null) {
throw new IllegalStateException("Unable to find 'FrontBaseJDBCInfo.plist' in this plugin jar.");
}
try {
jdbcInfo = (NSDictionary<String, Object>) NSPropertyListSerialization.propertyListFromData(new NSData(jdbcInfoStream, 2048), "US-ASCII");
}
catch (IOException e) {
throw new RuntimeException("Failed to load 'FrontBaseJDBCInfo.plist' from this plugin jar.", e);
} finally {
try { jdbcInfoStream.close(); } catch (IOException e) {}
}
}
else {
jdbcInfo = super.jdbcInfo();
}
NSMutableDictionary<String, Object> mutableJdbcInfo = new NSMutableDictionary<String, Object>(jdbcInfo);
NSMutableDictionary typeInfoDict = new NSMutableDictionary((NSDictionary) mutableJdbcInfo.objectForKey("typeInfo"));
NSDictionary typeDict = (NSDictionary) typeInfoDict.objectForKey("CHARACTER");
typeInfoDict.setObjectForKey(typeDict, "CHAR");
typeDict = (NSDictionary) typeInfoDict.objectForKey("CHARACTER VARYING");
typeInfoDict.setObjectForKey(typeDict, "VARCHAR");
typeInfoDict.setObjectForKey(typeDict, "CHAR VARYING");
typeDict = (NSDictionary) typeInfoDict.objectForKey("BIT");
typeInfoDict.setObjectForKey(typeDict, "BYTE");
typeDict = (NSDictionary) typeInfoDict.objectForKey("BIT VARYING");
typeInfoDict.setObjectForKey(typeDict, "BYTE VARYING");
mutableJdbcInfo.setObjectForKey(typeInfoDict, "typeInfo");
if (!shouldUseBundledJdbcInfo) {
JDBCContext jdbccontext = adaptor()._cachedAdaptorContext();
try {
jdbccontext.connection().commit();
}
catch (SQLException sqlexception) {
if (NSLog.debugLoggingAllowedForLevelAndGroups(3, 0x000010000L))
NSLog.debug.appendln(sqlexception);
}
}
return mutableJdbcInfo;
}
EOQualifier primaryKeyQualifier(EOQualifier eoqualifier, EOEntity eoentity) {
if (eoqualifier instanceof EOAndQualifier) {
NSArray<EOQualifier> qualifiers = ((EOAndQualifier) eoqualifier).qualifiers();
NSArray<String> attributeNames = eoentity.primaryKeyAttributeNames();
NSMutableArray nsmutablearray = new NSMutableArray();
for (int i = 0; i < qualifiers.count(); i++) {
EOQualifier eoqualifier1 = qualifiers.objectAtIndex(i);
if (eoqualifier1 instanceof EOKeyValueQualifier) {
EOKeyValueQualifier eokeyvaluequalifier = (EOKeyValueQualifier) eoqualifier1;
if (attributeNames.containsObject(eokeyvaluequalifier.key()))
nsmutablearray.addObject(eokeyvaluequalifier);
}
}
if (nsmutablearray.count() == 1)
return (EOQualifier) nsmutablearray.objectAtIndex(0);
else
return new EOAndQualifier(qualifiers);
}
else {
return eoqualifier;
}
}
@Override
public void updateLOBs(JDBCChannel channel, JDBCExpression expression, NSDictionary<String, Object> row, EOEntity entity) {
FrontbaseExpression frontbaseexpression = (FrontbaseExpression) expression;
if (!frontbaseexpression.hasLOBsToUpdate())
return;
NSArray array = frontbaseexpression.lobList();
try {
Connection con = ((JDBCContext) channel.adaptorContext()).connection();
NSMutableDictionary<String, Object> d = new NSMutableDictionary<String, Object>();
for (int i = 0; i < array.count(); i += 2) {
d.setObjectForKey(getLobHandle(con, array.objectAtIndex(i), array.objectAtIndex(i + 1)), ((EOAttribute) array.objectAtIndex(i)).name());
}
EOQualifier qualifier = frontbaseexpression.qualifier();
if (qualifier == null)
qualifier = entity.qualifierForPrimaryKey(row);
else
qualifier = primaryKeyQualifier(qualifier, entity);
frontbaseexpression.resetlobList();
channel.updateValuesInRowsDescribedByQualifier(d, qualifier, entity);
}
catch (SQLException e) {
System.err.print(e.getMessage());
}
}
// When using BLOB as an external type NSData is expected as the internal type.
// When using CLOB as an external type String is expected as the internal type.
String getLobHandle(Connection con, Object attribute, Object value) throws SQLException {
// MS: This is weird, but to allow for people to build FrontBasePlugIn without actually
// having the FrontBase JDBC driver installed, I've switched these two calls to be reflection.
try {
switch (FrontBaseTypes.internalTypeForExternal(((EOAttribute) attribute).externalType())) {
case FrontBaseTypes.FB_BLOB:
Method writeBLOBBytes = con.getClass().getMethod("writeBLOB", new Class[] { byte[].class });
return (String) writeBLOBBytes.invoke(con, new Object[] { ((NSData) value).bytes() });
case FrontBaseTypes.FB_CLOB:
Method writeCLOBString = con.getClass().getMethod("writeCLOB", new Class[] { String.class });
return (String) writeCLOBString.invoke(con, new Object[] { (String) value });
default:
return "NULL";
}
}
catch (Throwable e) {
if (e instanceof SQLException) {
throw (SQLException) e;
}
throw new RuntimeException("Failed to get LOB handle.", e);
}
}
@Override
public Object fetchBLOB(ResultSet resultset, int i, EOAttribute attribute, boolean flag) throws SQLException {
Blob blob = resultset.getBlob(i);
if (blob == null)
return null;
if (!flag)
return blob;
else {
try {
byte[] bytes = blob.getBytes(1, (int) blob.length());
return new NSData(bytes, new NSRange(0, bytes.length), true);
}
catch (Exception ioexception) {
throw new JDBCAdaptorException(ioexception.getMessage(), null);
}
}
}
@Override
public Object fetchCLOB(ResultSet resultset, int i, EOAttribute attribute, boolean flag) throws SQLException {
Clob clob = resultset.getClob(i);
if (clob == null)
return null;
if (!flag)
return clob;
else
return clob.getSubString(1L, (int) clob.length());
}
@Override
public NSArray<NSDictionary<String, Object>> newPrimaryKeys(int numberOfKeys, EOEntity eoentity, JDBCChannel jdbcchannel) {
NSMutableArray<NSDictionary<String, Object>> pkDicts = new NSMutableArray<NSDictionary<String, Object>>();
boolean pksGenerated = true;
int numberOfKeysLeft = numberOfKeys;
int keyBatchSize = 10;
while (pksGenerated && numberOfKeysLeft > 0) {
int thisKeyBatchSize = Math.min(keyBatchSize, numberOfKeysLeft);
pksGenerated = _newPrimaryKeys(thisKeyBatchSize, eoentity, jdbcchannel, pkDicts);
numberOfKeysLeft -= thisKeyBatchSize;
}
if (!pksGenerated) {
pkDicts = null;
}
return pkDicts;
}
private boolean _newPrimaryKeys(int keyBatchSize, EOEntity eoentity, JDBCChannel jdbcchannel, NSMutableArray<NSDictionary<String, Object>> pkDicts) {
if (keyBatchSize == 0) {
return true;
}
NSArray<EOAttribute> primaryKeyAttributes = eoentity.primaryKeyAttributes();
if (primaryKeyAttributes == null) {
return false;
}
EOAttribute firstPrimaryKeyAttribute = primaryKeyAttributes.lastObject();
boolean isNSData = firstPrimaryKeyAttribute.className().endsWith("NSData");
NSMutableArray<EOAttribute> attributesToFetch = new NSMutableArray<EOAttribute>();
StringBuilder sql = new StringBuilder();
sql.append("VALUES (");
for (int keyNum = 0; keyNum < keyBatchSize; keyNum++) {
if (isNSData) {
if (firstPrimaryKeyAttribute.externalType().startsWith("BIT")) {
sql.append("NEW_UID(" + (firstPrimaryKeyAttribute.width() >> 3) + ")");
}
else {
sql.append("NEW_UID(" + firstPrimaryKeyAttribute.width() + ")");
}
}
else {
sql.append("SELECT UNIQUE FROM " + quoteTableName(eoentity.primaryKeyRootName()));
}
if (keyNum < keyBatchSize - 1) {
sql.append(", ");
}
EOAttribute generatedPrimaryKeyAttribute = new EOAttribute();
generatedPrimaryKeyAttribute.setName("Unique" + keyNum);
generatedPrimaryKeyAttribute.setColumnName(firstPrimaryKeyAttribute.columnName());
generatedPrimaryKeyAttribute.setExternalType(firstPrimaryKeyAttribute.externalType());
generatedPrimaryKeyAttribute.setClassName(firstPrimaryKeyAttribute.className());
generatedPrimaryKeyAttribute.setValueType(firstPrimaryKeyAttribute.valueType());
generatedPrimaryKeyAttribute.setPrecision(firstPrimaryKeyAttribute.precision());
generatedPrimaryKeyAttribute.setScale(firstPrimaryKeyAttribute.scale());
generatedPrimaryKeyAttribute.setWidth(firstPrimaryKeyAttribute.width());
generatedPrimaryKeyAttribute.setAllowsNull(firstPrimaryKeyAttribute.allowsNull());
adaptor().plugIn().assignTypeForAttribute(generatedPrimaryKeyAttribute);
attributesToFetch.addObject(generatedPrimaryKeyAttribute);
}
sql.append(')');
boolean pksGenerated = false;
EOSQLExpression eosqlexpression = expressionFactory().expressionForString(sql.toString());
EOAdaptorContext adaptorContext = jdbcchannel.adaptorContext();
adaptorContext.transactionDidBegin();
jdbcchannel.evaluateExpression(eosqlexpression);
if (jdbcchannel._errorEvaluateExpression()) {
adaptorContext.transactionDidRollback();
jdbcchannel._setErrorEvaluateExpression(false);
}
else {
jdbcchannel.setAttributesToFetch(attributesToFetch);
NSMutableDictionary<String, Object> row = jdbcchannel.fetchRow();
jdbcchannel.cancelFetch();
adaptorContext.transactionDidCommit();
if (row != null && row.count() > 0) {
NSArray pkValues = row.allValues();
if (pkValues.count() == keyBatchSize) {
Enumeration keysEnum = pkValues.objectEnumerator();
while (keysEnum.hasMoreElements()) {
Object obj = keysEnum.nextElement();
NSMutableDictionary pkDict = new NSMutableDictionary();
Enumeration pkAttributeEnum = primaryKeyAttributes.objectEnumerator();
while (pkAttributeEnum.hasMoreElements()) {
EOAttribute pkAttribute = (EOAttribute) pkAttributeEnum.nextElement();
pkDict.setObjectForKey(obj, pkAttribute.name());
}
pkDicts.addObject(pkDict);
}
pksGenerated = true;
}
}
}
return pksGenerated;
}
protected static String notNullConstraintName(EOAttribute attribute) {
return notNullConstraintName(attribute.entity().externalName(), attribute.columnName());
}
protected static String notNullConstraintName(String tableName, String columnName) {
StringBuilder constraintBuffer = new StringBuilder();
constraintBuffer.append("NOT_NULL_");
constraintBuffer.append(tableName);
constraintBuffer.append("__");
constraintBuffer.append(columnName);
return constraintBuffer.toString();
}
protected static String quoteTableName(String s) {
if (s == null)
return null;
int i = s.lastIndexOf(46);
if (i == -1)
return "\"" + s + "\"";
else
return "\"" + s.substring(0, i) + "\".\"" + s.substring(i + 1, s.length()) + "\"";
}
public static class FrontbaseSynchronizationFactory extends EOSynchronizationFactory {
public FrontbaseSynchronizationFactory(EOAdaptor eoadaptor) {
super(eoadaptor);
}
@Override
public boolean supportsSchemaSynchronization() {
return true;
}
public static boolean boolValueForKeyDefault(NSDictionary nsdictionary, String s, boolean flag) {
String s1 = (String) nsdictionary.objectForKey(s);
if (s1 == null)
return flag;
else
return s1.equals("YES");
}
@Override
public String schemaCreationScriptForEntities(NSArray<EOEntity> allEntities, NSDictionary<String, String> options) {
StringBuffer result = new StringBuffer();
if (options == null) {
options = NSDictionary.emptyDictionary();
}
NSArray<EOSQLExpression> statements = schemaCreationStatementsForEntities(allEntities, options);
int i = 0;
for (int count = statements.count(); i < count; i++) {
appendExpressionToScript(statements.objectAtIndex(i), result);
}
return result.toString();
}
/**
* <span class="ja">
* Eclipse の EntityModeler でエンティティを作成時に使用されるメソッド。
* SQL 生成をクリックするとここで呼び出される
* </span>
*/
@Override
public NSArray<EOSQLExpression> schemaCreationStatementsForEntities(NSArray<EOEntity> entities, NSDictionary<String, String> options) {
NSMutableArray<EOSQLExpression> result = new NSMutableArray<EOSQLExpression>();
if (entities == null || entities.count() == 0)
return result;
// データベース・ストラクチャに変更する時にはこの行を実行しないとエラーになる可能性があります。
result.addObject(_expressionForString("-- SQL creation time : " + new NSTimestamp().toString()));
result.addObject(_expressionForString("-- PlugIn version : " + getPlugInVersion()));
result.addObject(_expressionForString("-- To change any Structure Information this Command is must have"));
result.addObject(_expressionForString("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, LOCKING PESSIMISTIC"));
NSDictionary<String, Object> connectionDict = entities.lastObject().model().connectionDictionary();
if (boolValueForKeyDefault(options, "dropDatabase", false)) {
result.addObjectsFromArray(dropDatabaseStatementsForConnectionDictionary(connectionDict, null));
}
if (boolValueForKeyDefault(options, "createDatabase", false)) {
result.addObjectsFromArray(createDatabaseStatementsForConnectionDictionary(connectionDict, null));
}
if (boolValueForKeyDefault(options, "dropPrimaryKeySupport", true)) {
NSArray<NSArray<EOEntity>> entityGroups = primaryKeyEntityGroupsForEntities(entities);
result.addObjectsFromArray(dropPrimaryKeySupportStatementsForEntityGroups(entityGroups));
}
if (boolValueForKeyDefault(options, "dropTables", true)) {
NSArray<NSArray<EOEntity>> entityGroups = tableEntityGroupsForEntities(entities);
result.addObjectsFromArray(dropTableStatementsForEntityGroups(entityGroups));
}
if (boolValueForKeyDefault(options, "createTables", true)) {
NSArray<NSArray<EOEntity>> entityGroups = tableEntityGroupsForEntities(entities);
result.addObjectsFromArray(createTableStatementsForEntityGroups(entityGroups));
result.addObjectsFromArray(createIndexStatementsForEntityGroups(entityGroups));
}
if (boolValueForKeyDefault(options, "createPrimaryKeySupport", true)) {
NSArray<NSArray<EOEntity>> entityGroups = primaryKeyEntityGroupsForEntities(entities);
result.addObjectsFromArray(primaryKeySupportStatementsForEntityGroups(entityGroups));
}
if (boolValueForKeyDefault(options, "primaryKeyConstraints", true)) {
NSArray<NSArray<EOEntity>> entityGroups = tableEntityGroupsForEntities(entities);
result.addObjectsFromArray(primaryKeyConstraintStatementsForEntityGroups(entityGroups));
}
if (boolValueForKeyDefault(options, "foreignKeyConstraints", false)) {
NSArray<NSArray<EOEntity>> entityGroups = tableEntityGroupsForEntities(entities);
for (NSArray<EOEntity> entityGroup : entityGroups) {
result.addObjectsFromArray(_foreignKeyConstraintStatementsForEntityGroup(entityGroup));
}
}
result.addObject(_expressionForString("COMMIT"));
return result;
}
@Override
public NSArray<EOSQLExpression> dropPrimaryKeySupportStatementsForEntityGroups(NSArray<NSArray<EOEntity>> entityGroups) {
return new NSArray<EOSQLExpression>(_expressionForString("-- The 'Drop Primary Key Support' option is unavailable."));
}
@Override
public NSArray<EOSQLExpression> dropDatabaseStatementsForConnectionDictionary(NSDictionary<String, Object> connectionDictionary, NSDictionary<String, Object> administrativeConnectionDictionary) {
return new NSArray<EOSQLExpression>(_expressionForString("-- The 'Drop Database' option is unavailable."));
}
@Override
public NSArray<EOSQLExpression> createDatabaseStatementsForConnectionDictionary(NSDictionary<String, Object> connectionDictionary, NSDictionary<String, Object> administrativeConnectionDictionary) {
return new NSArray<EOSQLExpression>(_expressionForString("-- The 'Create Database' option is unavailable."));
}
@Override
public NSArray<EOSQLExpression> dropTableStatementsForEntityGroups(NSArray<NSArray<EOEntity>> entityGroups) {
NSLog.debug.appendln("In dropTableStatementsForEntityGroups");
return super.dropTableStatementsForEntityGroups(entityGroups);
}
@Override
public NSArray<EOSQLExpression> dropTableStatementsForEntityGroup(NSArray<EOEntity> entityGroup) {
NSLog.debug.appendln("In dropTableStatementsForEntityGroup (no s)");
EOEntity entity = entityGroup.objectAtIndex(0);
String dropType = " CASCADE";
if (entity.userInfo() != null) {
NSDictionary dictionary = entity.userInfo();
if (dictionary.valueForKey("Restrict") != null && ((String) dictionary.valueForKey("Restrict")).equals("true"))
dropType = " RESTRICT";
}
EOSQLExpression expression = _expressionForString("DROP TABLE " + quoteTableName(entity.externalName()) + dropType);
return new NSArray<EOSQLExpression>(expression);
}
@Override
public NSArray<EOSQLExpression> primaryKeySupportStatementsForEntityGroup(NSArray<EOEntity> entityGroup) {
if (entityGroup == null)
return NSArray.emptyArray();
NSMutableArray<EOSQLExpression> result = new NSMutableArray<EOSQLExpression>();
for (int i = entityGroup.count() - 1; i >= 0; i--) {
EOEntity eoentity = entityGroup.objectAtIndex(i);
String externalName = eoentity.externalName();
NSArray<EOAttribute> priKeyAttributes = eoentity.primaryKeyAttributes();
if (priKeyAttributes.count() == 1 && externalName != null && externalName.length() > 0) {
EOAttribute priKeyAttribute = priKeyAttributes.objectAtIndex(0);
// pk counter not needed for non number primary key
if (priKeyAttribute.adaptorValueType() != EOAttribute.AdaptorNumberType) {
continue;
}
String unique = null;
if (eoentity.model() != null) {
unique = System.getProperty("com.frontbase.unique." + eoentity.model().name() + "." + eoentity.name());
if (unique == null) {
unique = System.getProperty("com.frontbase.unique." + eoentity.model().name());
}
}
if (unique == null) {
unique = System.getProperty("com.frontbase.unique");
}
if (unique == null) {
unique = "1000000";
}
result.addObject(_expressionForString("SET UNIQUE = " + unique + " FOR " + quoteTableName(externalName)));
result.addObject(_expressionForString("ALTER TABLE " + quoteTableName(externalName) + " ALTER "
+ quoteTableName(priKeyAttribute.name()) + " SET DEFAULT UNIQUE"));
}
}
return result;
}
@Override
public NSArray<EOSQLExpression> foreignKeyConstraintStatementsForRelationship(EORelationship relationship) {
if (!relationship.isToMany() && isPrimaryKeyAttributes(relationship.destinationEntity(), relationship.destinationAttributes())) {
StringBuilder sql = new StringBuilder();
String tableName = relationship.entity().externalName();
sql.append("ALTER TABLE ");
sql.append(quoteTableName(tableName.toUpperCase()));
sql.append(" ADD");
StringBuilder constraint = new StringBuilder(" CONSTRAINT \"FOREIGN_KEY_");
constraint.append(tableName);
StringBuilder fkSql = new StringBuilder(" FOREIGN KEY (");
NSArray<EOAttribute> attributes = relationship.sourceAttributes();
for (int i = 0; i < attributes.count(); i++) {
constraint.append('_');
if (i != 0)
fkSql.append(", ");
fkSql.append("\"");
String columnName = attributes.objectAtIndex(i).columnName();
fkSql.append(columnName.toUpperCase());
constraint.append(columnName);
fkSql.append("\"");
}
fkSql.append(") REFERENCES ");
constraint.append('_');
String referencedExternalName = relationship.destinationEntity().externalName();
fkSql.append(quoteTableName(referencedExternalName.toUpperCase()));
constraint.append(referencedExternalName);
fkSql.append(" (");
attributes = relationship.destinationAttributes();
for (int i = 0; i < attributes.count(); i++) {
constraint.append('_');
if (i != 0)
fkSql.append(", ");
fkSql.append("\"");
String referencedColumnName = attributes.objectAtIndex(i).columnName();
fkSql.append(referencedColumnName.toUpperCase());
constraint.append(referencedColumnName);
fkSql.append("\"");
}
// MS: did i write this code? sorry about that everything. this is crazy.
constraint.append("\"");
fkSql.append(") DEFERRABLE INITIALLY DEFERRED");
if (USE_NAMED_CONSTRAINTS)
sql.append(constraint);
sql.append(fkSql);
return new NSArray<EOSQLExpression>(_expressionForString(sql.toString()));
}
return NSArray.emptyArray();
}
/**
* <span class="ja">複数のエンティティ・グループス作成 SQL を生成します。</span>
*/
@Override
public NSArray<EOSQLExpression> createTableStatementsForEntityGroups(NSArray<NSArray<EOEntity>> entityGroups) {
NSMutableArray<EOSQLExpression> nsmutablearray = new NSMutableArray<EOSQLExpression>();
for (int i = 0; i < entityGroups.count(); i++) {
nsmutablearray.addObjectsFromArray(createTableStatementsForEntityGroup(entityGroups.objectAtIndex(i)));
}
return nsmutablearray;
}
/**
* <span class="ja">エンティティ・グループの SQL を生成します</span>
*/
@Override
public NSArray<EOSQLExpression> createTableStatementsForEntityGroup(NSArray<EOEntity> entityGroup) {
EOSQLExpression eosqlexpression = null;
EOEntity eoentity = null;
NSMutableArray<String> nsmutablearray = new NSMutableArray<String>();
int j = entityGroup != null ? entityGroup.count() : 0;
if (j == 0)
return NSArray.emptyArray();
// 出力バッファーを準備
StringBuilder columns = new StringBuilder();
// エンティティの出力開始
eosqlexpression = _expressionForEntity(entityGroup.objectAtIndex(0));
// 各エンティティをループで回す
for (int i = 0; i < j; i++) {
eoentity = entityGroup.objectAtIndex(i);
NSArray nsarray1 = eoentity.attributes();
int l = nsarray1 != null ? nsarray1.count() : 0;
for (int k = 0; k < l; k++) {
EOAttribute eoattribute = (EOAttribute) nsarray1.objectAtIndex(k);
String column = eoattribute.columnName();
if (!eoattribute.isDerived() && !eoattribute.isFlattened() && column != null && column.length() > 0 && nsmutablearray.indexOfObject(column) == NSArray.NotFound) {
if (columns.length() > 0) {
columns.append(',');
columns.append('\n');
columns.append('\t');
}
columns.append(addCreateClauseForAttribute(eoattribute));
nsmutablearray.addObject(column);
}
}
}
StringBuilder sql = new StringBuilder();
sql.append("CREATE TABLE ");
sql.append(quoteTableName(eoentity.externalName()));
sql.append(" (\n\t");
sql.append(columns.toString());
sql.append("\n)");
eosqlexpression.setStatement(sql.toString());
return new NSArray<EOSQLExpression>(eosqlexpression);
}
@Override
public NSArray<EOSQLExpression> createIndexStatementsForEntityGroups(NSArray<NSArray<EOEntity>> entityGroups) {
NSMutableArray<EOSQLExpression> statements = new NSMutableArray<EOSQLExpression>();
for (int i = 0; i < entityGroups.count(); i++) {
statements.addObjectsFromArray(createIndexStatementsForEntityGroup(entityGroups.objectAtIndex(i)));
}
return statements;
}
@Override
public NSArray<EOSQLExpression> createIndexStatementsForEntityGroup(NSArray<EOEntity> entityGroup) {
NSMutableArray<EOSQLExpression> result = new NSMutableArray<EOSQLExpression>();
EOSQLExpression eosqlexpression = null;
EOEntity eoentity = null;
int j = entityGroup != null ? entityGroup.count() : 0;
if (j == 0)
return NSArray.emptyArray();
eosqlexpression = _expressionForEntity(entityGroup.objectAtIndex(0));
for (int i = 0; i < j; i++) {
eoentity = entityGroup.objectAtIndex(i);
NSDictionary dictionary = eoentity.userInfo();
if (dictionary != null && dictionary.valueForKey("Index") != null) {
dictionary = (NSDictionary) dictionary.valueForKey("Index");
java.util.Enumeration e = dictionary.keyEnumerator();
while (e.hasMoreElements()) {
eosqlexpression.setStatement((String) dictionary.objectForKey(e.nextElement()));
result.addObject(eosqlexpression);
}
}
}
return result;
}
/**
* <span class="ja">1つのアトリビュートの SQL を生成します </span>
*/
public StringBuilder addCreateClauseForAttribute(EOAttribute eoattribute) {
EOSQLExpression expression = _expressionForEntity(eoattribute.entity());
expression.addCreateClauseForAttribute(eoattribute);
return new StringBuilder(expression.listString());
}
public String columnTypeStringForAttribute(EOAttribute eoattribute) {
EOSQLExpression expression = _expressionForEntity(eoattribute.entity());
return expression.columnTypeStringForAttribute(eoattribute);
}
@Override
public NSArray<EOSQLExpression> statementsToConvertColumnType(String columnName, String tableName, ColumnTypes oldType, ColumnTypes newType, NSDictionary<String, String> options) {
String columnTypeString = statementToCreateDataTypeClause(newType);
NSArray<EOSQLExpression> statements = new NSArray<EOSQLExpression>(_expressionForString("alter column " + quoteTableName(tableName) + "." + quoteTableName(columnName) + " to " + columnTypeString));
return statements;
}
@Override
public NSArray<EOSQLExpression> statementsToModifyColumnNullRule(String columnName, String tableName, boolean allowsNull, NSDictionary<String, String> options) {
NSArray<EOSQLExpression> statements;
if (allowsNull) {
if (USE_NAMED_CONSTRAINTS) {
statements = new NSArray<EOSQLExpression>(_expressionForString("alter table " + quoteTableName(tableName) + " drop constraint " + quoteTableName(_FrontBasePlugIn.notNullConstraintName(tableName, columnName)) + " cascade"));
}
else {
statements = null;
}
}
else {
if (USE_NAMED_CONSTRAINTS) {
statements = new NSArray<EOSQLExpression>(_expressionForString("alter table " + quoteTableName(tableName) + " add constraint " + quoteTableName(_FrontBasePlugIn.notNullConstraintName(tableName, columnName)) + " check (" + quoteTableName(columnName) + " is not null)"));
}
else {
statements = new NSArray<EOSQLExpression>(_expressionForString("alter table " + quoteTableName(tableName) + " add check (" + quoteTableName(columnName) + " is not null)"));
}
}
return statements;
}
@Override
public NSArray<EOSQLExpression> statementsToDeleteColumnNamed(String columnName, String tableName, NSDictionary<String, String> options) {
return new NSArray<EOSQLExpression>(_expressionForString("alter table " + quoteTableName(tableName) + " drop column \"" + columnName.toUpperCase() + "\" cascade"));
}
@Override
public String _columnCreationClauseForAttribute(EOAttribute attribute) {
return addCreateClauseForAttribute(attribute).toString();
}
@Override
public NSArray<EOSQLExpression> statementsToInsertColumnForAttribute(EOAttribute attribute, NSDictionary<String, String> options) {
String clause = _columnCreationClauseForAttribute(attribute);
return new NSArray<EOSQLExpression>(_expressionForString("alter table " + quoteTableName(attribute.entity().externalName()) + " add " + clause));
}
private String statementToCreateDataTypeClause(EOSchemaSynchronization.ColumnTypes columntypes) {
switch (FrontBaseTypes.internalTypeForExternal(columntypes.name())) {
case FrontBaseTypes.FB_Decimal:
case FrontBaseTypes.FB_Numeric:
int j = columntypes.precision();
if (j == 0)
return columntypes.name();
int k = columntypes.scale();
if (k == 0)
return columntypes.name() + "(" + j + ")";
else
return columntypes.name() + "(" + j + "," + k + ")";
case FrontBaseTypes.FB_Float:
case FrontBaseTypes.FB_Bit:
case FrontBaseTypes.FB_VBit:
case FrontBaseTypes.FB_Character:
case FrontBaseTypes.FB_VCharacter:
int l = columntypes.width();
if (l == 0)
l = columntypes.precision();
if (l == 0)
return columntypes.name();
else
return columntypes.name() + "(" + l + ")";
case FrontBaseTypes.FB_Timestamp:
int m = columntypes.precision();
if (m == 0)
return columntypes.name();
else
return columntypes.name() + "(" + m + ")";
}
return columntypes.name();
}
@Override
public NSArray<EOSQLExpression> statementsToRenameColumnNamed(String columnName, String tableName, String newName, NSDictionary<String, String> options) {
return new NSArray<EOSQLExpression>(_expressionForString("alter column name " + quoteTableName(tableName) + "." + quoteTableName(columnName) + " to " + quoteTableName(newName)));
}
@Override
public NSArray<EOSQLExpression> statementsToRenameTableNamed(String tableName, String newName, NSDictionary<String, String> options) {
return new NSArray<EOSQLExpression>(_expressionForString("alter table name " + quoteTableName(tableName) + " to " + quoteTableName(newName)));
}
boolean isPrimaryKeyAttributes(EOEntity entity, NSArray<EOAttribute> attributes) {
NSArray<String> keys = entity.primaryKeyAttributeNames();
boolean result = attributes.count() == keys.count();
if (result) {
for (int i = 0; i < keys.count(); i++) {
if (!(result = keys.indexOfObject(attributes.objectAtIndex(i).name()) != NSArray.NotFound))
break;
}
}
return result;
}
@Override
public NSArray<EOSQLExpression> primaryKeyConstraintStatementsForEntityGroups(NSArray<NSArray<EOEntity>> entityGroups) {
NSMutableArray<EOSQLExpression> result = new NSMutableArray<EOSQLExpression>();
for (int i = 0; i < entityGroups.count(); i++) {
result.addObjectsFromArray(primaryKeyConstraintStatementsForEntityGroup(entityGroups.objectAtIndex(i)));
}
return result;
}
@Override
public NSArray<EOSQLExpression> primaryKeyConstraintStatementsForEntityGroup(NSArray<EOEntity> entityGroup) {
if (entityGroup.count() != 0) {
EOEntity entity = entityGroup.objectAtIndex(0);
String tableName = entity.externalName();
NSArray<String> keys = entity.primaryKeyAttributeNames();
StringBuilder sql = new StringBuilder();
if (tableName != null && keys.count() > 0) {
sql.append("ALTER TABLE ");
sql.append(quoteTableName(tableName.toUpperCase()));
sql.append(" ADD");
StringBuilder constraint = new StringBuilder(" CONSTRAINT \"PRIMARY_KEY_");
constraint.append(tableName);
StringBuilder pkSql = new StringBuilder(" PRIMARY KEY (");
for (int j = 0; j < keys.count(); j++) {
constraint.append('_');
if (j != 0)
pkSql.append(',');
pkSql.append("\"");
String columnName = entity.attributeNamed(keys.objectAtIndex(j)).columnName();
pkSql.append(columnName.toUpperCase());
pkSql.append("\"");
constraint.append(columnName);
}
constraint.append("\"");
pkSql.append(") NOT DEFERRABLE INITIALLY IMMEDIATE");
if (USE_NAMED_CONSTRAINTS)
sql.append(constraint);
sql.append(pkSql);
return new NSArray<EOSQLExpression>(_expressionForString(sql.toString()));
}
}
return NSArray.emptyArray();
}
}
public static class FrontbaseExpression extends JDBCExpression {
private boolean _useBindVariables;
EOQualifier _qualifier;
NSMutableArray _lobList;
/**
* Holds array of join clauses.
*/
private NSMutableArray<JoinClause> _alreadyJoined = new NSMutableArray<JoinClause>();
public FrontbaseExpression(EOEntity eoentity) {
super(eoentity);
_useBindVariables = "true".equalsIgnoreCase(System.getProperty("FrontBasePlugIn.useBindVariables"));
_rtrimFunctionName = null;
_externalQuoteChar = "\"";
}
@Override
public void addCreateClauseForAttribute(EOAttribute attribute) {
StringBuilder sql = new StringBuilder();
sql.append("\"");
sql.append(attribute.columnName());
sql.append("\" ");
sql.append(columnTypeStringForAttribute(attribute));
NSDictionary dictionary = attribute.userInfo();
if (dictionary == null) {
_appendNotNullConstraintIfNecessary(attribute, sql);
}
else {
// Default values.
Object defaultValue = dictionary.valueForKey("Default");
if (defaultValue == null) {
defaultValue = dictionary.valueForKey("er.extensions.eoattribute.default"); // deprecated key
}
if (defaultValue == null) {
defaultValue = dictionary.valueForKey("default");
}
if (defaultValue != null) {
sql.append(" DEFAULT ");
sql.append(formatValueForAttribute(defaultValue, attribute));
}
// Column constraints.
_appendNotNullConstraintIfNecessary(attribute, sql);
if (dictionary.valueForKey("Unique") != null && dictionary.valueForKey("Unique").equals("true")) {
sql.append(" UNIQUE");
}
if (dictionary.valueForKey("Check") != null) {
sql.append(" CHECK ");
sql.append(dictionary.valueForKey("Check"));
}
// Column collation.
if (dictionary.valueForKey("Collate") != null) {
sql.append(" COLLATE ");
sql.append(dictionary.valueForKey("Collate"));
}
}
appendItemToListString(sql.toString(), _listString());
}
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;
}
private void _appendNotNullConstraintIfNecessary(EOAttribute attribute, StringBuilder sql) {
if (!shouldAllowNull(attribute)) {
if (USE_NAMED_CONSTRAINTS) {
sql.append(" CONSTRAINT ");
sql.append(notNullConstraintName(attribute));
}
sql.append(" NOT NULL");
if (isLOBAttribute(attribute))
sql.append(" DEFERRABLE INITIALLY DEFERRED");
}
}
@Override
public String columnTypeStringForAttribute(EOAttribute attribute) {
String externalTypeName = attribute.externalType();
NSDictionary typeInfo = (NSDictionary) jdbcInfo().objectForKey(JDBCAdaptor.TypeInfoKey);
if (typeInfo == null) {
typeInfo = JDBCAdaptor.typeInfoForModel(((EOEntity) attribute.parent()).model());
}
NSDictionary externalTypeInfo = (NSDictionary) typeInfo.objectForKey(externalTypeName);
if (externalTypeInfo == null && externalTypeName != null) {
externalTypeInfo = (NSDictionary) typeInfo.objectForKey(externalTypeName.toUpperCase());
}
if (externalTypeInfo == null) {
throw new JDBCAdaptorException("Unable to find type information for external type '" + externalTypeName + "' in attribute '" + attribute.name() + "' of entity '" + ((EOEntity) attribute.parent()).name() + "'. Check spelling and capitalization.", null);
}
int createParams;
try {
Object createParamsObj = externalTypeInfo.objectForKey("createParams");
if (createParamsObj instanceof Integer) {
createParams = ((Integer) createParamsObj).intValue();
}
else {
createParams = Integer.parseInt((String) createParamsObj);
}
}
catch (NumberFormatException numberformatexception) {
createParams = 0;
}
switch (createParams) {
case 2:
int precision = attribute.precision();
if (precision == 0) {
return attribute.externalType();
}
int scale = attribute.scale();
if (scale == 0) {
return attribute.externalType() + "(" + precision + ")";
}
else {
return attribute.externalType() + "(" + precision + "," + scale + ")";
}
case 1:
int length = attribute.width();
if (length == 0) {
length = attribute.precision();
}
if (length == 0) {
return attribute.externalType();
}
else {
return attribute.externalType() + "(" + length + ")";
}
}
return attribute.externalType();
}
public Class _synchronizationFactoryClass() {
return FrontbaseSynchronizationFactory.class;
}
EOQualifier qualifier() {
return _qualifier;
}
@Override
public String sqlStringForSelector(NSSelector selector, Object value) {
String retStr = null;
if (_frontbaseContainsOperatorFix == null) {
retStr = sqlStringForSelectorTreatingContainsAsLike(selector, value);
} else {
retStr = super.sqlStringForSelector(selector, value);
}
return retStr;
}
protected String sqlStringForSelectorTreatingContainsAsLike(NSSelector qualifierOperator, Object value) {
if (qualifierOperator.equals(EOQualifier.QualifierOperatorContains))
if (value == NSKeyValueCoding.NullValue)
return "is";
else
return "like";
else
return super.sqlStringForSelector(qualifierOperator, value);
}
@Override
public String externalNameQuoteCharacter() {
return "\"";
}
@Override
public String sqlStringForAttribute(EOAttribute attribute) {
String value = super.sqlStringForAttribute(attribute);
if (!useAliases())
value = "\"" + value + "\"";
return value;
}
/**
* 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) {
// Can't handle this
if (!useAliases()) {
return super.assembleJoinClause(leftName, rightName, semantic);
}
String leftAlias = leftName.substring(0, leftName.indexOf("."));
String rightAlias = rightName.substring(0, rightName.indexOf("."));
NSArray 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);
}
String rightTable = rightEntity.valueForSQLExpression(this);
String leftTable = leftEntity.valueForSQLExpression(this);
JoinClause jc = new JoinClause();
jc.setTable1(leftTable, leftAlias);
jc.table2 = rightTable + " " + rightAlias;
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;
}
NSArray joins = r.joins();
int joinsCount = joins.count();
NSMutableArray joinStrings = new NSMutableArray(joinsCount);
for (int i = 0; i < joinsCount; i++) {
EOJoin currentJoin = (EOJoin) joins.objectAtIndex(i);
String left = leftAlias + "." + sqlStringForSchemaObjectName(currentJoin.sourceAttribute().columnName());
String right = rightAlias + "." + sqlStringForSchemaObjectName(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.
*
* @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) {
// Adds the labels in an order by clause to the list of columns in the select
// clause if not already existing.
if (orderByClause != null && orderByClause.length() > 0) {
int i = 0;
while (i != -1) {
if (orderByClause.indexOf(' ', i) == i + 1)
i += 2;
int j = orderByClause.indexOf(' ', i);
int k = orderByClause.indexOf(',', i);
if (j > k && k != -1)
j = k;
else if (j == -1 && k == -1)
j = orderByClause.length();
String orderColumn = orderByClause.substring(i, j);
if (columnList.indexOf(orderColumn) == -1)
columnList = columnList.concat(", " + orderColumn);
i = orderByClause.indexOf(',', i);
}
}
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
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);
}
return sb.toString();
}
/**
* Overrides the parent implementation to compose the final string
* expression for the join clauses.
*/
@Override
public String joinClauseString() {
NSMutableDictionary seenIt = new NSMutableDictionary();
StringBuilder sb = new StringBuilder();
JoinClause jc;
EOSortOrdering.sortArrayUsingKeyOrderArray(_alreadyJoined, new NSArray<EOSortOrdering>(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();
}
@Override
public void addOrderByAttributeOrdering(EOSortOrdering eosortordering) {
NSSelector sortOrdering = eosortordering.selector();
String attribute = eosortordering.key();
String column = sqlStringForAttributeNamed(attribute);
if (column == null)
// Super throws exception.
super.addOrderByAttributeOrdering(eosortordering);
StringBuilder sql = new StringBuilder(column);
if (sortOrdering == EOSortOrdering.CompareCaseInsensitiveAscending)
if (entity()._attributeForPath(attribute).adaptorValueType() == 1)
sql.append(" COLLATE INFORMATION_SCHEMA.CASE_INSENSITIVE ASC");
else
sql.append(" ASC");
else if (sortOrdering == EOSortOrdering.CompareCaseInsensitiveDescending)
if (entity()._attributeForPath(attribute).adaptorValueType() == 1)
sql.append(" COLLATE INFORMATION_SCHEMA.CASE_INSENSITIVE DESC");
else
sql.append(" DESC");
else if (sortOrdering == EOSortOrdering.CompareAscending)
sql.append(" ASC");
else if (sortOrdering == EOSortOrdering.CompareDescending)
sql.append(" DESC");
appendItemToListString(sql.toString(), _orderByString());
}
@Override
public String assembleDeleteStatementWithQualifier(EOQualifier eoqualifier, String table, String qualifier) {
if (table != null && table.indexOf('"') == -1)
return super.assembleDeleteStatementWithQualifier(eoqualifier, quoteTableName(table), qualifier);
else
return super.assembleDeleteStatementWithQualifier(eoqualifier, table, qualifier);
}
@Override
public String assembleInsertStatementWithRow(NSDictionary row, String table, String columns, String values) {
if (table != null && table.indexOf('"') == -1)
return super.assembleInsertStatementWithRow(row, quoteTableName(table), columns, values);
else
return super.assembleInsertStatementWithRow(row, table, columns, values);
}
@Override
public String assembleUpdateStatementWithRow(NSDictionary row, EOQualifier qualifier, String table, String values, String sqlQualifier) {
_qualifier = qualifier;
if (table != null && table.indexOf('"') == -1)
return super.assembleUpdateStatementWithRow(row, qualifier, quoteTableName(table), values, sqlQualifier);
else
return super.assembleUpdateStatementWithRow(row, qualifier, table, values, sqlQualifier);
}
@Override
public String lockClause() {
return "";
}
@Override
public boolean useBindVariables() {
return _useBindVariables;
}
@Override
public boolean shouldUseBindVariableForAttribute(EOAttribute eoattribute) {
return useBindVariables() && !isLOBAttribute(eoattribute);
}
private boolean isLOBAttribute(EOAttribute att) {
int internalType = FrontBaseTypes.internalTypeForExternal(att.externalType());
return internalType == FrontBaseTypes.FB_BLOB || internalType == FrontBaseTypes.FB_CLOB;
}
@Override
public boolean mustUseBindVariableForAttribute(EOAttribute eoattribute) {
return false;
}
@Override
public String sqlStringForCaseInsensitiveLike(String value, String column) {
StringBuilder sql = new StringBuilder();
sql.append(column);
sql.append(" LIKE ");
sql.append(value);
sql.append(" COLLATE INFORMATION_SCHEMA.CASE_INSENSITIVE");
return sql.toString();
}
boolean hasLOBsToUpdate() {
return _lobList != null && _lobList.count() > 0;
}
void resetlobList() {
_lobList = null;
}
NSArray lobList() {
return _lobList != null ? _lobList : NSArray.EmptyArray;
}
@Override
public String sqlStringForKeyValueQualifier(EOKeyValueQualifier eokeyvaluequalifier) {
String attrubute = eokeyvaluequalifier.key();
String column = sqlStringForAttributeNamed(attrubute);
if (column == null)
throw new IllegalStateException("sqlStringForKeyValueQualifier: attempt to generate SQL for " + eokeyvaluequalifier.getClass().getName() + " " + eokeyvaluequalifier + " failed because attribute identified by key '" + attrubute + "' was not reachable from from entity '" + _entity.name() + "'");
Object qualifier = eokeyvaluequalifier.value();
if (qualifier instanceof EOQualifierVariable)
throw new IllegalStateException("sqlStringForKeyValueQualifier: attempt to generate SQL for " + eokeyvaluequalifier.getClass().getName() + " " + eokeyvaluequalifier + " failed because the qualifier variable '$" + ((EOQualifierVariable) qualifier).key() + "' is unbound.");
column = formatSQLString(column, _entity._attributeForPath(attrubute).readFormat());
NSSelector nsselector = eokeyvaluequalifier.selector();
boolean flag = false;
if (_frontbaseContainsOperatorFix == null) {
flag = nsselector.equals(EOQualifier.QualifierOperatorLike) || nsselector.equals(EOQualifier.QualifierOperatorCaseInsensitiveLike) || nsselector.equals(EOQualifier.QualifierOperatorContains);
} else {
flag = nsselector.equals(EOQualifier.QualifierOperatorLike) || nsselector.equals(EOQualifier.QualifierOperatorCaseInsensitiveLike);
}
if (flag) {
qualifier = sqlPatternFromShellPattern(qualifier.toString());
}
StringBuilder sql = new StringBuilder();
char sqlEscapeChar = sqlEscapeChar();
String value;
if (nsselector.equals(EOQualifier.QualifierOperatorCaseInsensitiveLike)) {
value = sqlStringForValue(qualifier, attrubute);
sql.append(sqlStringForCaseInsensitiveLike(value, column));
}
else {
value = sqlStringForValue(qualifier, attrubute);
sql.append(column);
sql.append(' ');
sql.append(sqlStringForSelector(nsselector, qualifier));
sql.append(' ');
sql.append(value);
}
if (value.indexOf(sqlEscapeChar) != -1 && flag) {
sql.append(" ESCAPE '");
sql.append(sqlEscapeChar);
sql.append('\'');
}
return sql.toString();
}
@Override
public String formatValueForAttribute(Object obj, EOAttribute eoattribute) {
if (obj != null && obj != NSKeyValueCoding.NullValue) {
if (eoattribute.valueFactoryMethod() != null && eoattribute.valueFactoryMethod().implementedByObject(obj) && eoattribute.adaptorValueConversionMethod().implementedByObject(obj)) {
obj = eoattribute.adaptorValueByConvertingAttributeValue(obj);
}
if (eoattribute.externalType() == null) {
throw new EOGeneralAdaptorException("Attribute " + eoattribute.name() + " on entity " + eoattribute.entity().name() + " with prototype named " + eoattribute.prototypeName() + " has no external type defined");
}
switch (FrontBaseTypes.internalTypeForExternal(eoattribute.externalType())) {
case FrontBaseTypes.FB_Character:
case FrontBaseTypes.FB_VCharacter: {
return escapedString(obj);
}
case FrontBaseTypes.FB_DayTime: {
return escapedString(obj);
}
case FrontBaseTypes.FB_BLOB:
case FrontBaseTypes.FB_CLOB: {
if (!(obj instanceof String) && eoattribute.valueFactoryMethod() != null) {
Class<?> valueClass = _NSUtilities.classWithName(eoattribute.className());
if (valueClass.isAssignableFrom(obj.getClass())) {
obj = eoattribute.adaptorValueByConvertingAttributeValue(obj);
}
}
if (obj instanceof String)
if (((String) obj).length() == 27 && ((String) obj).startsWith("@"))
return (String) obj;
if (_lobList == null)
_lobList = new NSMutableArray();
_lobList.addObject(eoattribute);
_lobList.addObject(obj);
return "NULL";
}
case FrontBaseTypes.FB_VBit:
case FrontBaseTypes.FB_Bit: {
if (obj instanceof NSData) {
return formatBit((NSData) obj);
}
else {
return "ERROR: Can not convert value from " + obj + " to Bit or Byte data type";
}
}
case FrontBaseTypes.FB_Time: {
StringBuffer time = new StringBuffer("TIME '");
Date d = (Date)eoattribute.adaptorValueByConvertingAttributeValue(obj);
TIME_FORMATTER.get().format(d, time, new FieldPosition(0));
time.append('\'');
return time.toString();
}
case FrontBaseTypes.FB_TimeTZ: {
StringBuffer time = new StringBuffer("TIME '");
Date d = (Date)eoattribute.adaptorValueByConvertingAttributeValue(obj);
SimpleDateFormat formatter = TIME_FORMATTER.get();
formatter.format(d, time, new FieldPosition(0));
time.append(getTimeZone(formatter.getTimeZone()));
time.append('\'');
return time.toString();
}
case FrontBaseTypes.FB_Timestamp: {
StringBuffer time = new StringBuffer("TIMESTAMP '");
Date d = (Date)eoattribute.adaptorValueByConvertingAttributeValue(obj);
TIMESTAMP_FORMATTER.get().format(d, time, new FieldPosition(0));
time.append('\'');
return time.toString();
}
case FrontBaseTypes.FB_TimestampTZ: {
StringBuffer time = new StringBuffer("TIMESTAMP '");
Date d = (Date)eoattribute.adaptorValueByConvertingAttributeValue(obj);
SimpleDateFormat formatter = TIMESTAMP_FORMATTER.get();
formatter.format(d, time, new FieldPosition(0));
time.append(getTimeZone(formatter.getTimeZone()));
time.append('\'');
return time.toString();
}
case FrontBaseTypes.FB_Date: {
StringBuffer time = new StringBuffer("DATE '");
Date d = (Date)eoattribute.adaptorValueByConvertingAttributeValue(obj);
DATE_FORMATTER.get().format(d, time, new FieldPosition(0));
time.append('\'');
return time.toString();
}
case FrontBaseTypes.FB_Boolean: {
if (obj instanceof Boolean) {
return obj.toString();
}
else if (obj instanceof String) {
String str = (String) obj;
if ("yes".equalsIgnoreCase(str) || "y".equalsIgnoreCase(str) || "true".equalsIgnoreCase(str) || "1".equalsIgnoreCase(str)) {
return "TRUE";
}
else if ("no".equalsIgnoreCase(str) || "n".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str) || "0".equalsIgnoreCase(str)) {
return "FALSE";
}
else {
throw new IllegalArgumentException("Unknown boolean value '" + str + "' for the attribute " + eoattribute.entity().name() + "." + eoattribute.name() + ".");
}
}
else if (obj instanceof NSData) {
return (((NSData) obj).bytes()[0] == 0) ? "FALSE" : "TRUE";
}
else if (((Number) obj).intValue() == 0) {
return "FALSE";
}
else {
return "TRUE";
}
}
case FrontBaseTypes.FB_SmallInteger:
case FrontBaseTypes.FB_Float:
case FrontBaseTypes.FB_Real:
case FrontBaseTypes.FB_Double:
case FrontBaseTypes.FB_LongInteger:
case FrontBaseTypes.FB_TinyInteger:
case FrontBaseTypes.FB_Numeric:
case FrontBaseTypes.FB_Integer:
case FrontBaseTypes.FB_Decimal: {
if (obj instanceof BigDecimal) {
return ((BigDecimal) obj).setScale(eoattribute.scale(), BigDecimal.ROUND_HALF_UP).toString();
}
else if (obj instanceof Number) {
String valueType = eoattribute.valueType();
if (valueType == null || "i".equals(valueType)) {
return String.valueOf(((Number) obj).intValue());
}
else if ("l".equals(valueType)) {
return String.valueOf(((Number) obj).longValue());
}
else if ("f".equals(valueType)) {
return String.valueOf(((Number) obj).floatValue());
}
else if ("d".equals(valueType)) {
return String.valueOf(((Number) obj).doubleValue());
}
else if ("s".equals(valueType)) {
return String.valueOf(((Number) obj).shortValue());
}
else if ("c".equals(valueType)) {
return String.valueOf(((Number) obj).intValue());
}
else {
throw new IllegalArgumentException("Unknown number value type '" + valueType + "' for the attribute " + eoattribute.entity().name() + "." + eoattribute.name() + ".");
}
}
else if (obj instanceof Boolean) {
String valueType = eoattribute.valueType();
return String.valueOf(((Boolean) obj).booleanValue() ? 1 : 0);
}
else if (obj instanceof String) {
String str = (String) obj;
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() + ".");
}
}
default:
// MS: I think we should probably throw IllegalArgumentException here, but I'm a little concerned about breaking people's apps.
return escapedString(obj);
}
}
return super.formatValueForAttribute(obj, eoattribute);
}
public String escapedString(Object obj) {
String escapedStr;
String value = obj.toString();
if (value.indexOf("'") == -1) {
escapedStr = "'" + value + "'";
}
else {
escapedStr = "'" + addEscapeChars(value) + "'";
}
return escapedStr;
}
public String addEscapeChars(String value) {
StringBuilder sb = new StringBuilder(value);
int index = 0;
for (int i = 0; i < value.length(); i++, index++) {
index = value.indexOf("'", index);
if (index == -1)
break;
sb.insert(index + i, "'");
}
return sb.toString();
}
String formatBit(NSData data) {
char[] heximals = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
byte[] bytes = data.bytes();
int b;
StringBuilder result = new StringBuilder((2 * data.length()) + 3);
result.append("X'");
for (int i = 0; i < data.length(); i++) {
b = bytes[i] & 0xFF;
result.append(heximals[b / 16]);
result.append(heximals[b % 16]);
}
result.append('\'');
return result.toString();
}
private String getTimeZone(java.util.TimeZone tz) {
String sign = "+";
int tzOffset = tz.getRawOffset();
if (tz.useDaylightTime() && tz.inDaylightTime(new java.util.Date(System.currentTimeMillis()))) {
tzOffset += 3600000L;
}
if (tzOffset < 0) {
tzOffset = -tzOffset;
sign = "-";
}
int hour = tzOffset / 3600000;
int minute = (tzOffset % 3600000) / 60000;
String hourString = String.valueOf(hour);
String minuteString = String.valueOf(minute);
if (hourString.length() < 2)
hourString = "0" + hourString;
if (minuteString.length() < 2)
minuteString = "0" + minuteString;
return sign + hourString + ":" + minuteString;
}
/**
* 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;
}
/**
* Helper class that stores a join definition and
* helps <code>FrontbaseExpression</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;
}
}
/**
* Returns the table alias for the first table (e.g. returns T2 if table 1 is "Students" T2). This makes this class "sortable"
* which is needed to correctly assemble a join clause.
*
* @return the table alias (e.g. returns T2 if table1 is "Students" T2)
*/
public String sortKey() {
return sortKey;
}
}
}
}