package com.webobjects.jdbcadaptor;
import org.apache.commons.lang3.StringUtils;
import com.webobjects.eoaccess.EOAdaptor;
import com.webobjects.eoaccess.EOAttribute;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOModel;
import com.webobjects.eoaccess.EORelationship;
import com.webobjects.eoaccess.EOSQLExpression;
import com.webobjects.eoaccess.EOSchemaGeneration;
import com.webobjects.eoaccess.EOSchemaSynchronization;
import com.webobjects.eoaccess.EOSynchronizationFactory;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableSet;
import com.webobjects.foundation._NSDictionaryUtilities;
import com.webobjects.foundation._NSStringUtilities;
/**
* A synchronization factory usable outside EOModeler
*
* @author giorgio_v
*/
public class PostgresqlSynchronizationFactory extends EOSynchronizationFactory implements EOSchemaGeneration, EOSchemaSynchronization {
public static final String USING_KEY = "USING";
private Boolean _enableIdentifierQuoting;
public PostgresqlSynchronizationFactory(EOAdaptor adaptor) {
super(adaptor);
}
@Override
public String _columnCreationClauseForAttribute(EOAttribute attribute) {
return addCreateClauseForAttribute(attribute).toString();
}
public StringBuffer addCreateClauseForAttribute(EOAttribute eoattribute) {
EOSQLExpression expression = _expressionForEntity(eoattribute.entity());
expression.addCreateClauseForAttribute(eoattribute);
return new StringBuffer(expression.listString());
}
private boolean enableIdentifierQuoting() {
if(_enableIdentifierQuoting == null) {
_enableIdentifierQuoting = Boolean.getBoolean(PostgresqlExpression.class.getName() + ".enableIdentifierQuoting") ? Boolean.TRUE : Boolean.FALSE;
}
return _enableIdentifierQuoting.booleanValue();
}
@Override
protected String formatTableName(String name) {
if (!enableIdentifierQuoting()) {
return name;
}
//Needs to replicate functionality of EOSQLExpression.sqlStringForSchemaObjectName(String name). For example MySchema.MyTable needs to be quoted "MySchema"."MyTable"
NSArray components = NSArray.componentsSeparatedByString(name, ".");
return "\"" + components.componentsJoinedByString("\".\"") + "\"";
}
@Override
protected String formatColumnName(String name) {
if (!enableIdentifierQuoting()) {
return name;
}
return "\"" + name + "\"";
}
@Override
public NSArray<EOSQLExpression> _foreignKeyConstraintStatementsForEntityGroup(NSArray<EOEntity> entityGroup) {
if (entityGroup == null)
return NSArray.EmptyArray;
NSMutableArray<EOSQLExpression> result = new NSMutableArray<>();
NSMutableSet<String> generatedStatements = new NSMutableSet<>();
for (EOEntity currentEntity : entityGroup) {
if (currentEntity.externalName() != null) {
NSArray<EORelationship> relationships = currentEntity.relationships();
for (EORelationship currentRelationship : relationships) {
if (_shouldGenerateForeignKeyConstraints(currentRelationship)) {
NSArray<EOSQLExpression> statements = foreignKeyConstraintStatementsForRelationship(currentRelationship);
if (!generatedStatements.containsObject(statements.valueForKey("statement"))) {
result.addObjectsFromArray(statements);
generatedStatements.addObject(statements.valueForKey("statement"));
}
}
}
}
}
return result;
}
protected boolean _shouldGenerateForeignKeyConstraints(EORelationship rel) {
EOEntity destinationEntity = rel.destinationEntity();
return !rel.isFlattened() && destinationEntity.externalName() != null && rel.entity().model() == destinationEntity.model();
}
/**
* <code>PostgresqlExpression</code> factory method.
*
* @param entity
* the entity to which <code>PostgresqlExpression</code> is to
* be rooted
* @param statement
* the SQL statement
* @return a <code>PostgresqlExpression</code> rooted to
* <code>entity</code>
*/
private static PostgresqlExpression createExpression(EOEntity entity, String statement) {
PostgresqlExpression result = new PostgresqlExpression(entity);
result.setStatement(statement);
return result;
}
/**
* Generates the PostgreSQL-specific SQL statements to drop the primary key
* support.
*
* @param entityGroup
* an array of <code>EOEntity</code> objects
* @return the array of SQL statements
*/
@Override
public NSArray<EOSQLExpression> dropPrimaryKeySupportStatementsForEntityGroup(NSArray<EOEntity> entityGroup) {
if (entityGroup == null) {
return NSArray.EmptyArray;
}
NSMutableSet<String> sequenceNames = new NSMutableSet<>();
NSMutableArray<EOSQLExpression> results = new NSMutableArray<>();
for (EOEntity entity : entityGroup) {
String sequenceName = PostgresqlPlugIn._sequenceNameForEntity(entity);
if (!sequenceNames.containsObject(sequenceName)) {
sequenceNames.addObject(sequenceName);
String sql = "DROP SEQUENCE " + sequenceName + " CASCADE";
results.addObject(createExpression(entity, sql));
}
}
return results;
}
/**
* Generates the PostgreSQL-specific SQL statements to drop tables.
*
* @param entityGroup
* an array of <code>EOEntity</code> objects
* @return the array of SQL statements
*/
@Override
public NSArray<EOSQLExpression> dropTableStatementsForEntityGroup(NSArray<EOEntity> entityGroup) {
if (entityGroup == null) {
return NSArray.EmptyArray;
}
NSMutableArray<EOSQLExpression> results = new NSMutableArray<>();
for (EOEntity entity : entityGroup) {
// timc 2006-11-06 create result here so we can check for
// enableIdentifierQuoting while building the statement
if (entityUsesSeparateTable(entity)) {
PostgresqlExpression result = new PostgresqlExpression(entity);
String tableName = result.sqlStringForSchemaObjectName(entity.externalName());
result.setStatement("DROP TABLE " + tableName + " CASCADE");
results.addObject(result);
}
}
return results;
}
/**
* Generates the PostgreSQL-specific SQL statements to enforce the foreign
* key constraints for <code>relationship</code>.
*
* @param relationship
* the relationship, as represented by EOF
* @return the array of SQL statements
*/
@Override
public NSArray<EOSQLExpression> foreignKeyConstraintStatementsForRelationship(EORelationship relationship) {
NSMutableArray<EOSQLExpression> results = new NSMutableArray<>();
NSArray<EOSQLExpression> superResults = super.foreignKeyConstraintStatementsForRelationship(relationship);
for (EOSQLExpression expression : superResults) {
String s = expression.statement();
s = StringUtils.replace(s, ") INITIALLY DEFERRED", ") DEFERRABLE INITIALLY DEFERRED");
expression.setStatement(s);
results.addObject(expression);
// timc 2006-11-06 check for enableIdentifierQuoting
String tableNameWithoutSchemaName = externalNameForEntityWithoutSchema(relationship.entity());
String tableName = expression.sqlStringForSchemaObjectName(expression.entity().externalName());
s = StringUtils.replace(s, "ALTER TABLE " + tableNameWithoutSchemaName, "ALTER TABLE " + tableName);
expression.setStatement(s);
NSArray<String> columnNames = ((NSArray<String>) relationship.sourceAttributes().valueForKey("columnName"));
StringBuilder sbColumnNames = new StringBuilder();
for (int j = 0; j < columnNames.count(); j++) {
sbColumnNames.append((j == 0 ? "" : ", ") + expression.sqlStringForSchemaObjectName(columnNames.objectAtIndex(j)));
}
String indexName = externalNameForEntityWithoutSchema(relationship.entity()) + "_" + columnNames.componentsJoinedByString("_") + "_idx";
results.addObject(createExpression(expression.entity(), "CREATE INDEX " + indexName + " ON " + tableName + "( " + sbColumnNames.toString() + " )"));
}
return results;
}
protected String externalNameForEntityWithoutSchema(EOEntity entity) {
String externalName = entity.externalName();
if (externalName != null) {
int dotIndex = externalName.indexOf('.');
if (dotIndex != -1) {
externalName = externalName.substring(dotIndex + 1);
}
}
return externalName;
}
/**
* Generates the PostgreSQL-specific SQL statements to enforce primary key
* constraints.
*
* @param entityGroup
* an array of <code>EOEntity</code> objects
* @return the array of SQL statements
*/
@Override
public NSArray<EOSQLExpression> primaryKeyConstraintStatementsForEntityGroup(NSArray<EOEntity> entityGroup) {
NSMutableArray<EOSQLExpression> results = new NSMutableArray<>();
for (EOEntity entity : entityGroup) {
if (!entityUsesSeparateTable(entity))
continue;
// timc 2006-11-06 create result here so we can check for
// enableIdentifierQuoting while building the statement
PostgresqlExpression result = new PostgresqlExpression(entity);
String constraintName = result.sqlStringForSchemaObjectName(externalNameForEntityWithoutSchema(entity) + "_pk");
String tableName = result.sqlStringForSchemaObjectName(entity.externalName());
StringBuilder statement = new StringBuilder("ALTER TABLE ");
statement.append(tableName);
statement.append(" ADD CONSTRAINT ");
statement.append(constraintName);
statement.append(" PRIMARY KEY (");
NSArray<EOAttribute> priKeyAttributes = entity.primaryKeyAttributes();
int priKeyAttributeCount = priKeyAttributes.count();
for (int j = 0; j < priKeyAttributeCount; j++) {
EOAttribute priKeyAttribute = priKeyAttributes.objectAtIndex(j);
String attributeName = result.sqlStringForAttribute(priKeyAttribute);
statement.append(attributeName);
if (j < priKeyAttributeCount - 1) {
statement.append(", ");
} else {
statement.append(')');
}
}
result.setStatement(statement.toString());
results.addObject(result);
}
return results;
}
/**
* Returns true if Entity Modeler is running the operation on this model.
*
* @param model the model to check
* @return true if Entity Modeler is running
*/
protected boolean isInEntityModeler(EOModel model) {
boolean inEntityModeler = false;
if (model != null) {
NSDictionary<String, Object> userInfo = model.userInfo();
NSDictionary entityModelerDict = (NSDictionary) userInfo.objectForKey("_EntityModeler");
if (entityModelerDict != null) {
Boolean inEntityModelerBoolean = (Boolean)entityModelerDict.objectForKey("inEntityModeler");
if (inEntityModelerBoolean != null && inEntityModelerBoolean.booleanValue()) {
inEntityModeler = inEntityModelerBoolean.booleanValue();
}
}
}
return inEntityModeler;
}
/**
* Generates the PostgreSQL-specific SQL statements to create the primary
* key support.
*
* @param entityGroup
* an array of <code>EOEntity</code> objects
* @return the array of SQL statements
*/
@Override
public NSArray<EOSQLExpression> primaryKeySupportStatementsForEntityGroup(NSArray<EOEntity> entityGroup) {
NSMutableSet<String> sequenceNames = new NSMutableSet<>();
NSMutableArray<EOSQLExpression> results = new NSMutableArray<>();
for (EOEntity entity : entityGroup) {
NSArray<EOAttribute> priKeyAttributes = entity.primaryKeyAttributes();
if (priKeyAttributes.count() == 1) {
EOAttribute priKeyAttribute = priKeyAttributes.objectAtIndex(0);
// Q: Don't create a sequence for non number primary keys
if (priKeyAttribute.adaptorValueType() != EOAttribute.AdaptorNumberType) {
continue;
}
String sequenceName = PostgresqlPlugIn._sequenceNameForEntity(entity);
if (!sequenceNames.containsObject(sequenceName)) {
sequenceNames.addObject(sequenceName);
// timc 2006-11-06 create result here so we can check for
// enableIdentifierQuoting while building the statement
PostgresqlExpression result = new PostgresqlExpression(entity);
String attributeName = result.sqlStringForAttribute(priKeyAttribute);
String tableName = result.sqlStringForSchemaObjectName(entity.externalName());
String sql = "CREATE SEQUENCE " + sequenceName;
results.addObject(createExpression(entity, sql));
sql = "CREATE TEMP TABLE EOF_TMP_TABLE AS SELECT SETVAL('" + sequenceName + "', (SELECT MAX(" + attributeName + ") FROM " + tableName + "))";
results.addObject(createExpression(entity, sql));
// In Entity Modeler, we want to skip over the drop statement
if (!isInEntityModeler(entity.model())) {
sql = "DROP TABLE EOF_TMP_TABLE";
results.addObject(createExpression(entity, sql));
}
sql = "ALTER TABLE " + tableName + " ALTER COLUMN " + attributeName + " SET DEFAULT nextval( '" + sequenceName + "' )";
results.addObject(createExpression(entity, sql));
}
}
}
return results;
}
public static boolean entityUsesSeparateTable(EOEntity entity) {
if (entity.parentEntity() == null)
return true;
EOEntity parent = entity.parentEntity();
while (parent != null) {
if (!entity.externalName().equals(parent.externalName()))
return true;
entity = parent;
parent = entity.parentEntity();
}
return false;
}
/**
* Quote table name if necessary
*/
@Override
public NSArray<EOSQLExpression> createTableStatementsForEntityGroup(NSArray<EOEntity> entityGroup) {
NSMutableSet<String> columnNames = new NSMutableSet<>();
StringBuffer aStatement = new StringBuffer(128);
if (entityGroup != null && entityGroup.count() > 0) {
EOSQLExpression sqlExpr = _expressionForEntity(entityGroup.objectAtIndex(0));
for (EOEntity entity : entityGroup) {
for (EOAttribute attribute : entity.attributes()) {
String columnName = attribute.columnName();
if (!attribute.isDerived() && !attribute.isFlattened() && columnName != null && columnName.length() > 0 && !columnNames.contains(columnName)) {
sqlExpr.appendItemToListString(_columnCreationClauseForAttribute(attribute), aStatement);
columnNames.addObject(columnName);
}
}
}
return new NSArray<>(_expressionForString(new StringBuilder().append("CREATE TABLE ").append(formatTableName(entityGroup.objectAtIndex(0).externalName())).append(" (").append(aStatement.toString()).append(')').toString()));
}
return NSArray.EmptyArray;
}
/**
* 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.
* @deprecated use {@link StringUtils#replace(String, String, String)} instead
*/
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();
}
// I blame statementstToConvertColumnType for not taking a damn EOAttribute for
// having to steal this from EOSQLExpression
public String columnTypeStringForAttribute(EOAttribute attribute) {
if (attribute.precision() != 0) {
String precision = String.valueOf(attribute.precision());
String scale = String.valueOf(attribute.scale());
return _NSStringUtilities.concat(attribute.externalType(), "(", precision, ",", scale, ")");
}
if (attribute.width() != 0) {
String width = String.valueOf(attribute.width());
return _NSStringUtilities.concat(attribute.externalType(), "(", width, ")");
}
return attribute.externalType();
}
@Override
public NSArray<EOSQLExpression> statementsToModifyColumnNullRule(String columnName, String tableName, boolean allowsNull, NSDictionary nsdictionary) {
NSArray<EOSQLExpression> statements;
if (allowsNull) {
statements = new NSArray<>(_expressionForString("alter table " + formatTableName(tableName) + " alter column " + formatColumnName(columnName) + " drop not null"));
} else {
statements = new NSArray<>(_expressionForString("alter table " + formatTableName(tableName) + " alter column " + formatColumnName(columnName) + " set not null"));
}
return statements;
}
@Override
public NSArray<EOSQLExpression> statementsToConvertColumnType(String columnName, String tableName, ColumnTypes oldType, ColumnTypes newType, NSDictionary options) {
EOAttribute attr = new EOAttribute();
attr.setName(columnName);
attr.setColumnName(columnName);
attr.setExternalType(newType.name());
attr.setScale(newType.scale());
attr.setPrecision(newType.precision());
attr.setWidth(newType.width());
String usingClause = "";
String columnTypeString = columnTypeStringForAttribute(attr);
if (options != null) {
String usingExpression = (String) options.objectForKey(PostgresqlSynchronizationFactory.USING_KEY);
if (usingExpression != null) {
usingClause = " USING " + usingExpression;
}
}
NSArray<EOSQLExpression> statements = new NSArray<>(_expressionForString("alter table " + formatTableName(tableName) + " alter column " + formatColumnName(columnName) + " type " + columnTypeString + usingClause));
return statements;
}
@Override
public NSArray<EOSQLExpression> statementsToRenameColumnNamed(String columnName, String tableName, String newName, NSDictionary nsdictionary) {
return new NSArray<>(_expressionForString("alter table " + formatTableName(tableName) + " rename column " + formatColumnName(columnName) + " to " + formatColumnName(newName)));
}
@Override
public NSArray<EOSQLExpression> statementsToInsertColumnForAttribute(EOAttribute attribute, NSDictionary options) {
String clause = _columnCreationClauseForAttribute(attribute);
return new NSArray<>(_expressionForString("alter table " + formatTableName(attribute.entity().externalName()) + " add " + clause));
}
@Override
public NSArray<EOSQLExpression> statementsToRenameTableNamed(String tableName, String newName, NSDictionary options) {
return new NSArray<>(_expressionForString("alter table " + formatTableName(tableName) + " rename to " + formatTableName(newName)));
}
@Override
public NSArray<EOSQLExpression> statementsToDeleteColumnNamed(String columnName, String tableName, NSDictionary options) {
return new NSArray<>(_expressionForString("alter table " + formatTableName(tableName) + " drop column " + formatTableName(columnName) + " cascade"));
}
/*
public StringBuffer addCreateClauseForAttribute(EOAttribute eoattribute) {
EOSQLExpression expression = _expressionForEntity(eoattribute.entity());
expression.addCreateClauseForAttribute(eoattribute);
return new StringBuffer(expression.listString());
}
public String _columnCreationClauseForAttribute(EOAttribute attribute) {
return addCreateClauseForAttribute(attribute).toString();
}
*/
@Override
public String schemaCreationScriptForEntities(NSArray allEntities, NSDictionary options)
{
StringBuffer result = new StringBuffer();
if (options == null)
options = NSDictionary.EmptyDictionary;
NSArray statements = schemaCreationStatementsForEntities(allEntities, options);
int i = 0;
for (int count = statements.count(); i < count; i++)
appendExpressionToScript((EOSQLExpression) statements.objectAtIndex(i),
result);
return result.toString();
}
@Override
public NSArray schemaCreationStatementsForEntities(NSArray allEntities, NSDictionary options)
{
NSMutableArray result = new NSMutableArray();
if (allEntities == null || allEntities.count() == 0)
return result;
if (options == null)
options = NSDictionary.EmptyDictionary;
NSDictionary connectionDictionary = ((EOEntity) allEntities.lastObject()).model()
.connectionDictionary();
boolean createDatabase = _NSDictionaryUtilities.boolValueForKeyDefault(options,
"createDatabase", false);
boolean dropDatabase = _NSDictionaryUtilities.boolValueForKeyDefault(options,
"dropDatabase", false);
if (createDatabase || dropDatabase) {
boolean adminCommentsNeeded = false;
NSArray dropDatabaseStatements = null;
NSArray createDatabaseStatements = null;
if (dropDatabase) {
dropDatabaseStatements = dropDatabaseStatementsForConnectionDictionary(
connectionDictionary, null);
if (dropDatabaseStatements == null)
dropDatabaseStatements = new NSArray(
_expressionForString("/* The 'Drop Database' option is unavailable. */"));
else
adminCommentsNeeded = true;
}
if (createDatabase) {
createDatabaseStatements = createDatabaseStatementsForConnectionDictionary(
connectionDictionary, null);
if (createDatabaseStatements == null)
createDatabaseStatements = new NSArray(
_expressionForString("/* The 'Create Database' option is unavailable. */"));
else
adminCommentsNeeded = true;
}
if (adminCommentsNeeded)
result.addObject(_expressionForString("/* connect as an administrator */"));
if (dropDatabaseStatements != null)
result.addObjectsFromArray(dropDatabaseStatements);
if (createDatabaseStatements != null)
result.addObjectsFromArray(createDatabaseStatements);
if (adminCommentsNeeded)
result.addObject(_expressionForString("/* connect as the user from the connection dictionary */"));
}
if (_NSDictionaryUtilities.boolValueForKeyDefault(options,
"dropPrimaryKeySupport", true)) {
NSArray entityGroups = primaryKeyEntityGroupsForEntities(allEntities);
result.addObjectsFromArray(dropPrimaryKeySupportStatementsForEntityGroups(entityGroups));
}
if (_NSDictionaryUtilities.boolValueForKeyDefault(options, "dropTables", true)) {
NSArray entityGroups = tableEntityGroupsForEntities(allEntities);
result.addObjectsFromArray(dropTableStatementsForEntityGroups(entityGroups));
}
if (_NSDictionaryUtilities.boolValueForKeyDefault(options, "createTables", true)) {
NSArray entityGroups = tableEntityGroupsForEntities(allEntities);
result.addObjectsFromArray(createTableStatementsForEntityGroups(entityGroups));
}
if (_NSDictionaryUtilities.boolValueForKeyDefault(options,
"createPrimaryKeySupport", true)) {
NSArray entityGroups = primaryKeyEntityGroupsForEntities(allEntities);
result.addObjectsFromArray(primaryKeySupportStatementsForEntityGroups(entityGroups));
}
if (_NSDictionaryUtilities.boolValueForKeyDefault(options,
"primaryKeyConstraints", true)) {
NSArray entityGroups = tableEntityGroupsForEntities(allEntities);
result.addObjectsFromArray(primaryKeyConstraintStatementsForEntityGroups(entityGroups));
}
if (_NSDictionaryUtilities.boolValueForKeyDefault(options,
"foreignKeyConstraints", false)) {
NSArray entityGroups = tableEntityGroupsForEntities(allEntities);
int i = 0;
for (int iCount = entityGroups.count(); i < iCount; i++)
result.addObjectsFromArray(_foreignKeyConstraintStatementsForEntityGroup((NSArray) entityGroups
.objectAtIndex(i)));
}
return result;
}
}